> ## 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.

# Bank Transfers & Virtual Accounts

> How virtual-account (VA) and bank-transfer payments behave: how long a VA number is valid and whether it can be reused later, what happens when the user types the wrong amount, and what a fiat overpayment or underpayment does to the order status.

Virtual-account (VA) and bank-transfer payments break two assumptions that hold
everywhere else: the user pays **outside** the checkout session, and the amount
that arrives is **whatever the user typed**. That combination produces the
questions on this page.

[Confirming Payment Results](/docs/en/support/payment-results-faq) explains the
two-stage result these methods report and why a long-pending order is normal.
[Timeouts & When a Result Is Final](/docs/en/support/payment-timeout-and-finality-faq)
covers the timeout side. This page is about the account number itself and about
amounts that do not match.

## What is a virtual account, and how does the flow differ from a redirect payment?

On a VA payment Waffo returns a **bank account number generated for that order**
instead of sending the user to a payment page. The user then transfers to that
number from their own banking app, ATM, or branch — entirely outside your
session and outside Waffo's.

Consequences you have to build for:

| Property | Effect on your integration |
| - | - |
| The user leaves and pays later | The order can stay pending for hours or days; that is not a symptom of failure |
| There are two results | Acceptance first (an account number was issued), payment result later |
| The bank collects the amount, not the checkout | The amount is not validated by a Waffo screen before it is sent |
| No redirect completes the payment | There is nothing for you to poll on the browser side; poll the order |

Related payment-method types on the checkout include `VA` (virtual account) and
the over-the-counter types, where the user pays in cash at a counter. The
customization parameters for showing only VA methods are documented in
[Checkout customization](/docs/en/developer-docs/integration/checkout/customization).

## How long is a VA number valid, and can I control it?

VA is one of the few method families where the expiry can actually be set on the
order rather than being fixed by the payment method. Where a VA method supports
it:

* The expiry window is capped at a small number of days; the exact range a row
  accepts is shown in the `Order Expiry Customization` column on **Service →
  Payin** (see [Payin Agreements & Transaction Limits](/docs/en/support/payin-agreements-and-limits-faq)).
* If you do not specify one, the method's default applies.

Every other method family generally uses its own default expiry, which you
cannot override; the published guidance on
[`orderExpiredAt`](/docs/en/developer-docs/tools-and-references/references/faq)
covers that. The Merchant Portal's PayIn page shows the expiry behaviour per
payment method.

## Is the VA number unique to my order? Could someone else's payment land on it?

Within the order's validity window the account number is **unique** — it belongs
to that order and nothing else.

After the order has expired, that guarantee ends. **Months later the same
account number can appear again**, issued for a different order. So:

* Never treat a VA number as a durable identifier for a customer, an
  order, or an invoice.
* Never store it and reconcile against it after the order has expired.
* Never show an expired VA number to a user again, and never let a user pay
  against a number you handed out for a previous order — reissue by creating a
  new order.

Match on your own order identifiers instead. The VA number is a payment
instruction with a short shelf life, not a key.

## The user typed the wrong amount, or used the number after it expired. Is my order dead?

No. Both cases are rejected **at the bank**, before anything reaches your order.

| What the user did | What they see | Effect on the order |
| - | - | - |
| Entered an amount that does not match | The bank's own error message, in the bank's interface | **None** — the order stays open |
| Used a number whose order has expired | The bank reports the account as expired or invalid | None on an already-expired order |

Because the bank validates the account number and the amount before accepting
the transfer, an incorrect attempt usually cannot even be submitted. **The order
status is not affected**, and the user can simply try again with the correct
amount for as long as the order is still valid.

The practical support answer: "your transfer was not taken; the order is still
open; transfer the exact amount shown before the deadline."

## The user paid more than the order amount. What happens?

On bank-transfer methods that report the amount actually received, an
overpayment produces a **successful order whose received amount is larger than
the amount you requested**. The result notification carries both figures, so the
difference is visible to you.

What that means:

* The order is payable and fulfillable — do not treat it as an exception that
  blocks delivery.
* The surplus is **not** something the payment method reverses on its own.
* Returning the surplus to the user is a **merchant action**. Depending on the
  method it may not be refundable through the API at all, in which case it is
  settled outside the payment.

Reconcile on the received amount, not on the amount you requested, or your
ledger will drift by exactly the surplus.

## The user paid less than the order amount. Can I still fulfil?

Only if they top it up in time. An underpayment is reported as a distinct
outcome — the notification says the amount received was insufficient and carries
the **shortfall** — and it resolves in one of two ways:

| What follows | Order outcome |
| - | - |
| The user transfers the shortfall before the deadline | The order becomes **successful** |
| The deadline passes with the amount still short | The order becomes **failed** |

