From 2073e46e79c5edcb8291696d499ae99845f3075a Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 14 Oct 2021 13:55:38 +0100 Subject: [PATCH] iOS InAppPurchases: Use recommended receipt verification procedure --- .../native/juce_ios_InAppPurchases.cpp | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/modules/juce_product_unlocking/native/juce_ios_InAppPurchases.cpp b/modules/juce_product_unlocking/native/juce_ios_InAppPurchases.cpp index 0a72e59777..a5270dc1f6 100644 --- a/modules/juce_product_unlocking/native/juce_ios_InAppPurchases.cpp +++ b/modules/juce_product_unlocking/native/juce_ios_InAppPurchases.cpp @@ -544,10 +544,10 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver void fetchReceiptDetailsFromAppStore (NSData* receiptData, const String& secret) { auto requestContents = [NSMutableDictionary dictionaryWithCapacity: (NSUInteger) (secret.isNotEmpty() ? 2 : 1)]; - [requestContents setObject: [receiptData base64EncodedStringWithOptions:0] forKey: nsStringLiteral ("receipt-data")]; + [requestContents setObject: [receiptData base64EncodedStringWithOptions:0] forKey: @"receipt-data"]; if (secret.isNotEmpty()) - [requestContents setObject: juceStringToNS (secret) forKey: nsStringLiteral ("password")]; + [requestContents setObject: juceStringToNS (secret) forKey: @"password"]; NSError* error; auto requestData = [NSJSONSerialization dataWithJSONObject: requestContents @@ -559,47 +559,62 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver return; } - #if JUCE_IN_APP_PURCHASES_USE_SANDBOX_ENVIRONMENT - auto storeURL = "https://sandbox.itunes.apple.com/verifyReceipt"; - #else - auto storeURL = "https://buy.itunes.apple.com/verifyReceipt"; - #endif + verifyReceipt ("https://buy.itunes.apple.com/verifyReceipt", requestData); + } - // TODO: use juce URL here - auto* urlPtr = [NSURL URLWithString: nsStringLiteral (storeURL)]; - auto storeRequest = [NSMutableURLRequest requestWithURL: urlPtr]; - [storeRequest setHTTPMethod: nsStringLiteral ("POST")]; + void verifyReceipt (const String& endpoint, NSData* requestData) + { + const auto nsurl = [NSURL URLWithString: juceStringToNS (endpoint)]; + + if (nsurl == nullptr) + return; + + const auto storeRequest = [NSMutableURLRequest requestWithURL: nsurl]; + [storeRequest setHTTPMethod: @"POST"]; [storeRequest setHTTPBody: requestData]; - auto task = [[NSURLSession sharedSession] dataTaskWithRequest: storeRequest - completionHandler: - ^(NSData* data, NSURLResponse*, NSError* connectionError) - { - if (connectionError != nil) - { - sendReceiptFetchFail(); - } - else - { - NSError* err; + constexpr auto sandboxURL = "https://sandbox.itunes.apple.com/verifyReceipt"; - if (NSDictionary* receiptDetails = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &err]) - processReceiptDetails (receiptDetails); - else - sendReceiptFetchFail(); - } - }]; + const auto shouldRetryWithSandboxUrl = [isProduction = (endpoint != sandboxURL)] (NSDictionary* receiptDetails) + { + if (isProduction) + if (const auto* status = getAs (receiptDetails[@"status"])) + return [status intValue] == 21007; - [task resume]; + return false; + }; + + [[[NSURLSession sharedSession] dataTaskWithRequest: storeRequest + completionHandler: ^(NSData* responseData, NSURLResponse*, NSError* connectionError) + { + if (connectionError == nullptr) + { + NSError* err = nullptr; + + if (NSDictionary* receiptDetails = [NSJSONSerialization JSONObjectWithData: responseData options: 0 error: &err]) + { + if (shouldRetryWithSandboxUrl (receiptDetails)) + { + verifyReceipt (sandboxURL, requestData); + return; + } + + processReceiptDetails (receiptDetails); + return; + } + } + + sendReceiptFetchFail(); + }] resume]; } void processReceiptDetails (NSDictionary* receiptDetails) { - if (auto receipt = getAs (receiptDetails[nsStringLiteral ("receipt")])) + if (auto receipt = getAs (receiptDetails[@"receipt"])) { - if (auto bundleId = getAs (receipt[nsStringLiteral ("bundle_id")])) + if (auto bundleId = getAs (receipt[@"bundle_id"])) { - if (auto inAppPurchases = getAs (receipt[nsStringLiteral ("in_app")])) + if (auto inAppPurchases = getAs (receipt[@"in_app"])) { Array purchases; @@ -608,14 +623,14 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver if (auto* purchaseData = getAs (inAppPurchaseData)) { // Ignore products that were cancelled. - if (purchaseData[nsStringLiteral ("cancellation_date")] != nil) + if (purchaseData[@"cancellation_date"] != nil) continue; - if (auto transactionId = getAs (purchaseData[nsStringLiteral ("original_transaction_id")])) + if (auto transactionId = getAs (purchaseData[@"original_transaction_id"])) { - if (auto productId = getAs (purchaseData[nsStringLiteral ("product_id")])) + if (auto productId = getAs (purchaseData[@"product_id"])) { - auto purchaseTime = getPurchaseDateMs (purchaseData[nsStringLiteral ("purchase_date_ms")]); + auto purchaseTime = getPurchaseDateMs (purchaseData[@"purchase_date_ms"]); if (purchaseTime > 0) {