Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.20

* Fixes manual invocation of `finishTransaction` causing a fatal crash.

## 0.3.19+1

* Removes unneeded platform availability annotations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,25 @@ 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 {
continue
}

// 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)
{
getPaymentQueueHandler().finish(transaction)
// 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 isCancelledTransaction =
transactionIdentifier == nil && transaction.transactionIdentifier == nil
&& transaction.payment.productIdentifier == productIdentifier

guard matchesTransactionIdentifier || isCancelledTransaction else {
continue
}
getPaymentQueueHandler().finish(transaction)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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+1
version: 0.3.20

environment:
sdk: ^3.3.0
Expand Down