So an underpaid order is not immediately failed, and it is not immediately
payable either. Hold fulfilment while it is short. When it ends up failed, the
amount already transferred has to be returned to the user — again a merchant
action, outside the original payment on methods that cannot refund a failed
bank transfer through the API.

Two rules that save the most trouble:

1. Never fulfil on the underpayment notification. Wait for the final state.
2. Never assume the shortfall notification is the end of the story — the user
   may still complete.

## Is this the same as stablecoin overpayment and underpayment?

The **shape** is the same, the **handling is not**. Both are cases of "the
amount that arrived is not the amount requested", and both exist because the
payer controls the amount. But the resolution paths differ enough that you
should not reuse one code path for both.

| | Bank transfer / VA (fiat) | Stablecoin |
| - | - | - |
| Overpayment | Order succeeds; received amount exceeds requested; surplus returned by the merchant | Order succeeds; the surplus is handled per the documented case classification |
| Underpayment | Succeeds if topped up before the deadline, otherwise fails | Handled per the documented case classification |
| Wrong destination | Not possible — the bank rejects an invalid or expired account | Possible: wrong chain, wrong token, or an old address |
| Return of funds | Merchant action, frequently outside the payment method | Refund requires a `refundAddress` and is chain-locked |

The stablecoin rules are in
[Stablecoin Exceptions & Refunds](/docs/en/support/stablecoin-exceptions-faq). Read
that page rather than inferring from this one, and vice versa.

## Can I refund a bank-transfer order through the API?

Not always — and this is worth checking before you accept the method rather than
after your first return request. Several bank-transfer payment methods are **not
refundable through the refund API at all**: the money did not come in over a
reversible rail, so there is nothing to reverse. Where the API can refund, some
methods need additional user details (bank account information) because the
money is returned by transfer rather than reversed on the original payment.

| Situation | What happens |
| - | - |
| The method cannot be refunded through the API | The refund request is rejected with `A0017` |
| The method can be refunded but needs more fields | The request is rejected with `A0003` and the missing field names are listed; resend with them filled in |
| The method can be refunded normally | The refund behaves like any other |

Do not decide which case you are in from the payment method's name in your own
code — required fields are configuration, not a property of the method you can
infer. Submit the refund and branch on the response. The full rules are in
[Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq)
and [Refund FAQ](/docs/en/support/refund-faq).

## Do over-the-counter payments work the same way?

Broadly yes — the user pays outside the session, the result arrives in two
stages, and a long pending period is normal. The difference is what you hand the
user: instead of an account number, over-the-counter methods return a **payment
code** the user presents at a counter, and a set of instructions describing where
and how to pay.

Two practical points:

* The instructions are text and a code, not an image or a link you can rely on
  rendering. Put them somewhere durable — an email is the usual answer, because
  the user will pay hours or days later.
* The order behaves like a VA order for everything on this page: do not cancel
  early, do not treat elapsed time as failure, wait for the final notification.

## Should I cancel a bank-transfer order that has been pending for hours?

No. For VA, bank-transfer and over-the-counter payments a long pending period is
the normal case, because the user pays on their own schedule. Cancelling early
creates the worst failure mode there is: the order is closed on your side while
the money is still on its way.

Wait for the final notification, or poll `/api/v1/order/inquiry`. See
[Confirming Payment Results](/docs/en/support/payment-results-faq) for the
long-pending case and
[Timeouts & When a Result Is Final](/docs/en/support/payment-timeout-and-finality-faq)
for what a timeout does and does not tell you.

## What should I show the user after creating a VA or over-the-counter order?

Everything they need to complete the transfer, and nothing that expires
silently:

1. The account number or payment code exactly as returned, in a form the user
   can copy.
2. The **exact** amount, including currency and decimals — an amount mismatch
   is rejected by the bank, so precision is not cosmetic.
3. The deadline, in the user's own terms ("pay before …"), not as a raw
   timestamp.
4. A note that the payment is confirmed after the transfer is received, not
   immediately.

Send the same details by email or another durable channel as well. The user will
close your page and pay later — that is the whole point of the method — and a
payment instruction that exists only on a page they navigated away from becomes
a support ticket.

## Where do I look next?

| Question | Page |
| - | - |
| The two-stage result, missing webhooks, long-pending orders | [Confirming Payment Results](/docs/en/support/payment-results-faq) |
| Whether a timeout means the payment failed | [Timeouts & When a Result Is Final](/docs/en/support/payment-timeout-and-finality-faq) |
| Whether a bank-transfer order can be refunded through the API | [Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq) |
| The stablecoin version of over- and underpayment | [Stablecoin Exceptions & Refunds](/docs/en/support/stablecoin-exceptions-faq) |
| Restricting the checkout to VA methods | [Checkout customization](/docs/en/developer-docs/integration/checkout/customization) |


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