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

[in_app_purchase] Fix finishing purchases upon payment dialog cancel… #3106

Merged
merged 1 commit into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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