> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# What Can I Do to an Order in Each Status?

> The order status list including the two statuses most merchants never see, which statuses accept cancel, capture and refund, why an order sits in AUTHORIZATION_REQUIRED, where the close reason is written, how cancel behaves when the payment method has already taken money, and the rules for capturing an authorized card payment.

The status list itself is published in
[Payment lifecycle and order statuses](/docs/en/developer-docs/core-concepts/payment-lifecycle)
and [Data reference](/docs/en/developer-docs/tools-and-references/references/data-reference).
This page is the *operational* half: which actions each status accepts, and what
the three write endpoints — cancel, capture, refund — do when the order is not in
the state you expected.

| Page | Use it for |
| - | - |
| [Confirming Payment Results](/docs/en/support/payment-results-faq) | What each status *means*, and how to confirm a result |
| [Timeouts & When a Result Is Final](/docs/en/support/payment-timeout-and-finality-faq) | Whether a result can still change, and timeout-safe integration |
| **This page** | Which action is allowed in which status, and the errors when it is not |

## Are there statuses that are not in the published list?

Two, and both are worth knowing about because they explain confusing
observations rather than being things you act on.

| Status | What it is |
| - | - |
| `CAPTURE_IN_PROGRESS` | A capture you requested is being carried out. It sits between the authorized state and `PAY_SUCCESS`, and it accepts a repeat capture call |
| *(a pre-status before anything reaches a payment method)* | An order that has been accepted but for which no payment attempt exists yet is **reported to you as `AUTHORIZATION_REQUIRED`**. There is no separate value for it on the API |

The second one is the answer to a question merchants ask often, so it is worth
stating plainly: `AUTHORIZATION_REQUIRED` covers both *"the payment method is
waiting for the user to authorize"* and *"nothing has happened on this order
yet"*. You cannot tell those apart from the status alone.

## My order is stuck in `AUTHORIZATION_REQUIRED`. Did the user close the WebView?

Maybe — but the status does not tell you that, and it is not the only
explanation. `AUTHORIZATION_REQUIRED` means *no result has come back*, which is
consistent with all of:

* The user never opened the payment page at all.
* The user opened it and abandoned it — closed the WebView, killed the app,
  walked away.
* The user is still on it, mid-authentication.
* The payment attempt has not yet been made.

So it is not a diagnosis, and specifically it is **not** evidence of a failure
you should report to the user. The correct handling is the same in every case:
leave it open, let the order reach its expiry, and take the result from the
webhook or Order Inquiry. Do not close it yourself on a timer — see
[Timeouts & When a Result Is Final](/docs/en/support/payment-timeout-and-finality-faq).

If it matters to you operationally to know whether the page was opened, that is a
question for your own front-end instrumentation, not for the order status.

## Which statuses are final, and can I store one as my outcome?

Two statuses are final: **`PAY_SUCCESS`** and **`ORDER_CLOSE`**. Everything else
is transitional, and an order in a transitional status can still move.

`PAY_SUCCESS` and `ORDER_CLOSE` do not flip into each other. Once an order is
closed, a later success report from the payment method does **not** reopen it and
does **not** rewrite it to `PAY_SUCCESS`; once it succeeded, it stays succeeded.
So yes — you can persist a final status as your business outcome.

The one thing that is *not* safe to persist is the assumption that a final status
means nothing further can happen to the **money**. A successful order can still be
refunded or disputed afterwards, and neither of those changes `orderStatus` — see
the refund question below.

## What can I do to an order in each status?

| Status | Cancel | Capture | Refund |
| - | - | - | - |
| Pre-status / no payment attempt yet | Yes | No | No |
| `PAY_IN_PROGRESS` | Yes | No | No |
| `AUTHORIZATION_REQUIRED` | Yes | No | No |
| `AUTHED_WAITING_CAPTURE` | Yes | **Yes** | No |
| `CAPTURE_IN_PROGRESS` | No | Yes (idempotent repeat) | No |
| `PAY_SUCCESS` | No — `A0013` | No | **Yes** |
| `ORDER_CLOSE` | Yes, as a no-op | No | No |

Read the two edges carefully, because they are where integrations get it wrong:

* **Refund is only possible from `PAY_SUCCESS`.** There is no "refund a pending
  order" — if the money has not landed, the action you want is cancel.
* **Cancel is the way to release an authorization you are not going to capture**,
  and it is materially better than authorizing, capturing and then refunding.

## I called cancel and it was refused. What do the errors mean?

| Situation | Code |
| - | - |
| The payment already succeeded | `A0013` Order cannot be cancelled due to payment already success |
| The payment method does not support cancellation | `A0015` Order cannot be cancelled due to payment channel not supported |
| The payment method refused the cancellation | `A0016` Order cannot be cancelled due to payment channel rejection |
| The order is in a status that cannot be cancelled | Order status is invalid to proceed |
| The order does not exist, or the id does not match | Order not found / id mismatch |
| Two cancel calls at once | A concurrency error — retry |

