Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## NEXT
## 0.4.8

* Fixes an issue causing StoreKit2 purchases to be reported as `restored` and left in an
unfinished state, due to `pendingCompletePurchase` being false.
* Fixes Xcode 26.2 analyzer warnings in example app tests.

## 0.4.7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ extension InAppPurchasePlugin: InAppPurchase2API {
switch completedPurchase {
case .verified(let purchase):
self.sendTransactionUpdate(
transaction: purchase, receipt: "\(completedPurchase.jwsRepresentation)")
transaction: purchase, receipt: "\(completedPurchase.jwsRepresentation)",
restoring: true)
case .unverified(let failedPurchase, let error):
unverifiedPurchases[failedPurchase.id] = (
receipt: completedPurchase.jwsRepresentation, error: error
Expand Down Expand Up @@ -354,8 +355,10 @@ extension InAppPurchasePlugin: InAppPurchase2API {
}

/// Sends an transaction back to Dart. Access these transactions with `purchaseStream`
private func sendTransactionUpdate(transaction: Transaction, receipt: String? = nil) {
let transactionMessage = transaction.convertToPigeon(receipt: receipt)
private func sendTransactionUpdate(
transaction: Transaction, receipt: String? = nil, restoring: Bool = false
) {
let transactionMessage = transaction.convertToPigeon(receipt: receipt, restoring: restoring)
Task { @MainActor in
self.transactionCallbackAPI?.onTransactionsUpdated(newTransactions: [transactionMessage]) {
result in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ extension Product.PurchaseResult {

@available(iOS 15.0, macOS 12.0, *)
extension Transaction {
func convertToPigeon(receipt: String?) -> SK2TransactionMessage {
func convertToPigeon(receipt: String?, restoring: Bool = false) -> SK2TransactionMessage {

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
Expand All @@ -205,7 +205,7 @@ extension Transaction {
expirationDate: expirationDate.map { dateFormatter.string(from: $0) },
purchasedQuantity: Int64(purchasedQuantity),
appAccountToken: appAccountToken?.uuidString,
restoring: receipt != nil,
restoring: restoring,
receiptData: receipt,
jsonRepresentation: String(decoding: jsonRepresentation, as: UTF8.self)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import XCTest
@testable import in_app_purchase_storekit

final class FakeIAP2Callback: InAppPurchase2CallbackAPIProtocol {

public var lastUpdate: [in_app_purchase_storekit.SK2TransactionMessage] = []

func onTransactionsUpdated(
newTransactions newTransactionsArg: [in_app_purchase_storekit.SK2TransactionMessage],
completion: @escaping (Result<Void, in_app_purchase_storekit.PigeonError>) -> Void
) {
lastUpdate = newTransactionsArg
// We should only write to a flutter channel from the main thread.
XCTAssertTrue(Thread.isMainThread)
}
Expand All @@ -21,6 +25,7 @@ final class FakeIAP2Callback: InAppPurchase2CallbackAPIProtocol {
final class InAppPurchase2PluginTests: XCTestCase {
private var session: SKTestSession!
private var plugin: InAppPurchasePlugin!
private var callback: FakeIAP2Callback = FakeIAP2Callback()

override func setUp() async throws {
try await super.setUp()
Expand All @@ -33,7 +38,7 @@ final class InAppPurchase2PluginTests: XCTestCase {
plugin = InAppPurchasePluginStub(receiptManager: FIAPReceiptManagerStub()) { request in
DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: request))
}
plugin.transactionCallbackAPI = FakeIAP2Callback()
plugin.transactionCallbackAPI = callback
try plugin.startListeningToTransactions()
}

Expand Down Expand Up @@ -86,11 +91,17 @@ final class InAppPurchase2PluginTests: XCTestCase {

await fulfillment(of: [purchaseExpectation], timeout: 5)

XCTAssert(callback.lastUpdate.count == 1)
XCTAssert(
callback.lastUpdate.first?.restoring == false,
"Ordinary purchase updates should not be marked as restoring")

plugin.transactions {
result in
switch result {
case .success(let transactions):
XCTAssert(transactions.count == 1)
XCTAssert(transactions.first?.restoring == false)
transactionExpectation.fulfill()
case .failure(let error):
XCTFail("Getting transactions should NOT fail. Failed with \(error)")
Expand Down Expand Up @@ -376,6 +387,9 @@ final class InAppPurchase2PluginTests: XCTestCase {
}
await fulfillment(of: [purchaseExpectation], timeout: 5)

XCTAssert(callback.lastUpdate.count == 1)
XCTAssert(callback.lastUpdate.first?.restoring == false)

plugin.restorePurchases { result in
switch result {
case .success():
Expand All @@ -385,6 +399,9 @@ final class InAppPurchase2PluginTests: XCTestCase {
}
}
await fulfillment(of: [restoreExpectation], timeout: 5)

XCTAssert(callback.lastUpdate.count == 1)
XCTAssert(callback.lastUpdate.first?.restoring == true)
}

func testFinishTransaction() async throws {
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.4.7
version: 0.4.8

environment:
sdk: ^3.9.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ void main() {
final List<PurchaseDetails> result = await completer.future;
expect(result.length, 1);
expect(result.first.productID, dummyProductWrapper.id);
expect(result.first.status, PurchaseStatus.purchased);
expect(result.first.pendingCompletePurchase, true);
},
);

Expand Down