Checkout & recovery
Reserve tickets before payment and keep the buyer informed until the purchase completes.
1. Create a checkout
Call POST /raffles/{raffleId}/checkouts from your server with an Idempotency-Key header and this body:
{"external_user_ref":"buyer_123","display_name":"Alex","qty":2}Use a unique, non-empty key of at most 200 characters for each purchase. Persist it before making the request. On timeouts, send the same key and body again.
A 201 returns the checkout. Save its id before opening the buyer's wallet. Replays return the existing checkout's current state; they do not create a fresh payment opportunity.
| Field | How to use it |
|---|---|
id | Resume and query this purchase |
total_usd | The agreed total, already including quantity |
payment | Wallet payment details, including the exact memo |
payment_deadline | Do not initiate payment after this time |
settlement_deadline | Latest settlement time for this reserved purchase |
status, error | The current outcome and any failure reason |
tickets | Ticket objects with ticket_no and code, populated on completion |
RAFFLE_PURCHASE_IN_PROGRESS means the buyer has an unresolved purchase. Resume it instead of charging again. IDEMPOTENCY_KEY_REUSED means the key was reused with a different raffle or quantity.
2. Submit the wallet payment
Follow Build the wallet payment. Use the exact payment.memo returned for this checkout. It includes the checkout ID.
After the wallet submits the transaction, call POST /raffles/{raffleId}/checkouts/{checkoutId}/payment with tx_signature. Register it promptly before payment_deadline; do not wait for finalization.
A 202 acknowledges the submission. It does not confirm tickets.
3. Track the result
Poll GET /raffles/{raffleId}/checkouts/{checkoutId}, or use payment.updated to trigger GET /payments/{operationId}. Keep the checkout ID through reloads and sign-in changes in your UI.
| API status | What the buyer sees |
|---|---|
awaiting_payment | Ready for wallet approval, if payment has not already been submitted and the deadline has not passed |
queued, processing, paid | Payment processing; do not prompt for another payment |
completed | Tickets confirmed; display the returned ticket codes |
failed | A clear error. Read retry_allowed before offering a retry |
cancelled | Reservation ended; keep checking if the buyer already submitted payment |
refund_pending | Refund pending |
refunded | Refund completed |
review | Payment under review; retain the purchase ID for support |
Only completed confirms a ticket purchase
A successful HTTP response, a wallet signature, or status paid alone does not mean tickets were issued.
Handle interruptions without a second charge
- Request timed out: retry with the same checkout key or transaction signature, depending on the step.
- Browser closed after payment: recover the checkout ID from your server and query its status.
- Wallet transaction outcome unknown: keep checking. Do not create a replacement payment.
- Failed attempt: a different signature is accepted only after the previous attempt is proven failed or expired and the deadline still permits it.
refund_only: true: a later successful payment will be refunded. Do not show another Pay button.
Use one status request at a time with backoff. Offer a Check status action after an extended wait rather than spinning indefinitely.
Track refunds
When returned, use refund_operation_id with GET /payments/{operationId}. A refund operation has kind: "refund"; its completed status means the refund finished, not that tickets were issued.
Verity-collected refunds require no refund-confirmation call from you. That endpoint is only for separately agreed partner-collected integrations.