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

Commit

Permalink
[in_app_purchase] Fix finishing purchases upon payment cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
dennis-tra committed Oct 5, 2020
1 parent 3a5d9ed commit 1d25eee
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 24 deletions.
3 changes: 3 additions & 0 deletions packages/in_app_purchase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.3.4+12

* [iOS] Fixed: finishing purchases upon payment dialog cancellation.

## 0.3.4+11

Expand Down
16 changes: 12 additions & 4 deletions packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -199,19 +199,27 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result {
}

- (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result {
if (![call.arguments isKindOfClass:[NSString class]]) {
if (![call.arguments isKindOfClass:[NSDictionary class]]) {
result([FlutterError errorWithCode:@"storekit_invalid_argument"
message:@"Argument type of finishTransaction is not a string."
message:@"Argument type of finishTransaction is not a Dictionary"
details:call.arguments]);
return;
}
NSString *transactionIdentifier = call.arguments;
NSDictionary *paymentMap = (NSDictionary *)call.arguments;
NSString *transactionIdentifier = [paymentMap objectForKey:@"transactionIdentifier"];
NSString *productIdentifier = [paymentMap objectForKey:@"productIdentifier"];

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

for (SKPaymentTransaction *transaction in pendingTransactions) {
if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier]) {
// If the user cancels the purchase dialog we won't have a transactionIdentifier.
// So if it is null AND a transaction in the pendingTransactions list has
// also a null transactionIdentifier we check for equal product identifiers.
if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier] ||
([transactionIdentifier isEqual:[NSNull null]] &&
transaction.transactionIdentifier == nil &&
[transaction.payment.productIdentifier isEqualToString:productIdentifier])) {
@try {
[self.paymentQueueHandler finishTransaction:transaction];
} @catch (NSException *e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,11 @@ class SKPaymentQueueWrapper {
/// finishTransaction:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction?language=objc).
Future<void> finishTransaction(
SKPaymentTransactionWrapper transaction) async {
Map<String, String> requestMap = transaction.toFinishMap();
await channel.invokeMethod<void>(
'-[InAppPurchasePlugin finishTransaction:result:]',
transaction.transactionIdentifier);
'-[InAppPurchasePlugin finishTransaction:result:]',
requestMap,
);
}

/// Restore previously purchased transactions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,10 @@ class SKPaymentTransactionWrapper {

@override
String toString() => _$SKPaymentTransactionWrapperToJson(this).toString();

/// The payload that is used to finish this transaction.
Map<String, String> toFinishMap() => {
"transactionIdentifier": this.transactionIdentifier,
"productIdentifier": this.payment?.productIdentifier,
};
}
2 changes: 1 addition & 1 deletion packages/in_app_purchase/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: in_app_purchase
description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
version: 0.3.4+11
version: 0.3.4+12

dependencies:
async: ^2.0.8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ void main() {

test('queryPastPurchases should not block transaction updates', () async {
fakeIOSPlatform.transactions
.add(fakeIOSPlatform.createPurchasedTransactionWithProductID('foo'));
.add(fakeIOSPlatform.createPurchasedTransaction('foo', 'bar'));
Completer completer = Completer();
Stream<List<PurchaseDetails>> stream =
AppStoreConnection.instance.purchaseUpdatedStream;
Expand Down Expand Up @@ -348,7 +348,7 @@ class FakeIOSPlatform {
testRestoredError = null;
}

SKPaymentTransactionWrapper createPendingTransactionWithProductID(String id) {
SKPaymentTransactionWrapper createPendingTransaction(String id) {
return SKPaymentTransactionWrapper(
transactionIdentifier: null,
payment: SKPaymentWrapper(productIdentifier: id),
Expand All @@ -358,21 +358,21 @@ class FakeIOSPlatform {
originalTransaction: null);
}

SKPaymentTransactionWrapper createPurchasedTransactionWithProductID(
String id) {
SKPaymentTransactionWrapper createPurchasedTransaction(
String productId, String transactionId) {
return SKPaymentTransactionWrapper(
payment: SKPaymentWrapper(productIdentifier: id),
payment: SKPaymentWrapper(productIdentifier: productId),
transactionState: SKPaymentTransactionStateWrapper.purchased,
transactionTimeStamp: 123123.121,
transactionIdentifier: id,
transactionIdentifier: transactionId,
error: null,
originalTransaction: null);
}

SKPaymentTransactionWrapper createFailedTransactionWithProductID(String id) {
SKPaymentTransactionWrapper createFailedTransaction(String productId) {
return SKPaymentTransactionWrapper(
transactionIdentifier: null,
payment: SKPaymentWrapper(productIdentifier: id),
payment: SKPaymentWrapper(productIdentifier: productId),
transactionState: SKPaymentTransactionStateWrapper.failed,
transactionTimeStamp: 123123.121,
error: SKError(
Expand Down Expand Up @@ -434,26 +434,26 @@ class FakeIOSPlatform {
return Future<void>.sync(() {});
case '-[InAppPurchasePlugin addPayment:result:]':
String id = call.arguments['productIdentifier'];
SKPaymentTransactionWrapper transaction =
createPendingTransactionWithProductID(id);
SKPaymentTransactionWrapper transaction = createPendingTransaction(id);
AppStoreConnection.observer
.updatedTransactions(transactions: [transaction]);
sleep(const Duration(milliseconds: 30));
if (testTransactionFail) {
SKPaymentTransactionWrapper transaction_failed =
createFailedTransactionWithProductID(id);
createFailedTransaction(id);
AppStoreConnection.observer
.updatedTransactions(transactions: [transaction_failed]);
} else {
SKPaymentTransactionWrapper transaction_finished =
createPurchasedTransactionWithProductID(id);
createPurchasedTransaction(id, transaction.transactionIdentifier);
AppStoreConnection.observer
.updatedTransactions(transactions: [transaction_finished]);
}
break;
case '-[InAppPurchasePlugin finishTransaction:result:]':
finishedTransactions
.add(createPurchasedTransactionWithProductID(call.arguments));
finishedTransactions.add(createPurchasedTransaction(
call.arguments["productIdentifier"],
call.arguments["transactionIdentifier"]));
break;
}
return Future<void>.sync(() {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ void main() {
queue.setTransactionObserver(observer);
await queue.finishTransaction(dummyTransaction);
expect(fakeIOSPlatform.transactionsFinished.first,
equals(dummyTransaction.transactionIdentifier));
equals(dummyTransaction.toFinishMap()));
});

test('should restore transaction', () async {
Expand Down Expand Up @@ -139,7 +139,7 @@ class FakeIOSPlatform {

// payment queue
List<SKPaymentWrapper> payments = [];
List<String> transactionsFinished = [];
List<Map<String, String>> transactionsFinished = [];
String applicationNameHasTransactionRestored;

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

0 comments on commit 1d25eee

Please sign in to comment.