Deliver consumable purchases
Fulfill once through Android handlers or manual sync/confirm, with durable inventory commits and iOS/Web differences.
The SDK owns verification and delivery workflow, not your inventory. DELIVERY_REQUIRED means fulfillment is pending, not that coins/credits were granted.
Durable grant contract
Commit inventory and the unique delivery key in one host database transaction. Scope it by app, original customer, source and delivery ID. Replaying a granted delivery must succeed without another credit. Memory flags or separate preferences writes don't establish this guarantee. Your trusted catalog determines credit quantity; the SDK does not infer it.
grantOnce below is a host-owned callback implementing that transaction. Validate the owner inside the transaction and quiesce/coordinate async delivery during account changes.
Android: registered handler
Register before configure:
Return delivered/DELIVERED only after commit; the SDK acknowledges and continues consumption/reporting. Never call confirmDelivery inside the handler. Failure, missing handler or retryLater leaves work pending. Kotlin returns a closable registration; Flutter removes a handler with null.
Manual iOS Direct or Android without a handler
Use only without a competing handler. stillSameCustomer complements, not replaces, transactional inventory ownership checks. Kotlin follows the same pattern with client.syncPendingConsumables() and client.confirmDelivery(deliveryId); its ID is Long.
A failed confirm must not erase or repeat a committed grant. The SDK retains checkpoints. Native consumption applies only to consumables, never subscriptions/non-consumables.
Direct JavaScript
The registered handler must explicitly return DeliveryResult.DELIVERED. Without a handler, syncPendingConsumables() returns summary/pendingDeliveries; durably grant before client.confirmDelivery(id). Registration returns an unregister function. The legacy inline-handler overload remains compatible; choose the explicit form for new code.
Platform limits
iOS has manual Direct fulfillment, not native handler registration. Flutter Web 0.2.1 doesn't provide native handler/sync/confirm. Direct JS support does not establish Flutter facade support. Don't bypass it with a second JS client. Consult the platform matrix.
Essential tests
Test duplicate delivery, termination after commit/before confirm, failed confirmation, restart and account change. Each delivery grants inventory at most once; confirmation never precedes the durable commit.