https://github.com/libsdl-org/Maelstrom/commit/1fa7f042a311643af9a78f95349aeb7201ee299e
From 1fa7f042a311643af9a78f95349aeb7201ee299e Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 27 Jan 2013 15:39:52 -0800
Subject: [PATCH] Added missing file
---
Xcode-iOS/ServiceManager_StoreKit.mm | 475 +++++++++++++++++++++++++++
1 file changed, 475 insertions(+)
create mode 100644 Xcode-iOS/ServiceManager_StoreKit.mm
diff --git a/Xcode-iOS/ServiceManager_StoreKit.mm b/Xcode-iOS/ServiceManager_StoreKit.mm
new file mode 100644
index 00000000..fd32a506
--- /dev/null
+++ b/Xcode-iOS/ServiceManager_StoreKit.mm
@@ -0,0 +1,475 @@
+
+#include "../game/store.h"
+#include "../utils/array.h"
+#include "../utils/prefs.h"
+
+#import <StoreKit/StoreKit.h>
+
+
+enum StoreEvent
+{
+ STORE_EVENT_SHOW_PURCHASE_DIALOG,
+ STORE_EVENT_PRODUCT_QUERY_FAILED,
+ STORE_EVENT_UPDATED_TRANSACTIONS,
+ NUM_STORE_EVENTS
+};
+
+extern Prefs *prefs;
+
+//
+// The StoreManagerStoreKit class implements the game side interface and
+// the StoreManagerDelegate handles the Objective C interaction with Store Kit.
+// Each class has a reference to the other so they can call back and forth.
+//
+class StoreManagerStoreKit;
+
+@interface StoreManagerDelegate : NSObject <SKProductsRequestDelegate,
+SKPaymentTransactionObserver> {
+@private
+ StoreManagerStoreKit *m_manager;
+}
+
+- (id)initWithStoreManager:(StoreManagerStoreKit *)manager;
+- (void)cleanup;
+
+// StoreManager interfaces
+- (void)promptPurchase:(NSString *)productIdentifier;
+- (void)completePurchase:(SKProduct *)product;
+- (void)handleEvent:(int)eventCode withData:(void*)data1 andData:(void*)data2;
+
+- (void)completeTransaction:(SKPaymentTransaction *)transaction;
+- (void)restoreTransaction:(SKPaymentTransaction *)transaction;
+- (void)failedTransaction:(SKPaymentTransaction *)transaction;
+- (void)recordTransaction:(SKPaymentTransaction *)transaction;
+
+- (NSString *)getProductPriceString:(SKProduct *)product;
+
+@end // StoreManagerDelegate
+
+
+static struct {
+ const char *feature;
+ const char *product;
+} SupportedFeatures[] = {
+ { FEATURE_KIDMODE, "maelstrom_kidmode" },
+ { FEATURE_NETWORK, "maelstrom_network_play" },
+};
+
+class StoreManagerStoreKit : public StoreManager
+{
+public:
+ StoreManagerStoreKit();
+ virtual ~StoreManagerStoreKit();
+
+ virtual bool IsAvailable() { return true; }
+ virtual bool HasFeature(const char *feature);
+ virtual void GrantFeature(const char *feature);
+ virtual void ActivateFeature(const char *feature,
+ const char *action_activate,
+ const char *action_canceled);
+
+ virtual void PurchaseDialogResult(void *context, bool shouldPurchase);
+ virtual void HandleEvent(int eventCode, void *data1, void *data2);
+
+ // Called by the Store Kit if a purchase was canceled
+ void ProductsCanceled(const char *message);
+ void ProductCanceled(NSString *productIdentifier, const char *message);
+
+ // Called by the Store Kit delegate when a product is made available
+ void ProductPurchased(NSString *productIdentifier);
+
+protected:
+ NSString *GetProductIDForFeature(const char *feature);
+ const char *GetFeatureForProductID(NSString *productID);
+ const char *GetPreferencesKeyForFeature(const char *feature, char *key, size_t maxlen);
+
+protected:
+ StoreManagerDelegate *m_delegate;
+
+ struct PendingRequest {
+ PendingRequest(NSString *productID,
+ const char *action_activate,
+ const char *action_canceled) {
+ this->productID = [productID retain];
+ if (action_activate) {
+ this->action_activate = SDL_strdup(action_activate);
+ } else {
+ this->action_activate = NULL;
+ }
+ if (action_canceled) {
+ this->action_canceled = SDL_strdup(action_canceled);
+ } else {
+ this->action_canceled = NULL;
+ }
+ }
+ ~PendingRequest() {
+ [this->productID release];
+ if (this->action_activate) {
+ SDL_free(this->action_activate);
+ }
+ if (this->action_canceled) {
+ SDL_free(this->action_canceled);
+ }
+ }
+
+ NSString *productID;
+ char *action_activate;
+ char *action_canceled;
+ };
+ array<PendingRequest*> m_requests;
+};
+
+StoreManager *
+StoreManager::Create()
+{
+ return new StoreManagerStoreKit;
+}
+
+StoreManagerStoreKit::StoreManagerStoreKit() : StoreManager()
+{
+ m_delegate = [[StoreManagerDelegate alloc] initWithStoreManager:this];
+}
+
+StoreManagerStoreKit::~StoreManagerStoreKit()
+{
+ [m_delegate cleanup];
+ [m_delegate release];
+}
+
+bool
+StoreManagerStoreKit::HasFeature(const char *feature)
+{
+ char key[BUFSIZ];
+ return prefs->GetBool(GetPreferencesKeyForFeature(feature, key, sizeof(key)));
+}
+
+void
+StoreManagerStoreKit::GrantFeature(const char *feature)
+{
+ char key[BUFSIZ];
+ prefs->SetBool(GetPreferencesKeyForFeature(feature, key, sizeof(key)), true);
+ prefs->Save();
+}
+
+void
+StoreManagerStoreKit::ActivateFeature(const char *feature,
+ const char *action_activate,
+ const char *action_canceled)
+{
+ if (HasFeature(feature)) {
+ StoreManager::PerformAction(action_activate);
+ return;
+ }
+
+ NSString *productIdentifier = GetProductIDForFeature(feature);
+ if (productIdentifier == nil) {
+ char message[BUFSIZ];
+ SDL_snprintf(message, sizeof(message), TEXT("Store requested unknown feature: %s"), feature);
+ ShowMessage(message);
+ PerformAction(action_canceled);
+ return;
+ }
+
+ for (int i = 0; i < m_requests.length(); ++i) {
+ if ([m_requests[i]->productID isEqualToString:productIdentifier] &&
+ SDL_strcmp(action_activate, m_requests[i]->action_activate) == 0) {
+ // We're on it!
+ return;
+ }
+ }
+
+ ShowMessage(TEXT("Connecting to App Store..."), NULL);
+
+ m_requests.add(new PendingRequest(productIdentifier, action_activate, action_canceled));
+ [m_delegate promptPurchase:productIdentifier];
+}
+
+void
+StoreManagerStoreKit::PurchaseDialogResult(void *context, bool shouldPurchase)
+{
+ SKProduct *product = (SKProduct *)context;
+ if (shouldPurchase) {
+ [m_delegate completePurchase:product];
+ } else {
+ ProductCanceled(product.productIdentifier, NULL);
+ }
+ [product release];
+}
+
+void
+StoreManagerStoreKit::HandleEvent(int eventCode, void *data1, void *data2)
+{
+ [m_delegate handleEvent:eventCode withData:data1 andData:data2];
+}
+
+void
+StoreManagerStoreKit::ProductsCanceled(const char *message)
+{
+ while (m_requests.length() > 0) {
+ PendingRequest *request = m_requests[0];
+ ProductCanceled(request->productID, message);
+ }
+}
+
+void
+StoreManagerStoreKit::ProductCanceled(NSString *productIdentifier, const char *message)
+{
+ for (int i = 0; i < m_requests.length();) {
+ if ([m_requests[i]->productID isEqualToString:productIdentifier]) {
+ PendingRequest *request = m_requests[i];
+ m_requests.remove(request);
+ if (m_requests.length() == 0) {
+ HideMessage();
+ }
+ ShowPurchaseCanceled(message, request->action_canceled);
+ delete request;
+ } else {
+ ++i;
+ }
+ }
+}
+
+void
+StoreManagerStoreKit::ProductPurchased(NSString *productIdentifier)
+{
+ const char *feature = GetFeatureForProductID(productIdentifier);
+ if (!feature) {
+ NSLog(@"Error: Couldn't find feature for product %@", productIdentifier);
+ return;
+ }
+
+ // Note that we now own this feature and save immediately!
+ GrantFeature(feature);
+
+ for (int i = 0; i < m_requests.length();) {
+ if ([m_requests[i]->productID isEqualToString:productIdentifier]) {
+ PendingRequest *request = m_requests[i];
+ m_requests.remove(request);
+ if (m_requests.length() == 0) {
+ HideMessage();
+ }
+ ShowPurchaseComplete(request->action_activate);
+ delete request;
+ } else {
+ ++i;
+ }
+ }
+}
+
+static NSString *GetProductIdentifier(const char *product)
+{
+ return [NSString stringWithUTF8String:product];
+}
+
+NSString *
+StoreManagerStoreKit::GetProductIDForFeature(const char *feature)
+{
+ for (int i = 0; i < SDL_arraysize(SupportedFeatures); ++i) {
+ if (SDL_strcasecmp(feature, SupportedFeatures[i].feature) == 0) {
+ return GetProductIdentifier(SupportedFeatures[i].product);
+ }
+ }
+ return nil;
+}
+
+const char *
+StoreManagerStoreKit::GetFeatureForProductID(NSString *productID)
+{
+ for (int i = 0; i < SDL_arraysize(SupportedFeatures); ++i) {
+ if ([productID isEqualToString:GetProductIdentifier(SupportedFeatures[i].product)]) {
+ return SupportedFeatures[i].feature;
+ }
+ }
+ return nil;
+}
+
+const char *
+StoreManagerStoreKit::GetPreferencesKeyForFeature(const char *feature, char *key, size_t maxlen)
+{
+ SDL_snprintf(key, maxlen, "Feature.%s", feature);
+ return key;
+}
+
+
+@implementation StoreManagerDelegate
+
+- (id)initWithStoreManager:(StoreManagerStoreKit *)manager
+{
+ self = [super init];
+ if (self == nil) {
+ return nil;
+ }
+
+ self->m_manager = manager;
+
+ // Add ourselves as an observer so we can catch any payments being processed
+ [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
+
+ return self;
+}
+
+- (void)cleanup
+{
+}
+
+//
+// StoreManagerDelegate
+//
+
+- (void)promptPurchase:(NSString *)productIdentifier
+{
+ if ([SKPaymentQueue canMakePayments]) {
+ SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:
+ [NSSet setWithObject: productIdentifier]];
+ request.delegate = self;
+ [request start];
+ } else {
+ m_manager->ProductCanceled(productIdentifier, TEXT("In-App purchases are disabled in Settings > General > Restrictions. Please enable them to access this feature."));
+ }
+}
+
+- (void)completePurchase:(SKProduct *)product
+{
+ SKPayment *payment = [SKPayment paymentWithProduct:product];
+ [[SKPaymentQueue defaultQueue] addPayment:payment];
+}
+
+- (void)handleEvent:(int)eventCode withData:(void *)data1 andData:(void *)data2
+{
+ switch (eventCode) {
+ case STORE_EVENT_SHOW_PURCHASE_DIALOG:
+ {
+ SKProduct *product = (SKProduct *)data1;
+ m_manager->ShowPurchaseDialog(product,
+ [product.localizedTitle UTF8String],
+ [product.localizedDescription UTF8String],
+ [[self getProductPriceString:product] UTF8String]);
+ break;
+ }
+ case STORE_EVENT_PRODUCT_QUERY_FAILED:
+ {
+ NSString *productIdentifier = (NSString *)data1;
+ if (productIdentifier) {
+ char message[BUFSIZ];
+ SDL_snprintf(message, sizeof(message), TEXT("Unknown product %s"), [productIdentifier UTF8String]);
+ m_manager->ProductCanceled(productIdentifier, message);
+ [productIdentifier release];
+ } else {
+ // Unfortunately we don't have any context here as to which product failed.
+ // We could fix this by having a custom object acting as the request delegate instead this class.
+ // Or we can just cancel all the pending requests. :)
+ m_manager->ProductsCanceled(TEXT("Store unavailable"));
+ }
+ break;
+ }
+ case STORE_EVENT_UPDATED_TRANSACTIONS:
+ {
+ NSArray *transactions = (NSArray *)data1;
+ for (SKPaymentTransaction *transaction in transactions) {
+ switch (transaction.transactionState) {
+ case SKPaymentTransactionStatePurchased:
+ [self completeTransaction:transaction];
+ break;
+ case SKPaymentTransactionStateFailed:
+ [self failedTransaction:transaction];
+ break;
+ case SKPaymentTransactionStateRestored:
+ [self restoreTransaction:transaction];
+ break;
+ }
+ }
+ [transactions release];
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+//
+// SKProductsRequestDelegate
+//
+
+- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
+{
+ for (SKProduct *product in response.products) {
+ [product retain];
+ m_manager->PushEvent(STORE_EVENT_SHOW_PURCHASE_DIALOG, product);
+ }
+ for (NSString *productIdentifier in response.invalidProductIdentifiers) {
+ [productIdentifier retain];
+ m_manager->PushEvent(STORE_EVENT_PRODUCT_QUERY_FAILED, productIdentifier);
+ }
+ [request release];
+}
+
+- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
+{
+ m_manager->PushEvent(STORE_EVENT_PRODUCT_QUERY_FAILED, NULL);
+ [request release];
+}
+
+/// end SKProductsRequestDelegate
+
+//
+// SKPaymentTransactionObserver
+//
+
+- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
+{
+ [transactions retain];
+ m_manager->PushEvent(STORE_EVENT_UPDATED_TRANSACTIONS, transactions);
+}
+
+- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
+{
+ // We don't download any content, so we shouldn't need to do anything
+}
+
+// end SKPaymentTransactionObserver
+
+- (void)completeTransaction:(SKPaymentTransaction *)transaction
+{
+ [self recordTransaction:transaction];
+ m_manager->ProductPurchased(transaction.payment.productIdentifier);
+ [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
+}
+
+- (void)restoreTransaction:(SKPaymentTransaction *)transaction
+{
+ [self recordTransaction:transaction];
+ m_manager->ProductPurchased(transaction.originalTransaction.payment.productIdentifier);
+ [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
+}
+
+- (void)failedTransaction:(SKPaymentTransaction *)transaction
+{
+ char message[BUFSIZ];
+ if (transaction.error.code == SKErrorPaymentCancelled) {
+ message[0] = '\0';
+ } else {
+ SDL_snprintf(message, sizeof(message), TEXT("Failed purchase transaction for %s: %s"),
+ [transaction.payment.productIdentifier UTF8String],
+ [[transaction.error localizedDescription] UTF8String]);
+ }
+ m_manager->ProductCanceled(transaction.payment.productIdentifier, message);
+ [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
+}
+
+- (void)recordTransaction:(SKPaymentTransaction *)transaction
+{
+ // Nothing to do here, at least for now.
+}
+
+- (NSString *)getProductPriceString:(SKProduct *)product
+{
+ NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
+ [formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
+ [formatter setLocale:product.priceLocale];
+
+ NSString *str = [formatter stringFromNumber:product.price];
+ [formatter release];
+ return str;
+}
+
+@end // StoreManagerDelegate