Payment and Order State Model
This page documents the lifecycle of payments and orders on the Aghanim platform: every status, every transition, and how payment status changes drive order status. Use it to understand which webhook fires when, when a player can retry, and how to avoid duplicate charges or premature entitlement revocation.
The webhook events and API status values described here are part of the public integration contract. The specific intermediate transitions between statuses described on this page are documented for technical clarity but are an implementation detail. They may evolve as the platform grows. Build your integration against the webhook events and API status values, not against a specific transition graph.
Overview
Aghanim separates two entities in the purchase lifecycle:
- Payment — a single attempt to charge the player through a payment provider. A single order may have multiple payment attempts (for example, after a card decline). Each attempt is a separate payment record.
- Order — the logical purchase record (a player buying an item, bundle, or subscription). The platform updates the order status in response to payment transitions.
An order terminates only when one of its payments succeeds, when a refund completes, or when a dispute is resolved against the player.
Before a new payment record is created, the platform locks the order by moving it from created (or reattempted) to captured. This lock prevents two concurrent payment attempts on the same order.
Payment lifecycle
Payment statuses
| Status | Terminal | Meaning |
|---|---|---|
created | No | Payment initiated. The player has started the transaction and Aghanim is waiting for the payment provider to confirm the outcome. payment.pending is emitted at this point. |
done | No | Payment captured successfully. May still move to dispute or refund_requested later. |
dispute | No | A chargeback or refund request has been opened against a previously successful payment. Resolution moves it back to done (defense won or refund declined) or to canceled (chargeback accepted or lost). |
refund_requested | No | A refund has been filed but not yet confirmed by the payment provider. |
refunded | Yes | Refund confirmed by the payment provider, although funds may not have returned to the player's account yet. |
failed | Yes | Payment declined or errored during processing. |
expired | Yes | Payment session expired without capture (the player abandoned checkout, or the payment provider timed out the session). |
voided | Yes | Payment was authorized but voided before capture, typically due to a post-authorization check (country mismatch, anti-fraud). |
rejected | Yes | Payment rejected before submission, typically by the anti-fraud system. |
canceled | Yes | Reached from dispute only, when a chargeback is accepted. |
Payment transitions
| From | To | Cause | Terminal |
|---|---|---|---|
created | done | Payment provider confirms successful capture | No |
created | failed | Payment provider declines the payment or returns an error | Yes |
created | rejected | Anti-fraud declines the payment | Yes |
created | expired | Payment provider session times out or the player abandons checkout | Yes |
created | voided | Post-authorization check fails (country mismatch, anti-fraud) | Yes |
done | dispute | Chargeback initiated or refund requested against a successful payment | No |
done | refund_requested | Refund filed but pending payment provider confirmation | No |
done | refunded | Payment provider confirms refund immediately | Yes |
refund_requested | refunded | Payment provider confirms the pending refund | Yes |
refund_requested | done | Refund could not be processed | No |
dispute | done | Chargeback reversed or refund declined | No |
dispute | canceled | Chargeback accepted or refund succeeded | Yes |
Payment state diagram
Order lifecycle
The order status values visible to your integration:
| Status | Terminal | Meaning |
|---|---|---|
created | No | Order created. No payment attempt started yet. |
captured | No | A payment attempt is in progress. The order is locked against parallel payment attempts. |
reattempted | No | The previous payment attempt failed in a non-terminal way. The order is open for a new payment attempt. |
paid | No | A payment on this order succeeded. The order proceeds to delivery. |
disputed | No | A payment on this order is under dispute. |
refund_requested | No | A refund has been filed. |
refunded | Yes | Refund completed. |
canceled | Yes | Reached via a resolved dispute. |
How payment status drives order status
Order status changes are triggered by payment transitions:
| Payment transition | Order status | Effect |
|---|---|---|
| (before payment record is created) | created or reattempted → captured | Order locked against concurrent payment attempts |
created → done | captured → paid | item.add fired |
created → failed | captured → reattempted | Player can retry |
created → rejected | captured → reattempted | Player can retry |
created → expired | captured → reattempted | Player can retry |
created → voided | captured → reattempted | Player can retry |
done → dispute | paid → disputed | Dispute workflow |
done → refund_requested | paid → refund_requested | Refund pending |
done / refund_requested → refunded | → refunded | Terminal; item.remove fired |
dispute → canceled | disputed → canceled | Terminal; item.remove fired |
dispute → done | disputed → paid | Dispute resolved; entitlement remains |
Order state diagram
Webhooks emitted during the lifecycle
The transitions above drive the webhooks delivered to your integration:
payment.pending— emitted when a payment enterscreated(the player starts the transaction).item.add— emitted only on the success path: when a payment transitionscreated → doneand the order moves topaid.item.remove— emitted on two terminal order outcomes: when an order reachesrefunded(coversrefund_requested → refunded) and whendisputed → canceled(chargeback accepted). Opening a dispute (paid → disputed) and filing a refund (paid → refund_requested) do not emititem.remove— only the terminal resolution does.- A payment that ends in
failed,rejected,expired, orvoideddoes not produceitem.add. The order returns toreattemptedand the player may retry.
For a player to complete a purchase, one of their payment attempts must reach done. All other terminal statuses leave the order open for another attempt. Once a payment is done, only → refunded or disputed → canceled revoke the entitlement via item.remove.
Practical guidance
Some payment methods — telco billing such as Beeline or MTS, certain wallets, and PayPal in specific flows — can keep a payment in created for minutes or longer before the payment provider confirms the outcome. To handle these flows correctly and avoid duplicate charges or premature entitlement revocation, your integration should:
- Treat
payment.pendingas the signal to show the player that the purchase is in progress and to block repeated purchase attempts on the same item in your client. - If
item.adddoes not arrive within a reasonable time (10–20 minutes for most methods), call Get Order with theorder_idreturned at checkout to inspect the current order status. - Release the block when one of the following happens:
item.addarrives — the payment succeeded.- The order returns to
reattempted— a retry is available. - The order reaches a terminal status (
refundedorcanceled).
- Treat
item.removeas the only signal to revoke entitlement. Do not revoke onpaid → disputedorpaid → refund_requested— both can be reversed (a dispute may be won, a refund may be declined) and the player keeps the items. - All Aghanim webhook deliveries carry an
idempotency_key. Use it to deduplicate repeated deliveries on your side. See Idempotency for the full contract.
Need help?
Contact our integration team at integration@aghanim.com