Skip to content

Commit d031e46

Browse files
Address reviewer feedback: use Transaction.unfinished and return error for non-existent transactions
Changes based on @LongCatIsLooong's review: - Use Transaction.unfinished instead of Transaction.all for finish() since we're looking for transactions that need to be finished - Return an error when attempting to finish a non-existent transaction instead of silently succeeding - Added fetchUnfinishedTransaction() helper specifically for finish() - Updated tests to expect error for non-existent transaction
1 parent 41a7230 commit d031e46

File tree

3 files changed

+28
-26
lines changed

3 files changed

+28
-26
lines changed

packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
## 0.4.6+3
22

3-
* Fixes consumable products not being repurchasable after the first purchase in StoreKit2.
4-
* Ensures `finish()` always completes even when the transaction is not found in the transaction history.
5-
* Improves transaction lookup to also check `Transaction.unfinished` for consumables.
3+
* Fixes `finish()` method to always call its completion handler in StoreKit2.
4+
* `finish()` now returns an error when the transaction is not found in unfinished transactions.
5+
* Adds `fetchUnfinishedTransaction()` helper for looking up transactions that need to be completed.
66

77
## 0.4.6+2
88

packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit/StoreKit2/InAppPurchasePlugin+StoreKit2.swift

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,18 @@ extension InAppPurchasePlugin: InAppPurchase2API {
264264
func finish(id: Int64, completion: @escaping (Result<Void, Error>) -> Void) {
265265
Task {
266266
do {
267-
let transaction = try await fetchTransaction(by: UInt64(id))
267+
let transaction = try await fetchUnfinishedTransaction(by: UInt64(id))
268268
if let transaction = transaction {
269269
await transaction.finish()
270270
completion(.success(Void()))
271271
} else {
272-
// Transaction not found - this can happen for consumables that have
273-
// already been finished or are no longer in the transaction history.
274-
// We still return success as the transaction is effectively complete.
275-
completion(.success(Void()))
272+
// Transaction not found in unfinished transactions
273+
completion(
274+
.failure(
275+
PigeonError(
276+
code: "storekit2_transaction_not_found",
277+
message: "Transaction not found in unfinished transactions.",
278+
details: "Transaction ID: \(id)")))
276279
}
277280
} catch {
278281
completion(
@@ -376,12 +379,10 @@ extension InAppPurchasePlugin: InAppPurchase2API {
376379
return transactions
377380
}
378381

379-
/// Helper function to fetch specific transaction by ID.
380-
/// First checks Transaction.all, then falls back to unfinished transactions
381-
/// to ensure consumable transactions can be found and finished.
382-
func fetchTransaction(by id: UInt64) async throws -> Transaction? {
383-
// First, try to find in Transaction.all
384-
for await result in Transaction.all {
382+
/// Helper function to fetch specific transaction by ID from unfinished transactions.
383+
/// Used by finish() to find transactions that need to be completed.
384+
func fetchUnfinishedTransaction(by id: UInt64) async throws -> Transaction? {
385+
for await result in Transaction.unfinished {
385386
switch result {
386387
case .verified(let transaction):
387388
if transaction.id == id {
@@ -391,11 +392,13 @@ extension InAppPurchasePlugin: InAppPurchase2API {
391392
continue
392393
}
393394
}
395+
return nil
396+
}
394397

395-
// If not found in Transaction.all, check unfinished transactions
396-
// This is important for consumables that may have been purchased
397-
// but not yet iterated through Transaction.all
398-
for await result in Transaction.unfinished {
398+
/// Helper function to fetch specific transaction by ID from all transactions.
399+
/// Used for general transaction lookups.
400+
func fetchTransaction(by id: UInt64) async throws -> Transaction? {
401+
for await result in Transaction.all {
399402
switch result {
400403
case .verified(let transaction):
401404
if transaction.id == id {
@@ -405,7 +408,6 @@ extension InAppPurchasePlugin: InAppPurchase2API {
405408
continue
406409
}
407410
}
408-
409411
return nil
410412
}
411413
}

packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -415,19 +415,19 @@ final class InAppPurchase2PluginTests: XCTestCase {
415415
await fulfillment(of: [finishExpectation], timeout: 5)
416416
}
417417

418-
func testFinishNonExistentTransactionSucceeds() async throws {
419-
// Test that finishing a non-existent transaction returns success
420-
// This is important for consumables that may have already been finished
421-
// or are no longer in the transaction history
418+
func testFinishNonExistentTransactionReturnsError() async throws {
419+
// Test that finishing a non-existent transaction returns an error
422420
let finishExpectation = self.expectation(
423-
description: "Finishing non-existent transaction should succeed")
421+
description: "Finishing non-existent transaction should return error")
424422

425423
plugin.finish(id: 999999) { result in
426424
switch result {
427425
case .success():
428-
finishExpectation.fulfill()
426+
XCTFail("Finish should fail for non-existent transaction")
429427
case .failure(let error):
430-
XCTFail("Finish should NOT fail for non-existent transaction. Failed with \(error)")
428+
let pigeonError = error as! PigeonError
429+
XCTAssertEqual(pigeonError.code, "storekit2_transaction_not_found")
430+
finishExpectation.fulfill()
431431
}
432432
}
433433

0 commit comments

Comments
 (0)