ZyloPaydocs

Payment lifecycle

The statuses a payment intent and a payment go through, what triggers each change, and what your code should do.

Two objects describe a payment:

  • A payment intent (pi_…) is the attempt: one amount, one terminal, from creation until it is paid, canceled or expires. It exists before any money moves.
  • A payment (pay_…) is the settlement: the USDC transfer recorded on Monad. It exists only once money has moved, and it is final.

Every payment has an intent, whoever started it: your server (source: "api") or the cashier on the terminal (source: "terminal").

Payment intent statuses

stateDiagram-v2
  [*] --> requires_payment_method: created
  requires_payment_method --> requires_action: tap held for payer confirmation
  requires_payment_method --> processing: tap accepted, settlement sent
  requires_payment_method --> requires_payment_method: tap declined (last_payment_error)
  requires_payment_method --> canceled: canceled or expired
  requires_action --> processing: payer confirms
  requires_action --> canceled: payer or cashier declines, or confirmation expires
  processing --> succeeded: settlement mined
  processing --> requires_payment_method: settlement failed, nothing charged
  succeeded --> [*]
  canceled --> [*]
StatusMeaningWhat to do
requires_payment_methodWaiting for a tap. Also the status after a declined attempt, with last_payment_error set.Wait. Show the customer the decline reason if there is one.
requires_actionThe tap was accepted but is held until the customer confirms in the payer app. next_action says why and until when.Wait for the next event. Cancel if the customer walks away.
processingThe settlement was sent to Monad. Its outcome is not known yet.Never retry. ZyloPay resolves it and sends payment_intent.succeeded or payment_intent.payment_failed.
succeededSettled. payment names the settled payment once indexed.Fulfil the order.
canceledClosed without payment: canceled by you or the cashier, declined confirmation, or expired. cancellation_reason says which.Nothing was charged. Start a new intent if needed.

A declined tap is not an HTTP error. The intent stays open until it expires, so the customer can try again, for example with another pass. Each decline sends payment_intent.payment_failed with a decline code in last_payment_error.code.

processing exists because a blockchain transaction can be sent and its receipt not seen in time. ZyloPay never sends a payment twice. A reconciler checks the chain and moves the intent to succeeded or back to requires_payment_method, with an event either way.

Payment statuses

A payment is created only when a settlement is mined and indexed, about a second after the tap. It is final.

StatusMeaning
succeededSettled, nothing refunded.
partially_refundedPart of amount was refunded (amount_refunded < amount).
refundedThe whole amount was refunded.

Events in order

A typical API-created payment produces:

  1. payment_intent.created
  2. payment_intent.requires_action (only if held for confirmation)
  3. payment_intent.processing (only if the outcome was not known at once)
  4. payment_intent.succeeded
  5. payment.succeeded

Webhooks are delivered at least once and not in order. Base your logic on the status in the object, not on the order of arrival. See Ordering and duplicates.

Which to listen to

  • To fulfil an order you created with the API, listen to payment_intent.succeeded and match on the intent ID (or your order_id or metadata).
  • To record every sale, whatever started it, listen to payment.succeeded. It fires once per payment.
  • To show a live status on your own screen, follow the payment_intent.* events, or poll GET /v1/payment_intents/{id}.

On this page