How to act on each:

* **`A0013` is not an error to retry.** The user paid. If you do not want to
  fulfil, the action is a refund, not a cancel.
* **`A0015` is a fact about the payment method**, not about your request. Some
  methods have no cancel operation at all; for those, the way an unpaid order
  ends is expiry. Design for "cancel may be unavailable" and let the expiry do
  the work.
* **`A0016` came from the payment method's side.** A retry can be worth one
  attempt, but if it repeats, stop and let the order expire.
* **Cancelling an already-closed order succeeds** and does nothing. That makes
  cancel safe to call from a cleanup job without checking the status first.

You may send either `paymentRequestId` or `acquiringOrderId` to identify the
order, plus your `merchantId`; sending neither id is a validation failure.

## After I cancel, can the user still pay on the payment method's page?

Cancel puts the order into `ORDER_CLOSE` — a final status — and asks the payment
method to cancel its side too. From that moment the order cannot complete: a
success report arriving afterwards is not applied, so the order will not silently
turn into a paid order behind your back.

What cancel cannot guarantee is that a **third-party payment page already open in
front of the user** stops accepting input. Where the payment method does not
support cancellation at all (`A0015`), nothing was cancelled on their side by
definition. So the honest answer to "can the user still pay?" is: *they may be
able to hand over money, and it will not become a completed order.* Money that
moves in that window is a recovery case, not a fulfilment case — the handling is
described in
[Timeouts & When a Result Is Final](/docs/en/support/payment-timeout-and-finality-faq).

Practical rule: cancel when you have decided not to sell, and do not treat cancel
as a way to close a checkout the user is still looking at.

## Is "set a short expiry, then cancel when it passes" a good design?

It is a reasonable design, with one correction and one caveat.

* **The correction:** you do not need the cancel call. An order that reaches its
  expiry without being paid is closed for you, and you are notified. Adding your
  own cancel afterwards is harmless — it is a no-op on a closed order — but it is
  not what makes the order end.
* **The caveat:** a short expiry does not shorten the payment method's own
  window, and it does not stop a late payment from being taken on their side. So
  a three-minute expiry buys you a three-minute *checkout*, not a three-minute
  guarantee that no money will move afterwards.

If your business needs a genuinely short window, set the expiry **and** build the
late-payment recovery path. If you only need the checkout to stop looking live,
the expiry alone is enough.

## Will an order close exactly at the expiry I set?

Close to it, but not to the second. Closing is performed by a periodic sweep, so
an order can remain technically open for a short interval after its nominal
expiry, and under high volume a batch of due orders can spill to the next pass.
Before closing, the payment method is checked, and an order with a payment
already in flight is left alone rather than being closed underneath the payment.

What this means for you:

* **Do not use "the order is closed" as a clock.** If your fulfilment logic needs
  a deadline, use your own timer against the expiry you sent; use the order status
  to learn the *outcome*, not the *time*.
* **Do not treat a slightly-late close as an incident.** It is the design.
* **An expiry in the past is not rejected — it is discarded.** If `orderExpiredAt`
  is already behind the clock when the order is created, the value is dropped and
  the order gets the payment method's default expiry instead. So a clock bug on
  your side does not fail loudly; it produces an order that stays open longer than
  you intended. Check the timestamp before you send it.

If you send no `orderExpiredAt` at all, the order still gets closed eventually,
but the time is not a value you set or should depend on. Send an explicit expiry
whenever the window matters to your business.

## Do I get a notification when an order is closed on timeout?

Yes. A closed order produces the ordinary payment notification —
`PAYMENT_NOTIFICATION`, carrying the same order structure as Order Inquiry, with
`orderStatus` = `ORDER_CLOSE` and the reason in `orderFailedReason`. There is no
separate "order expired" event type to subscribe to, and there is nothing extra
to enable.

So an integration that handles `PAYMENT_NOTIFICATION` and branches on
`orderStatus` already handles timeout closes. If you are not receiving them,
that is a webhook delivery question — see
[Portal webhook log](/docs/en/support/portal-webhook-log-faq) and
[Webhook retry](/docs/en/developer-docs/webhook/retry).

## Can I look up `orderFailedReason.orderFailedCode` in the error code table?

**No — it is a different set of codes.** The `Axxxx` codes are what an API call
returns to you synchronously when your *request* is wrong or refused. The code
inside `orderFailedReason` describes why an *order* ended the way it did, and it
is a separate, reason-oriented namespace. Looking one up in the other's table is
why merchants conclude the code "does not exist".

