Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 04b5870

Browse files
committed
[in_app_purchase] Fixe finishing purchases upon payment dialog cancellation
1 parent 18771ff commit 04b5870

File tree

7 files changed

+46
-22
lines changed

7 files changed

+46
-22
lines changed

packages/in_app_purchase/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11

2+
## 0.3.4+11
3+
4+
* [iOS] Fixed: finishing purchases upon payment dialog cancellation.
5+
26
## 0.3.4+10
37

48
* Fixed typo 'verity' for 'verify'.

packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,19 +197,31 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result {
197197
}
198198

199199
- (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result {
200-
if (![call.arguments isKindOfClass:[NSString class]]) {
200+
if (![call.arguments isKindOfClass:[NSDictionary class]]) {
201201
result([FlutterError errorWithCode:@"storekit_invalid_argument"
202-
message:@"Argument type of finishTransaction is not a string."
202+
message:@"Argument type of finishTransaction is not a Dictionary"
203203
details:call.arguments]);
204204
return;
205205
}
206-
NSString *transactionIdentifier = call.arguments;
206+
NSDictionary *paymentMap = (NSDictionary *)call.arguments;
207+
NSString *transactionIdentifier = [paymentMap objectForKey:@"transactionIdentifier"];
208+
NSString *productIdentifier = [paymentMap objectForKey:@"productIdentifier"];
207209

208210
NSArray<SKPaymentTransaction *> *pendingTransactions =
209211
[self.paymentQueueHandler getUnfinishedTransactions];
210212

211213
for (SKPaymentTransaction *transaction in pendingTransactions) {
212-
if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier]) {
214+
// If the user cancels the purchase dialog we won't have a transactionIdentifier. So if it is null
215+
// AND a transaction in the pendingTransactions list has also a null transactionIdentifier
216+
// we check for equal product identifiers.
217+
if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier] ||
218+
(
219+
[transactionIdentifier isEqual:[NSNull null]] &&
220+
transaction.transactionIdentifier == nil &&
221+
[transaction.payment.productIdentifier isEqualToString:productIdentifier]
222+
)
223+
)
224+
{
213225
@try {
214226
[self.paymentQueueHandler finishTransaction:transaction];
215227
} @catch (NSException *e) {

packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,11 @@ class SKPaymentQueueWrapper {
103103
/// finishTransaction:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction?language=objc).
104104
Future<void> finishTransaction(
105105
SKPaymentTransactionWrapper transaction) async {
106+
Map requestMap = transaction.toFinishMap();
106107
await channel.invokeMethod<void>(
107-
'-[InAppPurchasePlugin finishTransaction:result:]',
108-
transaction.transactionIdentifier);
108+
'-[InAppPurchasePlugin finishTransaction:result:]',
109+
requestMap,
110+
);
109111
}
110112

111113
/// Restore previously purchased transactions.

packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,10 @@ class SKPaymentTransactionWrapper {
185185

186186
@override
187187
String toString() => _$SKPaymentTransactionWrapperToJson(this).toString();
188+
189+
/// The payload that is used to finish this transaction.
190+
Map<String, String> toFinishMap() => {
191+
"transactionIdentifier": this.transactionIdentifier,
192+
"productIdentifier": this.payment?.productIdentifier,
193+
};
188194
}

packages/in_app_purchase/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: in_app_purchase
22
description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
33
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
4-
version: 0.3.4+10
4+
version: 0.3.4+11
55

66
dependencies:
77
async: ^2.0.8

packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ void main() {
9292

9393
test('queryPastPurchases should not block transaction updates', () async {
9494
fakeIOSPlatform.transactions
95-
.add(fakeIOSPlatform.createPurchasedTransactionWithProductID('foo'));
95+
.add(fakeIOSPlatform.createPurchasedTransaction('foo', 'bar'));
9696
Completer completer = Completer();
9797
Stream<List<PurchaseDetails>> stream =
9898
AppStoreConnection.instance.purchaseUpdatedStream;
@@ -348,7 +348,7 @@ class FakeIOSPlatform {
348348
testRestoredError = null;
349349
}
350350

351-
SKPaymentTransactionWrapper createPendingTransactionWithProductID(String id) {
351+
SKPaymentTransactionWrapper createPendingTransaction(String id) {
352352
return SKPaymentTransactionWrapper(
353353
transactionIdentifier: null,
354354
payment: SKPaymentWrapper(productIdentifier: id),
@@ -358,21 +358,21 @@ class FakeIOSPlatform {
358358
originalTransaction: null);
359359
}
360360

361-
SKPaymentTransactionWrapper createPurchasedTransactionWithProductID(
362-
String id) {
361+
SKPaymentTransactionWrapper createPurchasedTransaction(
362+
String productId, String transactionId) {
363363
return SKPaymentTransactionWrapper(
364-
payment: SKPaymentWrapper(productIdentifier: id),
364+
payment: SKPaymentWrapper(productIdentifier: productId),
365365
transactionState: SKPaymentTransactionStateWrapper.purchased,
366366
transactionTimeStamp: 123123.121,
367-
transactionIdentifier: id,
367+
transactionIdentifier: transactionId,
368368
error: null,
369369
originalTransaction: null);
370370
}
371371

372-
SKPaymentTransactionWrapper createFailedTransactionWithProductID(String id) {
372+
SKPaymentTransactionWrapper createFailedTransaction(String productId) {
373373
return SKPaymentTransactionWrapper(
374374
transactionIdentifier: null,
375-
payment: SKPaymentWrapper(productIdentifier: id),
375+
payment: SKPaymentWrapper(productIdentifier: productId),
376376
transactionState: SKPaymentTransactionStateWrapper.failed,
377377
transactionTimeStamp: 123123.121,
378378
error: SKError(
@@ -435,25 +435,25 @@ class FakeIOSPlatform {
435435
case '-[InAppPurchasePlugin addPayment:result:]':
436436
String id = call.arguments['productIdentifier'];
437437
SKPaymentTransactionWrapper transaction =
438-
createPendingTransactionWithProductID(id);
438+
createPendingTransaction(id);
439439
AppStoreConnection.observer
440440
.updatedTransactions(transactions: [transaction]);
441441
sleep(const Duration(milliseconds: 30));
442442
if (testTransactionFail) {
443443
SKPaymentTransactionWrapper transaction_failed =
444-
createFailedTransactionWithProductID(id);
444+
createFailedTransaction(id);
445445
AppStoreConnection.observer
446446
.updatedTransactions(transactions: [transaction_failed]);
447447
} else {
448448
SKPaymentTransactionWrapper transaction_finished =
449-
createPurchasedTransactionWithProductID(id);
449+
createPurchasedTransaction(id, transaction.transactionIdentifier);
450450
AppStoreConnection.observer
451451
.updatedTransactions(transactions: [transaction_finished]);
452452
}
453453
break;
454454
case '-[InAppPurchasePlugin finishTransaction:result:]':
455455
finishedTransactions
456-
.add(createPurchasedTransactionWithProductID(call.arguments));
456+
.add(createPurchasedTransaction(call.arguments["productIdentifier"], call.arguments["transactionIdentifier"]));
457457
break;
458458
}
459459
return Future<void>.sync(() {});

packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ void main() {
110110
queue.setTransactionObserver(observer);
111111
await queue.finishTransaction(dummyTransaction);
112112
expect(fakeIOSPlatform.transactionsFinished.first,
113-
equals(dummyTransaction.transactionIdentifier));
113+
equals(dummyTransaction.toFinishMap()));
114114
});
115115

116116
test('should restore transaction', () async {
@@ -139,7 +139,7 @@ class FakeIOSPlatform {
139139

140140
// payment queue
141141
List<SKPaymentWrapper> payments = [];
142-
List<String> transactionsFinished = [];
142+
List<Map<String,String>> transactionsFinished = [];
143143
String applicationNameHasTransactionRestored;
144144

145145
Future<dynamic> onMethodCall(MethodCall call) {
@@ -171,7 +171,7 @@ class FakeIOSPlatform {
171171
payments.add(SKPaymentWrapper.fromJson(call.arguments));
172172
return Future<void>.sync(() {});
173173
case '-[InAppPurchasePlugin finishTransaction:result:]':
174-
transactionsFinished.add(call.arguments);
174+
transactionsFinished.add(Map<String,String>.from(call.arguments));
175175
return Future<void>.sync(() {});
176176
case '-[InAppPurchasePlugin restoreTransactions:result:]':
177177
applicationNameHasTransactionRestored = call.arguments;

0 commit comments

Comments
 (0)