Devlog

Why Purchase Success Is Not Fulfillment

Making Games with AI · Part 14

Making Games with AI, part 14: Why Purchase Success Is Not Fulfillment. Historical Kibble Street TD screenshots and development diagrams.

This article revisits development in June–July 2026 and early August, using records through August 3, 2026. References to “current” behavior, values, images, candidates, and validation describe that historical snapshot, not today's released game.

Previous: Connecting a Game to Native iOS Services

A player taps Buy, confirms payment, and sees “Purchase successful.” It feels like one continuous action. For Kibble Street TD, platform confirmation is only an intermediate step. The game must still identify the product, grant its contents, save the result, and determine whether it has handled this transaction before.

A mistake can leave a paying player without their purchase or grant the same payment twice. One breaks trust; the other damages the economy. The difficult part is moving a transaction safely from platform state into game state.

One purchase has two ledgers

The platform records transaction facts: the product, transaction ID, verification result, and whether the transaction is finished. The game records fulfillment: the product's contents, the resources to grant, whether this transaction was already fulfilled, and whether those changes were saved.

The platform transaction ledger and the game's fulfillment ledger
The platform transaction ledger and the game's fulfillment ledger

The transaction ID connects the two. A product ID answers what was bought; a transaction ID identifies this particular purchase. Storing only the product cannot distinguish two purchases of the same item. Trusting only a success callback cannot reliably recover if the app closes before fulfillment.

Product-to-platform mappings also come from one configuration. Displayed prices use the platform's localized price, without assembling currency symbols or treating test prices as production prices. Configuration owns business identity; the platform owns transaction facts and localized pricing.

The order cannot be rearranged

The sequence is fixed: verify the platform transaction, check the product mapping, inspect the transaction ledger, grant rewards and record the transaction ID, save all changes, and only then tell the platform to finish the transaction.

Verification, fulfillment, saving, and finishing must happen in order
Verification, fulfillment, saving, and finishing must happen in order

Finishing must come last. If the platform transaction is finished before fulfillment or saving fails, it may no longer return as unfinished work. The player could lose the purchase permanently.

Granting and saving must also be one coordinated operation. The game snapshots currencies, inventory, purchase counts, and the transaction ledger before changing them. If any step fails, it restores the entire snapshot. This prevents partially applied states, such as added currency without a ledger entry or an increased purchase count without the item.

An earlier implementation rolled back only purchase counts and transaction records. That was insufficient because rewards could already have changed currency or inventory. A safe rollback must cover every state touched by the purchase.

The transaction ID prevents duplicate fulfillment

Idempotency has a concrete meaning here: however many times the same transaction arrives, it grants its reward only once.

A transaction ID acts as a gate against duplicate rewards
A transaction ID acts as a gate against duplicate rewards

On first receipt of a valid transaction, the game grants, records, and saves. If the same ID returns for the same product, it recognizes prior fulfillment, adds no resources, and only completes any unfinished platform cleanup. The same transaction ID pointing to a different product is rejected explicitly.

Weekly offer resets and the player's Reset Game action must preserve this ledger. Otherwise an old transaction could look new and grant again. The ledger records fulfilled payment obligations, rather than ordinary gameplay progress.

Two failures need two recovery paths

If granting or saving fails, the game restores every change and leaves the platform transaction unfinished. A later transaction update or a startup scan can retry the complete operation. A successful recovery produces one reward.

If saving succeeds but finishing the platform transaction fails, the game retains both rewards and the ledger. When the same transaction returns, the ledger blocks another grant and the game retries only the finish step.

Different recovery paths for fulfillment failure and finish failure
Different recovery paths for fulfillment failure and finish failure

The first path relies on the platform retaining unfinished work. The second relies on a durable game ledger. Combining them into one undifferentiated retry risks losing rewards or duplicating them.

Transactions must return after an app restart

Purchases do not always happen with an open screen and a stable connection. Players can leave the app after confirmation, and the process can terminate during fulfillment. Initialization therefore both listens for future transaction updates and actively scans the platform's unfinished transactions.

Live updates and startup recovery enter the same fulfillment pipeline
Live updates and startup recovery enter the same fulfillment pipeline

Both entry points use the same completion path. Only verified transactions can grant rewards. Unverified, pending, and canceled results are never converted to success. Recovery is another entrance to the existing rules, rather than a separate reward implementation.

Purchase limits also depend on timing. Before payment, a weekly limit or current state can prevent a new purchase. After confirmed payment, a later reduction in the configured limit must not deny fulfillment. Limits govern the next purchase, not an already established obligation.

Passing a test is not passing real payment acceptance

Early development used a direct-success switch to exercise product mapping, fulfillment, saving, and UI feedback. It shortened business-logic development, but could never be a fallback for failed real payments: that would grant paid content without a confirmed transaction.

The switch was disabled at this snapshot. Controlled protocol checks passed for both critical failures: a failed grant leaves the transaction unfinished and grants once after recovery; a failed finish preserves the reward and ledger and retries only cleanup.

Sandbox, TestFlight, device purchases, and interruption after payment still required real-environment testing. There was also a pre-submission gap around an unreadable transaction ledger. Normal resets and version cleanup preserved a valid ledger, but the exceptional unreadable-ledger path did not yet meet the release requirement to block explicitly.

Verified purchase checks and acceptance work still pending at the snapshot
Verified purchase checks and acceptance work still pending at the snapshot

Correct operation order, recovery from controlled failures, a successful real purchase, and recovery from a severe interruption are distinct conclusions. “Purchases are integrated” should not blur them.

What the human and AI each contribute

AI can follow a transaction ID through platform updates, rewards, saving, the ledger, and finishing. Injecting save failures, repeated callbacks, and finish failures reveals the state left behind. It can also find cross-module gaps, such as incomplete rollback or an exceptional cleanup that discards the ledger.

The human defines non-negotiable rules: confirmed payments must be fulfilled, a transaction cannot grant twice, and certain failures must block release. Real accounts, devices, backend configuration, and payment confirmation cannot be replaced by simulated results.

If you want to start

Answer six questions before implementing the Buy button: who verifies the transaction, how products map to rewards, where transaction IDs persist, whether granting and saving roll back together, when the transaction is finished, and how a restart recovers it.

Then inject two failures: one during fulfillment saving, another during platform finishing. Recovery from the first must grant exactly once. Recovery from the second must grant nothing additional. Consistent answers establish a basis for real platform acceptance.