Purchase recovery begins with a disputed state: the store accepted payment but the game closed before delivery. When the app reopens, it must determine what was paid for and whether the reward was already issued. This recovery path is part of the purchase system, not an optional customer-support feature.
This article covers digital items sold through Google Play Billing and Apple's StoreKit. The applicable distribution and billing requirements depend on the storefront, product and region; the technical flow below concerns these store integrations.
Model the product before building the shop
A product identifier connects the store listing with the game's delivery rules. Keep that mapping in a controlled catalogue. The server should know what a verified product grants; it should not accept an arbitrary quantity submitted by the client.
Product type | Example | State the game must track |
Consumable | A pack of 500 coins | One verified grant per transaction and subsequent spending |
Non-consumable | Permanent ad removal | Whether the entitlement remains valid |
Auto-renewable subscription | Monthly membership benefits | Current access, renewal-related states and expiry |
The 500-coin pack is an illustrative product, not a published offer. Its catalogue definition should explain whether any bonus is included and whether the amount changes across catalogue versions. Display localized store-provided pricing rather than constructing a price from a currency symbol and a hard-coded number.
Keep store identity and game identity distinct. A valid store transaction still needs to be associated with the correct in-game account. Explain account-switching rules before a purchase, especially when multiple players share one device.
Build a purchase state machine
A boolean called purchased cannot describe a multi-step transaction. The game needs to distinguish a request that was started, a pending store payment, verified payment, committed delivery and completed platform processing. Also define cancellation, verification failure and later revocation.
Request the configured product through the store's supported purchase flow.
Receive the result and handle cancellation or pending payment without issuing the item.
Validate transaction authenticity, product, environment and account association.
Commit the entitlement or currency grant once, with a durable transaction record.
Complete acknowledgement, consumption or transaction finishing as appropriate to the platform.
Reconcile later events, including refunds and interrupted delivery.
Every transition should be restartable. If the network disappears between steps four and five, the system must remember the completed grant and retry platform processing without granting the item again.
Google Play: pending purchases and the three-day window
Google states that an entitlement must not be granted while a purchase remains PENDING. The three-day acknowledgement window starts when the state changes to PURCHASED, not when the player first opens the payment flow. Unacknowledged purchases can be automatically refunded and their entitlements revoked.
Verify purchase tokens through the appropriate Google Play Developer API and track previously processed tokens. For consumables, consumption fulfils the acknowledgement requirement and allows the product to be purchased again. Non-consumable products and subscriptions use their applicable acknowledgement flow.
A client callback alone should not be the only way the backend discovers a paid purchase. Use the supported notification and reconciliation mechanisms for the architecture, then query authoritative state. A player may close the game immediately after paying and never return before the acknowledgement deadline.
StoreKit: verified transactions and finishing delivery
StoreKit supplies signed transaction information and a verification result. Check that the transaction belongs to the intended product and environment before granting content. Apple describes finishing a transaction after the purchased content or service has been delivered. Finishing is not a substitute for recording the actual delivery.
Use transaction updates to handle events that do not occur in the foreground purchase button flow. On launch, reconcile current access and unfinished delivery. Apple's currentEntitlements sequence includes relevant non-consumables and subscriptions, but consumable purchases do not appear there. A coin balance therefore needs its own durable record.
For subscriptions, cancellation of renewal is not necessarily immediate loss of paid access. Determine entitlement from the verified subscription state and dates, including any applicable grace-period behaviour. A label saying “cancelled” is too ambiguous to drive the whole access decision.
Prevent duplicate delivery with a transaction ledger
Consider the example pack of 500 coins. A player has 200 coins before buying it, so the correct balance after delivery is 700. If a response is lost and the same transaction is processed twice, an unprotected implementation can raise the balance to 1,200.
Store the transaction identifier, store, environment, owner, product and delivery status. Use the store's documented identifier semantics and a database uniqueness constraint appropriate to them. Update the processed-transaction record and inventory atomically where they share a database.
If delivery crosses multiple services, design for retryable, idempotent handling and reconciliation. A durable outbox can record work alongside the originating state change so a temporary message-delivery failure does not silently lose the grant. Test two workers processing the same event concurrently, not just a repeated request sent after the first one finishes.
Separate restoration, refunds and account transfer
Restoration re-establishes access that the customer still owns. It should not regenerate every consumable previously purchased. A player who bought and spent 500 coins is different from one whose verified pack was never delivered.
A refund or revocation changes an earlier purchase's status. Record that change once and apply the documented game policy. If currency has already been spent, a simple subtraction may create an impossible balance or remove unrelated items. Decide the handling in advance and preserve an auditable relationship between the payment and its grant.
Do not promise that all purchases move from Android to iOS. A shared game account can support cross-platform access only where the product design and applicable store conditions allow it. Clearly distinguish transferable progress, server-owned inventory and store-specific entitlements.
Keep subscription access separate from periodic rewards
A membership may unlock a feature continuously while also granting a monthly allocation of currency. Those are two different responsibilities. Rechecking active access must not issue the allocation again. Give each reward period a stable identifier and record whether its grant was committed.
For an illustrative membership granting 100 gems per paid period, two deliveries of the same period should still produce a single 100-gem grant. A subsequent renewal is a new period with its own record. Do not identify periods solely by a device's calendar month: start dates, time zones and interrupted billing can make that assumption wrong.
When a subscription stops renewing, keep the access screen aligned with verified entitlement dates. If the backend temporarily cannot confirm a new event, show a defined checking state and reconcile later. Access handling during an outage needs an explicit product policy; silently treating every failed request as either permanent access or permanent cancellation causes avoidable disputes.
Test failure paths in sandbox before release
Scenario | Expected invariant | Evidence to inspect |
Payment remains pending | No paid item is delivered | Store status and unchanged inventory |
Client closes after payment | The valid purchase is recovered | Transaction record and eventual delivery |
Notification is repeated | Exactly one logical grant exists | Uniqueness constraint and inventory history |
Server processing temporarily fails | Work remains retryable | Queue or reconciliation record |
Purchase is refunded | The event is applied once | Verified status and entitlement history |
Apple provides StoreKit testing in Xcode and sandbox testing; TestFlight in-app purchases use the sandbox. Google provides billing test facilities, including delayed-payment scenarios for license testers. Keep test transaction records separate from production so a sandbox event cannot grant a live item.
Measure delivery rather than only payment success
Track verification failures, purchases awaiting platform completion, duplicate events and the delay from confirmed payment to durable entitlement. Define the denominator of each metric. In an illustrative cohort of 1,000 verified purchases, 990 grants within five seconds represents 99% within that threshold; the other ten still need a resolved outcome.
A support screen should show a safe order reference and the game account involved. It should never ask a player to pay again simply because delivery is delayed. Reconciliation between store state and the game ledger is what turns a successful payment into reliable access.