summaryrefslogtreecommitdiff
path: root/platform/iphone
diff options
context:
space:
mode:
authorRĂ©mi Verschelde <rverschelde@gmail.com>2020-10-20 15:10:15 +0200
committerGitHub <noreply@github.com>2020-10-20 15:10:15 +0200
commit15ff276fcf1f5a0ab1f8f4228df54aa1ed707cd9 (patch)
treecc1d445af9e9de13769600548da9013127f376fa /platform/iphone
parent9dad483920ffa3c32a03f1f4cc869b3f7d1cbe0e (diff)
parent21bcf3f7939b0ffd53572fb4ce376ecdb71d659a (diff)
Merge pull request #42853 from naithar/fix/in-app-store-master
[4.0] [iOS] InAppStore fixes
Diffstat (limited to 'platform/iphone')
-rw-r--r--platform/iphone/in_app_store.h14
-rw-r--r--platform/iphone/in_app_store.mm284
2 files changed, 201 insertions, 97 deletions
diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h
index ccf31876a9..1b8f84ec7b 100644
--- a/platform/iphone/in_app_store.h
+++ b/platform/iphone/in_app_store.h
@@ -35,6 +35,17 @@
#include "core/class_db.h"
+#ifdef __OBJC__
+@class GodotProductsDelegate;
+@class GodotTransactionsObserver;
+
+typedef GodotProductsDelegate InAppStoreProductDelegate;
+typedef GodotTransactionsObserver InAppStoreTransactionObserver;
+#else
+typedef void InAppStoreProductDelegate;
+typedef void InAppStoreTransactionObserver;
+#endif
+
class InAppStore : public Object {
GDCLASS(InAppStore, Object);
@@ -43,6 +54,9 @@ class InAppStore : public Object {
List<Variant> pending_events;
+ InAppStoreProductDelegate *products_request_delegate;
+ InAppStoreTransactionObserver *transactions_observer;
+
public:
Error request_product_info(Dictionary p_params);
Error restore_purchases();
diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm
index 2b973dfbcf..3eec9dae69 100644
--- a/platform/iphone/in_app_store.mm
+++ b/platform/iphone/in_app_store.mm
@@ -32,24 +32,22 @@
#include "in_app_store.h"
-extern "C" {
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
-};
-bool auto_finish_transactions = true;
-NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
-static NSArray *latestProducts;
+InAppStore *InAppStore::instance = NULL;
@interface SKProduct (LocalizedPrice)
@property(nonatomic, readonly) NSString *localizedPrice;
+
@end
//----------------------------------//
// SKProduct extension
//----------------------------------//
@implementation SKProduct (LocalizedPrice)
+
- (NSString *)localizedPrice {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
@@ -61,29 +59,91 @@ static NSArray *latestProducts;
@end
-InAppStore *InAppStore::instance = NULL;
-
-void InAppStore::_bind_methods() {
- ClassDB::bind_method(D_METHOD("request_product_info"), &InAppStore::request_product_info);
- ClassDB::bind_method(D_METHOD("restore_purchases"), &InAppStore::restore_purchases);
- ClassDB::bind_method(D_METHOD("purchase"), &InAppStore::purchase);
+@interface GodotProductsDelegate : NSObject <SKProductsRequestDelegate>
- ClassDB::bind_method(D_METHOD("get_pending_event_count"), &InAppStore::get_pending_event_count);
- ClassDB::bind_method(D_METHOD("pop_pending_event"), &InAppStore::pop_pending_event);
- ClassDB::bind_method(D_METHOD("finish_transaction"), &InAppStore::finish_transaction);
- ClassDB::bind_method(D_METHOD("set_auto_finish_transaction"), &InAppStore::set_auto_finish_transaction);
-};
+@property(nonatomic, strong) NSMutableArray *loadedProducts;
+@property(nonatomic, strong) NSMutableArray *pendingRequests;
-@interface ProductsDelegate : NSObject <SKProductsRequestDelegate> {
-};
+- (void)performRequestWithProductIDs:(NSSet *)productIDs;
+- (Error)purchaseProductWithProductID:(NSString *)productID;
+- (void)reset;
@end
-@implementation ProductsDelegate
+@implementation GodotProductsDelegate
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.loadedProducts = [NSMutableArray new];
+ self.pendingRequests = [NSMutableArray new];
+}
+
+- (void)performRequestWithProductIDs:(NSSet *)productIDs {
+ SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
+
+ request.delegate = self;
+ [self.pendingRequests addObject:request];
+ [request start];
+}
+
+- (Error)purchaseProductWithProductID:(NSString *)productID {
+ SKProduct *product = nil;
+
+ NSLog(@"searching for product!");
+
+ if (self.loadedProducts) {
+ for (SKProduct *storedProduct in self.loadedProducts) {
+ if ([storedProduct.productIdentifier isEqualToString:productID]) {
+ product = storedProduct;
+ break;
+ }
+ }
+ }
+
+ if (!product) {
+ return ERR_INVALID_PARAMETER;
+ }
+
+ NSLog(@"product found!");
+
+ SKPayment *payment = [SKPayment paymentWithProduct:product];
+ [[SKPaymentQueue defaultQueue] addPayment:payment];
+
+ NSLog(@"purchase sent!");
+
+ return OK;
+}
+
+- (void)reset {
+ [self.loadedProducts removeAllObjects];
+ [self.pendingRequests removeAllObjects];
+}
+
+- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
+ [self.pendingRequests removeObject:request];
+
+ Dictionary ret;
+ ret["type"] = "product_info";
+ ret["result"] = "error";
+ ret["error"] = String::utf8([error.localizedDescription UTF8String]);
+
+ InAppStore::get_singleton()->_post_event(ret);
+}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
+ [self.pendingRequests removeObject:request];
+
NSArray *products = response.products;
- latestProducts = products;
+ [self.loadedProducts addObjectsFromArray:products];
Dictionary ret;
ret["type"] = "product_info";
@@ -107,7 +167,8 @@ void InAppStore::_bind_methods() {
ids.push_back(String::utf8([product.productIdentifier UTF8String]));
localized_prices.push_back(String::utf8([product.localizedPrice UTF8String]));
currency_codes.push_back(String::utf8([[[product priceLocale] objectForKey:NSLocaleCurrencyCode] UTF8String]));
- };
+ }
+
ret["titles"] = titles;
ret["descriptions"] = descriptions;
ret["prices"] = prices;
@@ -119,50 +180,54 @@ void InAppStore::_bind_methods() {
for (NSString *ipid in response.invalidProductIdentifiers) {
invalid_ids.push_back(String::utf8([ipid UTF8String]));
- };
+ }
+
ret["invalid_ids"] = invalid_ids;
InAppStore::get_singleton()->_post_event(ret);
-};
+}
@end
-Error InAppStore::request_product_info(Dictionary p_params) {
- ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
+@interface GodotTransactionsObserver : NSObject <SKPaymentTransactionObserver>
- PackedStringArray pids = p_params["product_ids"];
- printf("************ request product info! %i\n", pids.size());
+@property(nonatomic, assign) BOOL shouldAutoFinishTransactions;
+@property(nonatomic, strong) NSMutableDictionary *pendingTransactions;
- NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()];
- for (int i = 0; i < pids.size(); i++) {
- printf("******** adding %s to product list\n", pids[i].utf8().get_data());
- NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()];
- [array addObject:pid];
- };
+- (void)finishTransactionWithProductID:(NSString *)productID;
+- (void)reset;
- NSSet *products = [[NSSet alloc] initWithArray:array];
- SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:products];
+@end
- ProductsDelegate *delegate = [[ProductsDelegate alloc] init];
+@implementation GodotTransactionsObserver
- request.delegate = delegate;
- [request start];
+- (instancetype)init {
+ self = [super init];
- return OK;
-};
+ if (self) {
+ [self godot_commonInit];
+ }
-Error InAppStore::restore_purchases() {
- printf("restoring purchases!\n");
- [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
+ return self;
+}
- return OK;
-};
+- (void)godot_commonInit {
+ self.pendingTransactions = [NSMutableDictionary new];
+}
-@interface TransObserver : NSObject <SKPaymentTransactionObserver> {
-};
-@end
+- (void)finishTransactionWithProductID:(NSString *)productID {
+ SKPaymentTransaction *transaction = self.pendingTransactions[productID];
+
+ if (transaction) {
+ [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
+ }
-@implementation TransObserver
+ self.pendingTransactions[productID] = nil;
+}
+
+- (void)reset {
+ [self.pendingTransactions removeAllObjects];
+}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
printf("transactions updated!\n");
@@ -190,6 +255,7 @@ Error InAppStore::restore_purchases() {
receipt = [NSData dataWithContentsOfURL:receiptFileURL];
NSString *receipt_to_send = nil;
+
if (receipt != nil) {
receipt_to_send = [receipt base64EncodedStringWithOptions:0];
}
@@ -200,13 +266,13 @@ Error InAppStore::restore_purchases() {
InAppStore::get_singleton()->_post_event(ret);
- if (auto_finish_transactions) {
+ if (self.shouldAutoFinishTransactions) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} else {
- [pending_transactions setObject:transaction forKey:transaction.payment.productIdentifier];
+ self.pendingTransactions[transaction.payment.productIdentifier] = transaction;
}
- }; break;
+ } break;
case SKPaymentTransactionStateFailed: {
printf("status transaction failed!\n");
String pid = String::utf8([transaction.payment.productIdentifier UTF8String]);
@@ -231,95 +297,119 @@ Error InAppStore::restore_purchases() {
} break;
default: {
printf("status default %i!\n", (int)transaction.transactionState);
- }; break;
- };
- };
-};
+ } break;
+ }
+ }
+}
@end
-Error InAppStore::purchase(Dictionary p_params) {
- ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
- if (![SKPaymentQueue canMakePayments])
- return ERR_UNAVAILABLE;
+void InAppStore::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("request_product_info"), &InAppStore::request_product_info);
+ ClassDB::bind_method(D_METHOD("restore_purchases"), &InAppStore::restore_purchases);
+ ClassDB::bind_method(D_METHOD("purchase"), &InAppStore::purchase);
- printf("purchasing!\n");
- ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER);
+ ClassDB::bind_method(D_METHOD("get_pending_event_count"), &InAppStore::get_pending_event_count);
+ ClassDB::bind_method(D_METHOD("pop_pending_event"), &InAppStore::pop_pending_event);
+ ClassDB::bind_method(D_METHOD("finish_transaction"), &InAppStore::finish_transaction);
+ ClassDB::bind_method(D_METHOD("set_auto_finish_transaction"), &InAppStore::set_auto_finish_transaction);
+}
- NSString *pid = [[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()];
+Error InAppStore::request_product_info(Dictionary p_params) {
+ ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
- SKProduct *product = nil;
+ PackedStringArray pids = p_params["product_ids"];
+ printf("************ request product info! %i\n", pids.size());
- if (latestProducts) {
- for (SKProduct *storedProduct in latestProducts) {
- if ([storedProduct.productIdentifier isEqualToString:pid]) {
- product = storedProduct;
- break;
- }
- }
- }
+ NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()];
+ for (int i = 0; i < pids.size(); i++) {
+ printf("******** adding %s to product list\n", pids[i].utf8().get_data());
+ NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()];
+ [array addObject:pid];
+ };
- if (!product) {
- return ERR_INVALID_PARAMETER;
- }
+ NSSet *products = [[NSSet alloc] initWithArray:array];
- SKPayment *payment = [SKPayment paymentWithProduct:product];
- SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
- [defq addPayment:payment];
- printf("purchase sent!\n");
+ [products_request_delegate performRequestWithProductIDs:products];
return OK;
-};
+}
+
+Error InAppStore::restore_purchases() {
+ printf("restoring purchases!\n");
+ [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
+
+ return OK;
+}
+
+Error InAppStore::purchase(Dictionary p_params) {
+ ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
+ if (![SKPaymentQueue canMakePayments]) {
+ return ERR_UNAVAILABLE;
+ }
+
+ printf("purchasing!\n");
+ Dictionary params = p_params;
+ ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
+
+ NSString *pid = [[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()];
+
+ return [products_request_delegate purchaseProductWithProductID:pid];
+}
int InAppStore::get_pending_event_count() {
return pending_events.size();
-};
+}
Variant InAppStore::pop_pending_event() {
Variant front = pending_events.front()->get();
pending_events.pop_front();
return front;
-};
+}
void InAppStore::_post_event(Variant p_event) {
pending_events.push_back(p_event);
-};
+}
void InAppStore::_record_purchase(String product_id) {
String skey = "purchased/" + product_id;
NSString *key = [[NSString alloc] initWithUTF8String:skey.utf8().get_data()];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
-};
+}
InAppStore *InAppStore::get_singleton() {
return instance;
-};
+}
InAppStore::InAppStore() {
ERR_FAIL_COND(instance != NULL);
instance = this;
- auto_finish_transactions = false;
- TransObserver *observer = [[TransObserver alloc] init];
- [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
- //pending_transactions = [NSMutableDictionary dictionary];
-};
+ products_request_delegate = [[GodotProductsDelegate alloc] init];
+ transactions_observer = [[GodotTransactionsObserver alloc] init];
+
+ [[SKPaymentQueue defaultQueue] addTransactionObserver:transactions_observer];
+}
void InAppStore::finish_transaction(String product_id) {
NSString *prod_id = [NSString stringWithCString:product_id.utf8().get_data() encoding:NSUTF8StringEncoding];
- if ([pending_transactions objectForKey:prod_id]) {
- [[SKPaymentQueue defaultQueue] finishTransaction:[pending_transactions objectForKey:prod_id]];
- [pending_transactions removeObjectForKey:prod_id];
- }
-};
+ [transactions_observer finishTransactionWithProductID:prod_id];
+}
void InAppStore::set_auto_finish_transaction(bool b) {
- auto_finish_transactions = b;
+ transactions_observer.shouldAutoFinishTransactions = b;
}
-InAppStore::~InAppStore() {}
+InAppStore::~InAppStore() {
+ [products_request_delegate reset];
+ [transactions_observer reset];
+
+ products_request_delegate = nil;
+ [[SKPaymentQueue defaultQueue] removeTransactionObserver:transactions_observer];
+ transactions_observer = nil;
+}
#endif