Payments

How payments work

Payment intents, attempts, the statuses a payment goes through, and which signal to trust before you deliver.

Open as Markdown
On this page
  1. Payment intents and attempts
  2. The lifecycle
  3. Each status, and what to do
  4. When something does not add up
  5. Attempts
  6. Next actions
  7. Amounts and currency
  8. Redirects and webhooks: which one to trust
  9. Refunds
  10. Events you receive

Every payment, by card or in cash, is a payment intent: one object for one amount you want to collect, from creation to the money received. You create it yourself, or a checkout session or a payment link creates it for you. Its status tells you where it stands; your code reads it rather than guessing.

Payment intents and attempts

A payment intent can be tried several times. Each try is an attempt, also called a charge (ch_…): a card payment page opened, or a cash code issued. A declined card or an expired code ends the attempt, not the payment intent: your customer can try again, with the same method or another.

  • The intent's latest_charge names its most recent attempt.
  • A payment intent succeeds once. Even if a confirmation reaches us several times, there is one success, one payment_intent.succeeded event and one entry in your balance.
  • amount_received is set when the payment succeeds; amount_refunded grows with each refund.

The lifecycle

The statuses of a payment intent
  • requires_payment_method → requires_action: card page or cash code
  • requires_action → processing: customer paid
  • processing → succeeded: confirmed, amount matches
  • requires_action → requires_payment_method: declined, failed or expired
  • processing → requires_payment_method
  • requires_payment_method → canceled: canceled or expired
  • requires_action → canceled: you cancel

A card payment confirmed at once can also go straight from requires_action to succeeded.

Each status, and what to do

StatusWhat it meansWhat you do
requires_payment_methodNothing is being paid: the payment was just created, or the last attempt failed.Send your customer to pay, or let them try again. last_payment_error says why the last attempt failed.
requires_actionYour customer has to act: finish on the card payment page, or pay the cash code at an agent.Wait. With your own page, show next_action (see below).
processingYour customer has paid; the confirmation is on its way.Wait for the webhook. Do not deliver yet.
succeededThe payment is confirmed and the amount matches.Deliver the order. This is final.
canceledYou canceled the payment, or its checkout session expired unpaid.Nothing: no money moves. Create a new payment if your customer comes back.

When something does not add up

If a payment is confirmed for another amount than the one asked, or money arrives after the payment was canceled or its code expired, nothing is credited automatically. The payment intent stays in processing and gains a review object, with reason set to amount_mismatch or paid_after_end, and you receive review.opened. A person on our side looks at it. In the sandbox, the cash scenarios let you trigger both.

Attempts

An attempt's status is pending, succeeded, failed, canceled or expired. A failed attempt carries failure_code and failure_message, and the intent copies them into last_payment_error:

CodeMethodWhat happened
card_declinedCardThe bank declined. decline_code may say more, such as insufficient_funds.
expired_cardCardThe card has expired.
authentication_failedCard3-D Secure did not succeed.
processing_errorCard, cashThe payment could not be processed. Your customer can try again.
voucher_expiredCashThe code was not paid before its deadline.
voucher_refusedCashThe payment network refused the code.

An attempt has a limited life: about 30 minutes for a card payment page, and the deadline of the code for cash (about three days). An attempt that runs out ends expired.

Next actions

While a payment intent is in requires_action, its next_action says what your customer must do. With hosted checkout or a payment link you never read it: our pages do the work. With your own page:

  • form_post, for a card: an address and fields that your customer's browser posts to reach the card payment page.
  • display_voucher, for cash: the code, the amount_to_pay (your amount plus a small stamp fee your customer pays), expires_at, a printable slip_url, and hosted_voucher_url, our voucher page.

Amounts and currency

  • Every amount is an integer in centimes: 25000 is 250.00 MAD. Never send decimals.
  • currency is always mad, in lowercase.
  • A cash payment is at least 100.00 MAD (10000); a card payment at least 1.00 MAD (100). A smaller amount is refused with amount_too_small, in test mode as in live mode.

Redirects and webhooks: which one to trust

Your customer comes back to your success_url after paying, but that return proves nothing: they can close the tab first, a cash payment is made days later at an agent, and anyone can type the address.

Deliver on one of these two signals only:

  1. the webhook payment_intent.succeeded, with a verified signature;
  2. the payment intent read from the API, with your secret key, showing status: "succeeded".

Use the return to thank your customer and show "We are confirming your payment"; then let the webhook do the rest.

Refunds

A succeeded card payment can be refunded, in full or in part, as long as the total refunded stays within the amount received. A refund is pending, then succeeded, failed or canceled. A cash payment cannot be refunded through AdProMaxPay: the money was handed over in cash, and there is no card to return it to. Settle it with your customer directly. See Refunds.

Events you receive

EventWhen
payment_intent.requires_actionA card page was opened or a cash code issued.
payment_intent.processingYour customer paid; the confirmation is on its way.
payment_intent.succeededThe payment is confirmed. Deliver.
payment_intent.payment_failedAn attempt failed: declined, expired or refused.
payment_intent.canceledThe payment was canceled.
charge.succeeded, charge.failedAn attempt succeeded or failed.
review.openedSomething does not add up; nothing was credited.

The full list is in Webhooks.