| @@ -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); | |||
| } | |||
| void verifyReceipt (const String& endpoint, NSData* requestData) | |||
| { | |||
| const auto nsurl = [NSURL URLWithString: juceStringToNS (endpoint)]; | |||
| // TODO: use juce URL here | |||
| auto* urlPtr = [NSURL URLWithString: nsStringLiteral (storeURL)]; | |||
| auto storeRequest = [NSMutableURLRequest requestWithURL: urlPtr]; | |||
| [storeRequest setHTTPMethod: nsStringLiteral ("POST")]; | |||
| 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"; | |||
| const auto shouldRetryWithSandboxUrl = [isProduction = (endpoint != sandboxURL)] (NSDictionary* receiptDetails) | |||
| { | |||
| if (isProduction) | |||
| if (const auto* status = getAs<NSNumber> (receiptDetails[@"status"])) | |||
| return [status intValue] == 21007; | |||
| 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; | |||
| } | |||
| if (NSDictionary* receiptDetails = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &err]) | |||
| processReceiptDetails (receiptDetails); | |||
| else | |||
| sendReceiptFetchFail(); | |||
| } | |||
| }]; | |||
| processReceiptDetails (receiptDetails); | |||
| return; | |||
| } | |||
| } | |||
| [task resume]; | |||
| sendReceiptFetchFail(); | |||
| }] resume]; | |||
| } | |||
| void processReceiptDetails (NSDictionary* receiptDetails) | |||
| { | |||
| if (auto receipt = getAs<NSDictionary> (receiptDetails[nsStringLiteral ("receipt")])) | |||
| if (auto receipt = getAs<NSDictionary> (receiptDetails[@"receipt"])) | |||
| { | |||
| if (auto bundleId = getAs<NSString> (receipt[nsStringLiteral ("bundle_id")])) | |||
| if (auto bundleId = getAs<NSString> (receipt[@"bundle_id"])) | |||
| { | |||
| if (auto inAppPurchases = getAs<NSArray> (receipt[nsStringLiteral ("in_app")])) | |||
| if (auto inAppPurchases = getAs<NSArray> (receipt[@"in_app"])) | |||
| { | |||
| Array<Listener::PurchaseInfo> purchases; | |||
| @@ -608,14 +623,14 @@ struct InAppPurchases::Pimpl : public SKDelegateAndPaymentObserver | |||
| if (auto* purchaseData = getAs<NSDictionary> (inAppPurchaseData)) | |||
| { | |||
| // Ignore products that were cancelled. | |||
| if (purchaseData[nsStringLiteral ("cancellation_date")] != nil) | |||
| if (purchaseData[@"cancellation_date"] != nil) | |||
| continue; | |||
| if (auto transactionId = getAs<NSString> (purchaseData[nsStringLiteral ("original_transaction_id")])) | |||
| if (auto transactionId = getAs<NSString> (purchaseData[@"original_transaction_id"])) | |||
| { | |||
| if (auto productId = getAs<NSString> (purchaseData[nsStringLiteral ("product_id")])) | |||
| if (auto productId = getAs<NSString> (purchaseData[@"product_id"])) | |||
| { | |||
| auto purchaseTime = getPurchaseDateMs (purchaseData[nsStringLiteral ("purchase_date_ms")]); | |||
| auto purchaseTime = getPurchaseDateMs (purchaseData[@"purchase_date_ms"]); | |||
| if (purchaseTime > 0) | |||
| { | |||