Statuses, declines and cancelling

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.

StatusWhat it meansWhat to do
redirect_issuedThe 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.
processingHashry 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.
succeededPaid.Check that external_reference, amount and currency match your order. Then mark it paid.
requires_payment_methodNothing 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.
expiredThe 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.
canceledYour server cancelled it before a card was sent. Nothing was charged.Start a new payment if needed.
payment_reviewHashry 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_customerPayment 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.

  1. The bank declines the card. The card form shows "Your card was declined. Nothing was charged. Try another card."
  2. Your page gets onDeclined (ondeclined in Svelte), then onReady. Unlock the page and keep the card form.
  3. The payment stays redirect_issued. A new attempt appears in its attempts. No payment.failed webhook is sent.
  4. 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, and payment.failed is sent.
  5. 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.

LimitLiveSandbox
Failures for one card, in 30 minutes320
Different cards from one device, in 10 minutes320

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_url back, 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, and payment.expired is 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.

Do:

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.

Don't:

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.

Node: cancel
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
  }
}
Python: cancel
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

AnswerWhat it means
200, status canceledThe payment is closed. No card can be charged on it. A repeat gets the same answer.
409 cancel_not_allowedA card was sent or is going through. Nothing changed. Confirm, and wait for the outcome.
502 gateway_unavailableNothing 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.

Next: build it for your stack

Back to top

© 2026 Hashry Solutions Limited. All rights reserved.