Skip to content

Commit 339a616

Browse files
shaunpanjabiOrtes
authored andcommitted
[in_app_purchase] Return jwsRepresentation and jsonRepresentation for StoreKit2 (flutter#9280)
Fix for flutter/flutter#164433 Providing access to `jwsRepresentation`. property of the transaction. With the most recent update `PurchaseVerificationData` returns empty now. I think it could make sense to repurpose it and return the: - `jsonRepresentation` for `localVerificationData` - `jwsRepresentation` for `serverVerificationData` ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent b9d6821 commit 339a616

File tree

6 files changed

+49
-5
lines changed

6 files changed

+49
-5
lines changed

packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.4.2
2+
3+
* 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.
4+
* Add [jsonRepresentation](https://developer.apple.com/documentation/storekit/transaction/jsonrepresentation) to `SK2PurchaseDetails` as `localVerificationData` for local transaction debugging and verification.
5+
16
## 0.4.1
27

38
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.

packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ extension InAppPurchasePlugin: InAppPurchase2API {
8888
case .success(let verification):
8989
switch verification {
9090
case .verified(let transaction):
91-
self.sendTransactionUpdate(transaction: transaction)
91+
self.sendTransactionUpdate(
92+
transaction: transaction, receipt: verification.jwsRepresentation)
9293
completion(.success(result.convertToPigeon()))
9394
case .unverified(_, let error):
9495
completion(.failure(error))
@@ -284,7 +285,8 @@ extension InAppPurchasePlugin: InAppPurchase2API {
284285
for await verificationResult in Transaction.updates {
285286
switch verificationResult {
286287
case .verified(let transaction):
287-
self?.sendTransactionUpdate(transaction: transaction)
288+
self?.sendTransactionUpdate(
289+
transaction: transaction, receipt: verificationResult.jwsRepresentation)
288290
case .unverified:
289291
break
290292
}

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,10 @@ extension on SK2TransactionMessage {
121121
// receipt isn’t necessary with SK2 as a Transaction can only be returned
122122
// from validated purchases.
123123
verificationData: PurchaseVerificationData(
124-
localVerificationData: '', serverVerificationData: '', source: ''),
124+
localVerificationData: jsonRepresentation ?? '',
125+
// receiptData is the JWS representation of the transaction
126+
serverVerificationData: receiptData ?? '',
127+
source: kIAPSource),
125128
transactionDate: purchaseDate,
126129
// Note that with SK2, any transactions that *can* be returned will
127130
// require to be finished, and are already purchased.

packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: in_app_purchase_storekit
22
description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework.
33
repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
5-
version: 0.4.1
5+
version: 0.4.2
66

77
environment:
88
sdk: ^3.6.0

packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,5 +442,7 @@ SK2TransactionMessage createPendingTransaction(String id, {int quantity = 1}) {
442442
originalId: 2,
443443
productId: id,
444444
purchaseDate: 'purchaseDate',
445-
appAccountToken: 'appAccountToken');
445+
appAccountToken: 'appAccountToken',
446+
receiptData: 'receiptData',
447+
jsonRepresentation: 'jsonRepresentation');
446448
}

packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,38 @@ void main() {
154154
throwsA(isInstanceOf<AssertionError>()));
155155
});
156156

157+
test(
158+
'buying consumable, should get PurchaseVerificationData with serverVerificationData and localVerificationData',
159+
() async {
160+
final List<PurchaseDetails> details = <PurchaseDetails>[];
161+
final Completer<List<PurchaseDetails>> completer =
162+
Completer<List<PurchaseDetails>>();
163+
final Stream<List<PurchaseDetails>> stream =
164+
iapStoreKitPlatform.purchaseStream;
165+
166+
late final StreamSubscription<List<PurchaseDetails>> subscription;
167+
subscription = stream.listen((List<PurchaseDetails> purchaseDetailsList) {
168+
details.addAll(purchaseDetailsList);
169+
if (purchaseDetailsList.first.status == PurchaseStatus.purchased) {
170+
completer.complete(details);
171+
subscription.cancel();
172+
}
173+
});
174+
final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam(
175+
productDetails:
176+
AppStoreProduct2Details.fromSK2Product(dummyProductWrapper),
177+
applicationUserName: 'appName');
178+
await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam);
179+
180+
final List<PurchaseDetails> result = await completer.future;
181+
expect(result.length, 1);
182+
expect(result.first.productID, dummyProductWrapper.id);
183+
expect(
184+
result.first.verificationData.serverVerificationData, 'receiptData');
185+
expect(result.first.verificationData.localVerificationData,
186+
'jsonRepresentation');
187+
});
188+
157189
test('should process Sk2PurchaseParam with winBackOfferId only', () async {
158190
final Sk2PurchaseParam purchaseParam = Sk2PurchaseParam(
159191
productDetails:

0 commit comments

Comments
 (0)