diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index d62aa3605ba..f952dbc0617 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.4.2 + +* Add [jwsRepresentation](https://developer.apple.com/documentation/storekit/verificationresult/jwsrepresentation-21vgo) to `SK2PurchaseDetails` as `serverVerificationData` for secure server-side purchase verification. Use this JSON Web Signature (JWS) value to perform your own JWS verification on your server. +* Add [jsonRepresentation](https://developer.apple.com/documentation/storekit/transaction/jsonrepresentation) to `SK2PurchaseDetails` as `localVerificationData` for local transaction debugging and verification. + ## 0.4.1 * Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift index 395148d0660..bb9bfff7c6e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift @@ -88,7 +88,8 @@ extension InAppPurchasePlugin: InAppPurchase2API { case .success(let verification): switch verification { case .verified(let transaction): - self.sendTransactionUpdate(transaction: transaction) + self.sendTransactionUpdate( + transaction: transaction, receipt: verification.jwsRepresentation) completion(.success(result.convertToPigeon())) case .unverified(_, let error): completion(.failure(error)) @@ -284,7 +285,8 @@ extension InAppPurchasePlugin: InAppPurchase2API { for await verificationResult in Transaction.updates { switch verificationResult { case .verified(let transaction): - self?.sendTransactionUpdate(transaction: transaction) + self?.sendTransactionUpdate( + transaction: transaction, receipt: verificationResult.jwsRepresentation) case .unverified: break } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart index 5fb6cee8969..fe44700815e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart @@ -121,7 +121,10 @@ extension on SK2TransactionMessage { // receipt isn’t necessary with SK2 as a Transaction can only be returned // from validated purchases. verificationData: PurchaseVerificationData( - localVerificationData: '', serverVerificationData: '', source: ''), + localVerificationData: jsonRepresentation ?? '', + // receiptData is the JWS representation of the transaction + serverVerificationData: receiptData ?? '', + source: kIAPSource), transactionDate: purchaseDate, // Note that with SK2, any transactions that *can* be returned will // require to be finished, and are already purchased. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index 23acddf5aad..b353b6fe3dc 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.4.1 +version: 0.4.2 environment: sdk: ^3.6.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index 450e6a86a2e..d82a41c4e91 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -442,5 +442,7 @@ SK2TransactionMessage createPendingTransaction(String id, {int quantity = 1}) { originalId: 2, productId: id, purchaseDate: 'purchaseDate', - appAccountToken: 'appAccountToken'); + appAccountToken: 'appAccountToken', + receiptData: 'receiptData', + jsonRepresentation: 'jsonRepresentation'); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart index ba072db74f2..20da75f241b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart @@ -154,6 +154,38 @@ void main() { throwsA(isInstanceOf())); }); + test( + 'buying consumable, should get PurchaseVerificationData with serverVerificationData and localVerificationData', + () async { + final List details = []; + final Completer> completer = + Completer>(); + final Stream> stream = + iapStoreKitPlatform.purchaseStream; + + late final StreamSubscription> subscription; + subscription = stream.listen((List purchaseDetailsList) { + details.addAll(purchaseDetailsList); + if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { + completer.complete(details); + subscription.cancel(); + } + }); + final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( + productDetails: + AppStoreProduct2Details.fromSK2Product(dummyProductWrapper), + applicationUserName: 'appName'); + await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam); + + final List result = await completer.future; + expect(result.length, 1); + expect(result.first.productID, dummyProductWrapper.id); + expect( + result.first.verificationData.serverVerificationData, 'receiptData'); + expect(result.first.verificationData.localVerificationData, + 'jsonRepresentation'); + }); + test('should process Sk2PurchaseParam with winBackOfferId only', () async { final Sk2PurchaseParam purchaseParam = Sk2PurchaseParam( productDetails: