diff --git a/InAppUtils/InAppUtils.m b/InAppUtils/InAppUtils.m index 3b545cb..135a90b 100644 --- a/InAppUtils/InAppUtils.m +++ b/InAppUtils/InAppUtils.m @@ -57,7 +57,6 @@ - (void)paymentQueue:(SKPaymentQueue *)queue } else { RCTLogWarn(@"No callback registered for transaction with state purchased."); } - [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; break; } case SKPaymentTransactionStateRestored: @@ -75,6 +74,37 @@ - (void)paymentQueue:(SKPaymentQueue *)queue } } +RCT_EXPORT_METHOD(getPendingPurchases:(RCTResponseSenderBlock)callback) +{ + NSMutableArray *transactionsArrayForJS = [NSMutableArray array]; + for (SKPaymentTransaction *transaction in [SKPaymentQueue defaultQueue].transactions) { + + NSMutableDictionary *purchase = [NSMutableDictionary new]; + purchase[@"transactionDate"] = @(transaction.transactionDate.timeIntervalSince1970 * 1000); + purchase[@"productIdentifier"] = transaction.payment.productIdentifier; + purchase[@"transactionState"] = StringForTransactionState(transaction.transactionState); + + if (transaction.transactionIdentifier != nil) { + purchase[@"transactionIdentifier"] = transaction.transactionIdentifier; + } + + NSString *receipt = [[transaction transactionReceipt] base64EncodedStringWithOptions:0]; + + if (receipt != nil) { + purchase[@"transactionReceipt"] = receipt; + } + + SKPaymentTransaction *originalTransaction = transaction.originalTransaction; + if (originalTransaction) { + purchase[@"originalTransactionDate"] = @(originalTransaction.transactionDate.timeIntervalSince1970 * 1000); + purchase[@"originalTransactionIdentifier"] = originalTransaction.transactionIdentifier; + } + + [transactionsArrayForJS addObject:purchase]; + } + callback(@[[NSNull null], transactionsArrayForJS]); +} + RCT_EXPORT_METHOD(purchaseProductForUser:(NSString *)productIdentifier username:(NSString *)username callback:(RCTResponseSenderBlock)callback) @@ -113,6 +143,24 @@ - (void) doPurchaseProduct:(NSString *)productIdentifier } } +RCT_EXPORT_METHOD(finishPurchase:(NSString *)transactionIdentifier + callback:(RCTResponseSenderBlock)callback) +{ + for (SKPaymentTransaction *transaction in [SKPaymentQueue defaultQueue].transactions) { + if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier]) { + if (transaction.transactionState == SKPaymentTransactionStatePurchased) { + [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; + callback(@[[NSNull null]]); + } else { + callback(@[@"invalid_purchase"]); + } + return; + } + } + callback(@[@"invalid_purchase"]); +} + + - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { @@ -282,5 +330,18 @@ - (void)dealloc { return [NSString stringWithFormat:@"%p", instance]; } + +static NSString *StringForTransactionState(SKPaymentTransactionState state) +{ + switch(state) { + case SKPaymentTransactionStatePurchasing: return @"purchasing"; + case SKPaymentTransactionStatePurchased: return @"purchased"; + case SKPaymentTransactionStateFailed: return @"failed"; + case SKPaymentTransactionStateRestored: return @"restored"; + case SKPaymentTransactionStateDeferred: return @"deferred"; + } + + [NSException raise:NSGenericException format:@"Unexpected SKPaymentTransactionState."]; +} @end diff --git a/Readme.md b/Readme.md index 79ba175..d597767 100644 --- a/Readme.md +++ b/Readme.md @@ -79,7 +79,15 @@ InAppUtils.purchaseProduct(productIdentifier, (error, response) => { // NOTE for v3.0: User can cancel the payment which will be available as error object here. if(response && response.productIdentifier) { Alert.alert('Purchase Successful', 'Your Transaction ID is ' + response.transactionIdentifier); + //unlock store here. + + // After providing the item, we call finishPurchase to finalize the transaction. + InAppUtils.finishPurchase(response.transactionIdentifier, (error) => { + if (!error) { + // Transaction Complete + } + }); } }); ``` @@ -104,6 +112,40 @@ https://stackoverflow.com/questions/29255568/is-there-any-way-to-know-purchase-m **NOTE:** `originalTransactionDate` and `originalTransactionIdentifier` are only available for subscriptions that were previously cancelled or expired. +## Check for any pending transactions + +Sometimes transactions aren't instantaneous, and can update while the user is not in the app. It is a good idea to check for any pending purchases on app start: + +```javascript + +// During app startup: + +InAppUtils.getPendingPurchases((error, response) => { + + if(error) { + Alert.alert('itunes Error', 'Could not connect to itunes store.'); + } else { + + response.forEach((purchase) => { + if (purchase.transactionState === 'purchased') { + + // Logic here to deliver purchased product... + + // Call finishPurchase to finalize the transaction. + InAppUtils.finishPurchase(purchase.transactionIdentifier, error => { + if (!error) { + // Transaction Complete + } + }); + + } + }); + + } + +} +``` + ### Restore payments ```javascript @@ -112,7 +154,7 @@ InAppUtils.restorePurchases((error, response) => { Alert.alert('itunes Error', 'Could not connect to itunes store.'); } else { Alert.alert('Restore Successful', 'Successfully restores all your purchases.'); - + if (response.length === 0) { Alert.alert('No Purchases', "We didn't find any purchases to restore."); return;