Developers · Hashry Pay
Statuses, declines and cancelling
What happens to a payment after it starts, and what your server and page do at each point.
The code on each stack page already handles all of this. Read this page to understand it, or before you change that code.
Payment statuses
Your server reads a payment's status with retrieve or refresh. That is the answer that counts. The page sees the same values through pollStatus, for display only.
| Status | What it means | What to do |
|---|---|---|
redirect_issued | The card form is open. The shopper may be typing, trying another card, or a card may be going through. (The name comes from the older redirect flow.) | Wait. Never start a second payment for this order. |
processing | Hashry is still setting up the card form, or the gateway has not answered yet. | Wait, then confirm again. Never start a new payment: the first one may still go through. |
succeeded | Paid. | Check that external_reference, amount and currency match your order. Then mark it paid. |
requires_payment_method | Nothing was charged. Every card the card form allowed was declined, the gateway failed, or the card form expired unused. | Your payment route starts a new attempt. The code on each stack page does this. |
expired | The payment passed its retry window: the time it stays open for new attempts (7 days by default). Nothing was charged. | Start a new payment with a new key, if the shopper still wants to pay. |
canceled | Your server cancelled it before a card was sent. Nothing was charged. | Start a new payment if needed. |
payment_review | Hashry staff are checking it, for example because the amount the gateway reported does not match. attention_required is true. | Don't charge again, and don't ship yet. payment.succeeded follows if it is approved. |
awaiting_customer | Payment links only. Nobody has opened the link yet. | Nothing. |
succeeded, expired and canceled never change. A payment
can be refunded after succeeded: amount_refunded and the payment.refunded webhook tell you.
Declines and retries
A declined card does not end the payment. The card form asks for another card, and your server does nothing.
- The bank declines the card. The card form shows "Your card was declined. Nothing was charged. Try another card."
- Your page gets
onDeclined(ondeclinedin Svelte), thenonReady. Unlock the page and keep the card form. - The payment stays
redirect_issued. A new attempt appears in itsattempts. Nopayment.failedwebhook is sent. - The shopper can try up to 3 cards in one card form. When the last one is
declined, the card form says completed. The payment is now
requires_payment_method, andpayment.failedis sent. - Your page shows "Try again". It calls your payment route, which starts a new attempt. That card form takes up to 3 more cards.
- Only a card problem gets another try in the same card form: the bank declined it, or 3-D
Secure did not verify it. Any other failure, such as a gateway error, ends the attempt at
once with
requires_payment_method. "Try again" works the same way. - The server SDKs turn this on for every embedded payment, with
retry_after_decline: true. It is not a setting. - A redirect payment (no card form on your page) works differently. After a decline, the
shopper returns to your
cancel_url, and your server starts a new attempt.
Too many attempts
The gateway limits tries, to stop people testing stolen cards. Past the limit, the card form
shows "Too many attempts" and pauses for a few minutes. Your page gets onPaused (onpaused in Svelte). Nothing was charged. Keep the card form: its "Try again" brings
the form back.
| Limit | Live | Sandbox |
|---|---|---|
| Failures for one card, in 30 minutes | 3 | 20 |
| Different cards from one device, in 10 minutes | 3 | 20 |
One payment has its own limit on tries too.
Expiry
- A card form expires 10 minutes after it opens, if no card was sent. It says expired. Your page calls your payment route again. The same idempotency key renews the form, and the page shows the new one.
- A card form that was used is never renewed. Your route gets no
embed_urlback, and the page checks the status instead. - A whole payment expires at the end of its retry window (7 days by default). Its status
becomes
expired, andpayment.expiredis sent. The next call to your payment route starts a new payment.
Leaving the page and cancelling
The card form has no Cancel button. Your page owns the way out, so the shopper can always get back to their order without being charged twice.
Show "Back to cart" until onProcessing. Hide it while a card is going through.
Show it again on onDeclined, on onPaused, or after the outcome.
Never change an order's amount, or start a second payment for it, while its payment is redirect_issued or processing. A card may still be charged on the
first one.
When the shopper goes back to change the order, cancel its payment on your server first. A cancel works only if no card was sent.
import { ConflictError } from "@hashrytech/pay-node";
// The shopper went back to change the order. Cancel its payment first.
try {
await hashry.paymentIntents.cancel(order.paymentIntentUid);
// canceled: nothing can be charged now. The next checkout starts a new payment.
} catch (error) {
if (error instanceof ConflictError && error.code === "cancel_not_allowed") {
// A card was sent. Keep the order as it is: confirm and wait for the outcome.
} else {
throw error; // gateway_unavailable (502) changed nothing: try again
}
}from hashry_pay import ConflictError
# The shopper went back to change the order. Cancel its payment first.
try:
await hashry.payment_intents.cancel(order.payment_intent_uid)
# canceled: nothing can be charged now. The next checkout starts a new payment.
except ConflictError as exc:
if exc.code != "cancel_not_allowed":
raise
# A card was sent. Keep the order as it is: confirm and wait for the outcome.What cancel answers
| Answer | What it means |
|---|---|
200, status canceled | The payment is closed. No card can be charged on it. A repeat gets the same answer. |
409 cancel_not_allowed | A card was sent or is going through. Nothing changed. Confirm, and wait for the outcome. |
502 gateway_unavailable | Nothing changed. Try the cancel again. A repeat is safe. |
A payment you never cancel is safe too. Its card form expires, and the payment expires at the end of its retry window. The full list of error codes is on Errors.