| You are holding | It is | Look it up in |
| - | - | - |
| The `code` / `msg` on an API response | An API error code | [Error codes](/docs/en/developer-docs/tools-and-references/developer-tools/error-codes), [API Reference Tables](/docs/en/support/api-reference-tables) |
| `orderFailedReason.orderFailedCode` on a closed or failed order | An order failure reason | The `orderFailedDescription` shipped alongside it, and Portal → Transactions → the order's Order Failure Description |

Can you automate on it? Partly, and carefully:

* **Do** branch on the broad distinction you can rely on — a timeout close is a
  different business event from a payment refusal, and both differ from a
  cancellation.
* **Do** always store `orderFailedDescription` next to the code, because it is
  the part that stays readable when a code you have never seen arrives.
* **Do not** build an exhaustive switch that treats an unrecognised reason as a
  failure of your own code. The set is larger than the handful you will see in
  testing, and new reasons appear as payment methods change. Default to
  "closed, not paid, reason recorded" and alert a human rather than guessing.

There are two distinct timeout reasons — one for an order that never reached a
payment method, one for an order that did — and
[Confirming Payment Results](/docs/en/support/payment-results-faq) explains the
practical difference.

## Can I capture less than I authorized?

Yes. A capture amount may be **anything greater than zero and up to the
authorized amount**; the remainder of the authorization is released rather than
held. Capturing more than was authorized is refused.

| What you send | What happens |
| - | - |
| Less than the authorized amount | Captured; the rest is released |
| Exactly the authorized amount | Captured |
| More than the authorized amount | Refused — capture is not supported for that amount |
| A second capture, same amount as the first | Idempotent: you get the original result back |
| A second capture, **different** amount | Refused as an idempotency mismatch (`A0011`) |
| A capture on an order that is not in an authorized state | Refused — the current status does not allow capture |
| A capture on an order that was never set to manual capture | Refused — capture is not supported on that order |

That last row is the one that surprises people. Capture only exists for orders
created **in manual-capture mode** — you opt in at order creation with
`paymentInfo.captureMode`, and the only value accepted there is `manualCapture`.
An ordinary order is captured automatically and there is nothing for you to call.

The published product behaviour matches the code on the point that matters most:
capture is effectively **single** — once you capture, even partially, the rest of
the authorization is released, so you cannot draw down an authorization in
instalments. For usage-based billing, the pattern is a stored token and periodic
charges, not repeated captures. See
[Auth-Capture](/docs/en/essentials/auth-capture) and
[Tokenization](/docs/en/support/tokenization-faq).

## How long do I have to capture?

The window is counted in whole days from **when the order was created**, not from
when you noticed the authorization. It is a single platform-wide setting, applied
to every merchant alike — it is not negotiated per account, and the wording of the
refusal message is not a reliable guide to the current figure. Confirm the window
with support before you design a fulfilment process around it.

After the window lapses two things happen, and you should build for both:

* **Your capture call starts failing.** It is refused as unsupported, not queued.
* **The authorization is released for you.** Expired authorizations are cancelled
  proactively, so the customer's funds do not stay held because you forgot.

So an authorization is not a promise you can hold indefinitely, and a capture is
not something to leave in a retry queue with no deadline. If your fulfilment can
take longer than your capture window, capture on shipment rather than on
delivery, or use a stored token and charge when you are ready.

## Why is `refundExpiryAt` empty on some of my orders?

`refundExpiryAt` is the point after which the order can no longer be refunded. It
is derived from the **refund window in your agreement for that payment method**,
counted from the order's **creation date** — not from the moment the payment
succeeded — and it lands at the end of that day.

Two consequences:

* A window expressed in days is a **calendar** window. An order created late in
  the day gets a slightly shorter usable window than one created early, because
  the boundary is the end of a day either way.
* The clock does not restart when the payment succeeds. An order paid a long time
  after it was created has already spent part of its refund window.

It is empty — `null` — in every case where the question does not apply:

| When it is null | Why |
| - | - |
| The order is not successful | There is nothing to refund |
| Refunds are not permitted for that agreement | There is no window to express |
| No refund window is configured for that agreement | Same |

So do not read a missing `refundExpiryAt` as "refundable forever". Read it as "no
deadline is being published for this order", check
[Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq)
for what applies, and use `remainingRefundAmount` on the refund APIs to decide
what is still refundable.

## Does a refund change the original order's status?

No — and this is the single most common false alarm on this endpoint. A refund
never rewrites `orderStatus`; a fully refunded order still reports
`PAY_SUCCESS`. Refund progress lives on the refund record and on the order's
separate refund-status field, not on the payment status.

If your bookkeeping keys refund state off `orderStatus`, it will look permanently
stuck. Key it off the refund record instead —
[Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq)
sets out where to look for each question.

***

Stuck on a status that will not move? [Contact support](mailto:support@waffo.com)
with the `acquiringOrderId` and the status you are seeing.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.