An order VA is a virtual account number dedicated to a single order. The customer transfers to that VA, SePay matches the transaction to the order by its exact amount and sends a webhook to your system.
The Sandbox gives you a simulated account to run the whole flow: create an order, pay it, receive the webhook.
Supported Banks
BIDV, Sacombank, Vietcombank and VietinBank accounts linked to your SePay company all create order VAs through the same endpoint, but differ in the account type, the required parameters and how they take the money. Read the section for the bank you are integrating.
BIDV
| Account type | Enterprise |
Amount (amount) | Optional |
Order code (order_code) | 6-50 chars, ^[a-zA-Z0-9]+$ (letters and digits only, no spaces or special characters) |
| Partial payment | Yes |
Omit amount and the VA takes any sum. BIDV is the only bank that supports this kind of order.
When the customer pays less than amount, the order moves to Partially and the VA keeps accepting payments until the total is reached.
A BIDV enterprise account can also set its own VA holder name through va_holder_name, but the feature has to be enabled first. Contact SePay if you need it.
Sacombank
| Account type | Personal or household business |
VA prefix (va_prefix) | Required |
Amount (amount) | Required |
Order code (order_code) | 6-50 chars, ^[a-zA-Z0-9]+$ (letters and digits only, no spaces or special characters) |
| Partial payment | No |
Every Sacombank order must be tied to a VA prefix. Call VA Prefixes to fetch an active va_prefix before creating the order. The merchant must be activated, otherwise the call returns 400.
Orders only transition from Pending to Paid or Cancelled; there is no Partially state.
Vietcombank
| Account type | Enterprise or household business |
Terminal ID (tid) | Required |
Amount (amount) | Required |
Order code (order_code) | 6-15 chars, ^[a-zA-Z0-9]+$ (letters and digits only, no spaces or special characters) |
| Partial payment | No |
Every order needs a tid
Every Vietcombank order must be tied to a specific terminal via the tid parameter. If you don't have a terminal yet, see how to add a terminal for a Vietcombank enterprise/household business account. Call List Terminals to fetch a valid tid before creating an order. tid must be the raw Vietcombank-issued Terminal ID (e.g. 20933557), not the SePay xid UUID.
Exact amount only
Orders only transition from Pending to Paid or Cancelled; there is no Partially state. Note also that order_code is capped at 15 characters, shorter than the other banks.
VietinBank
| Account type | Enterprise |
Amount (amount) | Required |
Order code (order_code) | 6-50 chars, ^[a-zA-Z0-9]+$ (letters and digits only, no spaces or special characters) |
| Partial payment | Yes (overpayment rejected) |
| Refunds | Yes |
A VietinBank enterprise account must be registered for identified-account collection with VietinBank before it can create orders. An unregistered account returns 422 vietinbank_va_order_not_enabled. If you hit this error, contact SePay for support.
A VietinBank order always carries an amount, but a customer paying less is still recorded: the order moves to Partially and the VA keeps accepting until the total is reached. A VA that has expired, is already paid, or was cancelled takes no further money.
A transfer above the VA amount is not matched: the money still lands in the real account, but the order does not move to Paid. Collecting in instalments on VietinBank means each instalment must be at or below the VA amount.
What VietinBank collected can be refunded through the API.
Statuses
An order also carries its own refund_status field, null until the first refund is issued. It only takes another value on a bank that supports refunds.
Unlike status, this field is recomputed from all of the order's refunds, so it can walk back. See what each value means.
Endpoints
https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/ordershttps://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/ordershttps://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/orders/{order_xid}https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/orders/{order_xid}https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/orders/{order_xid}/vahttps://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/orders/{order_xid}/va/{va_number}https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/prefixeshttps://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/prefixes/{va_prefix}https://userapi.sepay.vn/v2/bank-accounts/{ba_xid}/terminalsPayment Verification Flow for Order VAs
order_code is the thread tying your own order to the order on SePay. Your website generates the code when the customer checks out, passes it into the create-order call, and the webhook returns it in the code field so you can reconcile. Omit it and SePay generates one, but then you have to store the returned code yourself to match back.
SePay normalises order_code to upper case before storing it. Sending dh20250001 is accepted, but the order is stored as DH20250001, and that is what the response and the webhook return. If your system uses lower-case codes, compare them case-insensitively, or store the value from the response rather than the one you sent.
Refund Flow
Once an order is Paid or Partially, you send what it collected back through the Refunds API. Only VietinBank enterprise accounts that SePay has enabled for refunds can use it today.
A refund does not change the order's status or paid_amount; the order carries a separate refund_status field instead. See Refund an order for the parameters, the state diagram and the error table.
Integration Steps
Prerequisites
- Create an API Token
- A bank account linked on SePay:
- BIDV: enterprise account
- Sacombank: personal or business account, merchant activated
- Vietcombank: enterprise or household business account with at least one terminal (see how to add a terminal, or fetch the list via List Terminals)
- VietinBank: enterprise account registered for identified-account collection with VietinBank
- Get the account UUID from Bank Accounts API
Fetch VA Prefixes (Sacombank) or Terminals (Vietcombank)
Create Order
curl -X POST "https://userapi.sepay.vn/v2/bank-accounts/{ba_uuid}/orders" \-H "Content-Type: application/json" \-H "Authorization: Bearer YOUR_API_TOKEN" \-d '{"amount": 500000, "order_code": "DH20250001", "with_qrcode": "1"}'
{"status": "success","message": "Order created successfully","data": {"id": "b2c3d4e5-f6a7-8901-bcde-f12345678902","order_code": "DH20250001","va_number": "963NQDORD1234567890AB","va_holder_name": "CONG TY CP TECH VINA","amount": 500000,"status": "Pending","bank_name": "BIDV","account_holder_name": "CONG TY CP TECH VINA","account_number": "1234567890","expired_at": null,"qr_code": "data:image/png;base64,...","qr_code_url": "https://vietqr.app/img?acc=963NQDORD1234567890AB&bank=BIDV&amount=500000&template=compact"}}
Display to Customer
From the response, show the customer:
- Account number:
va_number(VA number to transfer to) - Amount:
amount - QR code:
qr_codeorqr_code_url - Expiry:
expired_at(if set)
Receive Payment Notification
When the customer transfers successfully, SePay sends a webhook to your configured URL. The transaction will contain a code field matching the order's order_code.
See webhook configuration for details.
Check Order Status
curl -X GET "https://userapi.sepay.vn/v2/bank-accounts/{ba_uuid}/orders/{order_uuid}" \-H "Authorization: Bearer YOUR_API_TOKEN"
Order statuses:
Pending: awaiting paymentPaid: fully paidPartially: partially paid (BIDV and VietinBank)Cancelled: cancelled
Key Parameters
| Parameter | Required | Description |
|---|---|---|
va_prefix | Yes (Sacombank) | VA prefix. Not used for BIDV, Vietcombank or VietinBank. |
tid | Yes (Vietcombank) | Raw Vietcombank-issued Terminal ID. Not used for BIDV, Sacombank or VietinBank. |
order_code | No | Order code, 6-50 chars (Vietcombank max 15), matching ^[a-zA-Z0-9]+$, meaning letters and digits only. SePay normalises it to upper case. Auto-generated if omitted, as 10 upper-case characters. |
amount | Yes (Sacombank, Vietcombank, VietinBank) | Amount (VND). Only BIDV allows it to be optional. |
Partial Payments
A partial payment is a transfer smaller than the order's amount that SePay still records, instead of matching only an exact-amount transfer. The order collects gradually across several transfers until the total is reached.
A first transfer below amount moves the order to Partially, and the paid_amount field holds the running total. The VA keeps accepting, each transfer adding to paid_amount. Once paid_amount reaches amount, the order becomes Paid. You can also create an extra VA on an order in Pending or Partially to collect the remainder.
Compared to a full payment
| Full payment | Partial payment | |
|---|---|---|
| Amount the customer transfers | Exactly amount | Less than amount |
| Order status after the first transfer | Paid | Partially |
| VA after the first transfer | Moves to Paid, stops accepting | Keeps accepting until amount is reached |
| Supported banks | BIDV, Sacombank, Vietcombank, VietinBank | BIDV, VietinBank |
Bank support
BIDV and VietinBank record underpayments: the order sits at Partially until the full amount is collected.
VietinBank adds one constraint in the other direction: a transfer larger than the VA amount is not matched. The money still lands in the real account, but the order does not move. So when collecting in instalments on VietinBank, each instalment must be at or below the VA amount.
To collect the remainder with an extra VA, work the figure out yourself and pass amount. Omitting amount gives the VA the parent order's amount, not the outstanding remainder, on BIDV too. On a BIDV open order (amount null) an extra VA defaults to an amount of 0.
Sacombank and Vietcombank do not support Partially: their orders move straight from Pending to Paid on payment. They can still take an extra VA on a Pending order, but that VA is a parallel payment channel for the same amount, not a way to collect a remainder.
An open order omits amount, so there is no figure to measure against: the VA takes any sum and no remainder ever arises. A partial payment is the opposite: amount is fixed up front and the order tracks paid_amount until it is met.
Cancel Order or VA
Only Pending orders and Unpaid VAs can be cancelled. Response: 204 No Content.