From a8728852e70ff966656b00cfa83146db8fe69e5c Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 14:38:33 -0800 Subject: [PATCH 1/9] boop --- .../ios/Runner.xcodeproj/project.pbxproj | 2 +- .../darwin/Classes/InAppPurchasePlugin.swift | 4 +- .../InAppPurchasePluginTests.swift | 40 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj index c3302f6efa7..bea5556579c 100644 --- a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj @@ -177,7 +177,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index 23b8972c96c..cb18a860955 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -256,10 +256,10 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { // 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 == transactionIdentifier + if ((transaction.transactionIdentifier == transactionIdentifier || (transactionIdentifier == nil && transaction.transactionIdentifier == nil - && transaction.payment.productIdentifier == productIdentifier) + && transaction.payment.productIdentifier == productIdentifier)) && transaction.transactionState != SKPaymentTransactionState.purchasing) { getPaymentQueueHandler().finish(transaction) } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift index a074f133161..a889d70d407 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift @@ -135,6 +135,46 @@ final class InAppPurchasePluginTests: XCTestCase { XCTAssertNil(error) } + func testFinishTransactionNotCalledOnPurchasingTransactions() { + let args: [String: Any] = [ + "transactionIdentifier": NSNull(), + "productIdentifier": "unique_identifier", + ] + + let paymentMap: [String: Any] = [ + "productIdentifier": "123", + "requestData": "abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + "quantity": 2, + "applicationUsername": "app user name", + "simulatesAskToBuyInSandbox": false, + ] + + let transactionMap: [String: Any] = [ + "transactionState": SKPaymentTransactionState.purchasing.rawValue, + "payment": paymentMap, + "error": FIAObjectTranslator.getMapFrom( + NSError(domain: "test_stub", code: 123, userInfo: [:])), + "transactionTimeStamp": NSDate().timeIntervalSince1970, + ] + + let paymentTransactionStub = SKPaymentTransactionStub(map: transactionMap) + + let handler = PaymentQueueHandlerStub() + plugin.paymentQueueHandler = handler; + + var finishTransactionInvokeCount = 0; + + handler.finishTransactionStub = { _ in + finishTransactionInvokeCount += 1; + } + + var error: FlutterError? + plugin.finishTransactionFinishMap(args, error: &error) + + XCTAssertNil(error) + XCTAssertEqual(finishTransactionInvokeCount, 0); + } + func testGetProductResponseWithRequestError() { let argument = ["123"] let expectation = self.expectation(description: "completion handler successfully called") From 0d591135665620d4863bf1fabe1c660bd35a821f Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 14:45:40 -0800 Subject: [PATCH 2/9] bump version --- .../example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../in_app_purchase/in_app_purchase_storekit/CHANGELOG.md | 4 ++++ .../in_app_purchase/in_app_purchase_storekit/pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj index bea5556579c..c3302f6efa7 100644 --- a/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj @@ -177,7 +177,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { 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 4e46c343ea1..3b86191b403 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,7 @@ +## 0.3.20 + +* Fixes manual invocation of `finishTransaction` causing a fatal crash. + ## 0.3.19 * Adds StoreKit2 Transaction expiration date. 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 eeaa9a865f2..aa3c1d7b3e6 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.3.19 +version: 0.3.20 environment: sdk: ^3.3.0 From af7f7aff2f417784a15ddeb7db5d541af030c778 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 14:58:35 -0800 Subject: [PATCH 3/9] format --- .../darwin/Classes/InAppPurchasePlugin.swift | 5 +++-- .../shared/RunnerTests/InAppPurchasePluginTests.swift | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index cb18a860955..df4a823b068 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -256,10 +256,11 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { // 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 == transactionIdentifier + if (transaction.transactionIdentifier == transactionIdentifier || (transactionIdentifier == nil && transaction.transactionIdentifier == nil - && transaction.payment.productIdentifier == productIdentifier)) && transaction.transactionState != SKPaymentTransactionState.purchasing) + && transaction.payment.productIdentifier == productIdentifier)) + && transaction.transactionState != SKPaymentTransactionState.purchasing { getPaymentQueueHandler().finish(transaction) } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift index a889d70d407..dd91bc6040a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.swift @@ -160,19 +160,19 @@ final class InAppPurchasePluginTests: XCTestCase { let paymentTransactionStub = SKPaymentTransactionStub(map: transactionMap) let handler = PaymentQueueHandlerStub() - plugin.paymentQueueHandler = handler; + plugin.paymentQueueHandler = handler - var finishTransactionInvokeCount = 0; + var finishTransactionInvokeCount = 0 handler.finishTransactionStub = { _ in - finishTransactionInvokeCount += 1; + finishTransactionInvokeCount += 1 } var error: FlutterError? plugin.finishTransactionFinishMap(args, error: &error) XCTAssertNil(error) - XCTAssertEqual(finishTransactionInvokeCount, 0); + XCTAssertEqual(finishTransactionInvokeCount, 0) } func testGetProductResponseWithRequestError() { From e0ed8770664202350931f30d5c63a3f3f8bc6cd7 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 15:48:39 -0800 Subject: [PATCH 4/9] pr comments --- .../darwin/Classes/InAppPurchasePlugin.swift | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index df4a823b068..7030870a683 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -253,17 +253,22 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { let pendingTransactions = getPaymentQueueHandler().getUnfinishedTransactions() for transaction in pendingTransactions { + // finishTransaction() cannot be called on a Transaction with a current purchasing state + // https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction + guard (transaction.transactionState != SKPaymentTransactionState.purchasing) else { + return + } + // 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 == transactionIdentifier - || (transactionIdentifier == nil - && transaction.transactionIdentifier == nil - && transaction.payment.productIdentifier == productIdentifier)) - && transaction.transactionState != SKPaymentTransactionState.purchasing - { - getPaymentQueueHandler().finish(transaction) + guard transaction.transactionIdentifier == transactionIdentifier || + (transactionIdentifier == nil && + transaction.transactionIdentifier == nil && + transaction.payment.productIdentifier == productIdentifier) else { + return } + getPaymentQueueHandler().finish(transaction) } } From 1cf595cb260bb1ad1b45f24b63ef9201b0f40ce2 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 15:52:18 -0800 Subject: [PATCH 5/9] format --- .../darwin/Classes/InAppPurchasePlugin.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index 7030870a683..d9f61184090 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -255,17 +255,18 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { for transaction in pendingTransactions { // finishTransaction() cannot be called on a Transaction with a current purchasing state // https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction - guard (transaction.transactionState != SKPaymentTransactionState.purchasing) else { + guard transaction.transactionState != SKPaymentTransactionState.purchasing else { return } // 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. - guard transaction.transactionIdentifier == transactionIdentifier || - (transactionIdentifier == nil && - transaction.transactionIdentifier == nil && - transaction.payment.productIdentifier == productIdentifier) else { + guard + transaction.transactionIdentifier == transactionIdentifier + || (transactionIdentifier == nil && transaction.transactionIdentifier == nil + && transaction.payment.productIdentifier == productIdentifier) + else { return } getPaymentQueueHandler().finish(transaction) From 0bf665b6c87a790218629c54730cbc23607d6b55 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 16:15:34 -0800 Subject: [PATCH 6/9] pr comments --- .../darwin/Classes/InAppPurchasePlugin.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index d9f61184090..679fa8f62f4 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -262,11 +262,10 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { // 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. - guard - transaction.transactionIdentifier == transactionIdentifier - || (transactionIdentifier == nil && transaction.transactionIdentifier == nil - && transaction.payment.productIdentifier == productIdentifier) - else { + let matchesTransactionIdentifier = transaction.transactionIdentifier == transactionIdentifier; + let isRestoringTransaction = transactionIdentifier == nil && transaction.transactionIdentifier == nil && transaction.payment.productIdentifier == productIdentifier + + guard matchesTransactionIdentifier || isRestoringTransaction else { return } getPaymentQueueHandler().finish(transaction) From 23014d043b41f733198653242695aff674b46142 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 16:20:27 -0800 Subject: [PATCH 7/9] =?UTF-8?q?FORMAT=20=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../darwin/Classes/InAppPurchasePlugin.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index 679fa8f62f4..bc77bb25187 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -262,8 +262,10 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { // 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. - let matchesTransactionIdentifier = transaction.transactionIdentifier == transactionIdentifier; - let isRestoringTransaction = transactionIdentifier == nil && transaction.transactionIdentifier == nil && transaction.payment.productIdentifier == productIdentifier + let matchesTransactionIdentifier = transaction.transactionIdentifier == transactionIdentifier + let isRestoringTransaction = + transactionIdentifier == nil && transaction.transactionIdentifier == nil + && transaction.payment.productIdentifier == productIdentifier guard matchesTransactionIdentifier || isRestoringTransaction else { return From 99a25a97aee05cc6735cdf37387c551367398a2a Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 17:18:40 -0800 Subject: [PATCH 8/9] :( --- .../darwin/Classes/InAppPurchasePlugin.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index bc77bb25187..9fd9bbec1de 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -256,7 +256,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { // finishTransaction() cannot be called on a Transaction with a current purchasing state // https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction guard transaction.transactionState != SKPaymentTransactionState.purchasing else { - return + continue } // If the user cancels the purchase dialog we won't have a transactionIdentifier. @@ -268,7 +268,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { && transaction.payment.productIdentifier == productIdentifier guard matchesTransactionIdentifier || isRestoringTransaction else { - return + continue } getPaymentQueueHandler().finish(transaction) } From 4e79b13c12a0a3cb9594f96d7ddf0767c20f432e Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 12 Nov 2024 17:31:49 -0800 Subject: [PATCH 9/9] . --- .../darwin/Classes/InAppPurchasePlugin.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index 9fd9bbec1de..8954f2f46b1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -260,14 +260,15 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, FIAInAppPurchaseAPI { } // 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. + // So if transactionIdentifier is null AND a transaction in the pendingTransactions list + // also has a null transactionIdentifier, we check for equal product identifiers. + // TODO(louisehsu): See if we can check for SKErrorPaymentCancelled instead. let matchesTransactionIdentifier = transaction.transactionIdentifier == transactionIdentifier - let isRestoringTransaction = + let isCancelledTransaction = transactionIdentifier == nil && transaction.transactionIdentifier == nil && transaction.payment.productIdentifier == productIdentifier - guard matchesTransactionIdentifier || isRestoringTransaction else { + guard matchesTransactionIdentifier || isCancelledTransaction else { continue } getPaymentQueueHandler().finish(transaction)