Skip to content

Conversation

@edpizzi
Copy link
Contributor

@edpizzi edpizzi commented Dec 20, 2025

The StoreKit 2 (iOS, MacOS) in_app_purchase backend causes ordinary client use (eg. the in_app_purchase example app) to leave transactions in an unfinished state, contrary to the StoreKit contract.

This happens because StoreKit 2 purchase updates have pendingCompletePurchase == false, causing clients to not call completePurchase (flutter/flutter#180046). This in turn happens because purchase updates incorrectly have state restored instead of purchased (flutter/flutter#172434).

Fix this by keeping track of which TransactionMessages are created in the restorePurchases code path to set the restoring field correctly. Previously it was set whenever a transaction had receipt data (which is also true for ordinary purchase updates). This keeps ordinary purcahses as state purchased. These purchase records will also have pendingCompletePurchase set to true, since this is gated by state purchased.

Fixes:

Pre-Review Checklist

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2 3

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly fixes an issue where StoreKit 2 transactions were left unfinished. The introduction of a restoring flag to distinguish between new and restored purchases is a solid approach, ensuring that new purchases get the correct purchased status and can be properly completed by the client. The accompanying test updates effectively validate this fix.

I've added one minor suggestion for code clarity. Also, while I understand you skipped running the auto-formatter to ease the review, please ensure it's run before merging to comply with the repository's style guidelines.

case .verified(let purchase):
self.sendTransactionUpdate(
transaction: purchase, receipt: "\(completedPurchase.jwsRepresentation)")
transaction: purchase, receipt: "\(completedPurchase.jwsRepresentation)", restoring: true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The string interpolation "\()" around completedPurchase.jwsRepresentation is redundant because jwsRepresentation is already a String. You can simplify the code by passing the property directly for better readability and to adhere to Swift best practices.

              transaction: purchase, receipt: completedPurchase.jwsRepresentation, restoring: true
References
  1. The repository style guide requires following language-specific style guides (lines 10-12). In Swift, it's best practice to avoid redundant operations like wrapping an existing string in string interpolation for code clarity. (link)

@edpizzi
Copy link
Contributor Author

edpizzi commented Dec 21, 2025

I had overlooked the Swift tests under in_app_purchase_storekit/example/shared.RunnerTests. I've now added to them, and changed the callback to save the last update so we can validate that purchase callbacks do not have restoring set.

I've validated that the new assertions fail before this change.

Also resolve an issue where SK2 purchase updates are in state `restoring`.

Keep track of when TransactionMessages are created in the restorePurchases
code path, instead of incorrectly inferring that transactions with receipts
are from the restore code path.

This keeps ordinary purcahses as state `purchased`. These purchase detail
records will also have `pendingCompletePurchase` set to `true`, since this
is gated by state `purchased`.
@edpizzi edpizzi force-pushed the 2025-12-17-sk2-fix-pending-3-simple branch from b0c41ab to 626cb28 Compare December 21, 2025 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant