Payments
A payment represents one attempt to collect money from a customer. You create it on your server,
attach a payment method, confirm it, and then follow its status until it reaches a final value.
See
Payment lifecycle for what each status means and
Redirects and customer actions for payments that need the
customer to do something.
You can run a payment in two ways:
- Two steps: create the payment with
confirm: false, then confirm it later with Confirm a payment, from your server or from your front end using the publishable key and the payment’sclient_secret. - One step: create the payment with
confirm: trueand the payment method in the same call. See Create and confirm a payment in one call.
All amounts are integers in the currency’s smallest unit. VND has no smaller unit, so
"amount": 150000 with "currency": "VND" is 150,000 VND. See
Amounts and currencies.
Payment object fields
Every payment operation returns the same payment object. These are the fields you will use most.
| Field | Type | Description |
|---|---|---|
payment_id | string | VNSwitch’s ID for the payment. Store it with your order. |
status | string | Where the payment is in its lifecycle, for example requires_payment_method, requires_customer_action, requires_capture, succeeded or failed. See
Payment lifecycle. |
amount | integer | The amount to collect, in the currency’s smallest unit. |
amount_capturable | integer | The amount you can still capture. Relevant when capture_method is manual. |
amount_received | integer | The amount captured so far. |
currency | string | Three-letter ISO 4217 code, for example VND. |
customer_id | string | The customer this payment belongs to. |
capture_method | string | automatic or manual. See
Capture a payment. |
payment_method | string | The payment method group, for example card, wallet, bank_transfer or real_time_payment. |
payment_method_type | string | The specific method within the group, for example credit, debit or viet_qr. |
payment_method_data | object | Non-sensitive details of the method used. For cards: last4, expiry, holder name and network. Full card numbers are never returned. |
client_secret | string | A secret for this one payment. Pass it to your front end so it can confirm or retrieve the payment with the publishable key. Never log it or put it in a URL you share. |
next_action | object | Present when status is requires_customer_action. Tells you what the customer must do, such as follow a redirect or scan a QR code. See
Redirects and customer actions. |
return_url | string | Where VNSwitch sends the customer after a redirect. |
description | string | Your free-text description. |
metadata | object | Your own key-value data, returned unchanged. |
setup_future_usage | string | on_session or off_session. Whether the payment method was saved for later use. |
mandate_id | string | Set when the payment created or used a mandate. See Mandates. |
error_code, error_message | string | Why the latest attempt failed, as reported by the payment provider. |
unified_code, unified_message | string | The same failure in a provider-independent form. Prefer these for your own logic. |
manual_retry_allowed | boolean | true if you can confirm the payment again after a failure. |
refunds | array | Refunds made on this payment. See Refunds. |
attempt_count | integer | How many attempts were made to process the payment. |
created | string | When the payment was created (ISO 8601). |
A confirmed card payment
{
"payment_id": "pay_8f2c1d9e0a",
"merchant_id": "merchant_demo",
"status": "succeeded",
"amount": 150000,
"net_amount": 150000,
"amount_capturable": 0,
"amount_received": 150000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "automatic",
"payment_method": "card",
"payment_method_type": "credit",
"payment_method_data": {
"card": {
"last4": "4242",
"card_exp_month": "12",
"card_exp_year": "2030",
"card_holder_name": "NGUYEN VAN AN",
"card_network": "Visa"
}
},
"client_secret": "pay_8f2c1d9e0a_secret_Xk2pQ9rTz4",
"description": "Order #1001",
"return_url": "https://merchant.example.vn/checkout/return",
"metadata": { "order_id": "1001" },
"next_action": null,
"error_code": null,
"error_message": null,
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}Create a payment
Creates a payment without charging the customer yet. Use this when your front end collects the payment method and confirms the payment later.
POST /payments
Authentication: secret API key (api-key header).
With confirm: false (the default), the payment is returned with status
requires_payment_method, or requires_confirmation if you already attached a payment method.
Nothing is sent to a payment provider until you
confirm the payment.
Pass the returned client_secret to your front end if it will confirm the payment; keep your
secret API key on your server.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | integer | Yes | Amount in the currency’s smallest unit. For VND, the number of dong: 150000 is 150,000 VND. |
currency | string | Yes | Three-letter ISO 4217 code, for example VND. |
confirm | boolean | No | true confirms the payment in the same call; you must then include the payment method. Default false. |
capture_method | string | No | automatic (default) captures the funds as soon as the payment is authorised. manual only authorises them; you capture later with
Capture a payment. |
customer_id | string | No | Your customer’s ID, for example cus_demo_001. Required if you save the payment method. See
Customers. |
customer | object | No | Customer details: id, name, email, phone, phone_country_code. Creates the customer if it does not exist, or attaches an existing one. If you also send customer_id, it must match customer.id. |
payment_method | string | No | Payment method group: card, wallet, bank_redirect, bank_transfer, real_time_payment and others. |
payment_method_type | string | No | Specific method, for example credit, debit or viet_qr. |
payment_method_data | object | No | The payment method details, keyed by group. For a card: {"card": {"card_number", "card_exp_month", "card_exp_year", "card_holder_name", "card_cvc"}}. |
return_url | string | No | Where VNSwitch sends the customer after a redirect, for example 3-D Secure or a bank page. If omitted, the return URL set on your business profile is used. See Redirects and customer actions. |
description | string | No | Free text for your records or for display to the customer. |
metadata | object | No | Your own key-value data, up to 50 keys. Key names up to 40 characters, values up to 500 characters. Do not put card or personal data here. |
setup_future_usage | string | No | on_session or off_session. Saves the payment method to the customer for later payments. Use off_session if you will charge it while the customer is not present. See
Saved payment methods and recurring payments. |
authentication_type | string | No | three_ds or no_three_ds. Whether to ask for 3-D Secure on card payments. |
merchant_order_reference_id | string | No | Your order reference, for reconciliation. |
billing | object | No | Billing address and phone. |
session_expiry | integer | No | How long the client_secret stays valid, in seconds. Default 900 (15 minutes). |
Example request
Response
{
"payment_id": "pay_8f2c1d9e0a",
"merchant_id": "merchant_demo",
"status": "requires_payment_method",
"amount": 150000,
"net_amount": 150000,
"amount_capturable": 150000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "automatic",
"client_secret": "pay_8f2c1d9e0a_secret_Xk2pQ9rTz4",
"description": "Order #1001",
"return_url": "https://merchant.example.vn/checkout/return",
"metadata": { "order_id": "1001" },
"next_action": null,
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z",
"expires_on": "2026-11-01T09:45:00Z"
}Response
The payment object. Store payment_id with your order and send client_secret to your front end
if it will confirm the payment. See
Payment object fields.
Create and confirm a payment in one call
Creates the payment and sends it to the payment provider straight away. Use this when your server already has everything it needs, for example a recurring charge or a server-to-server integration.
POST /payments
Authentication: secret API key (api-key header).
Set confirm: true and include the payment method (payment_method, payment_method_type and
payment_method_data, or a saved method). The body parameters are the same as for
Create a payment.
Sending raw card numbers from your server means your systems handle card data
and fall under PCI DSS requirements. If that is not what you want, collect card details on the front
end and confirm with the publishable key and client_secret instead.
Example request
Response
{
"payment_id": "pay_8f2c1d9e0a",
"status": "succeeded",
"amount": 150000,
"amount_capturable": 0,
"amount_received": 150000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "automatic",
"payment_method": "card",
"payment_method_type": "credit",
"payment_method_data": {
"card": {
"last4": "4242",
"card_exp_month": "12",
"card_exp_year": "2030",
"card_holder_name": "NGUYEN VAN AN",
"card_network": "Visa"
}
},
"client_secret": "pay_8f2c1d9e0a_secret_Xk2pQ9rTz4",
"return_url": "https://merchant.example.vn/checkout/return",
"next_action": null,
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}Response
The payment object. Check status:
succeeded: the money is collected (withcapture_method: automatic).requires_capture: the amount is authorised and waiting for you to capture it (withcapture_method: manual).requires_customer_action: the customer must complete a step such as 3-D Secure. Readnext_actionand follow Redirects and customer actions.processing: the provider has not answered yet. Wait for a webhook or retrieve the payment later.failed: seeerror_code,error_message,unified_codeandunified_message.
Update a payment
Changes a payment that has not been confirmed yet, for example when the customer edits the cart before paying.
POST /payments/{payment_id}
Authentication: secret API key (api-key header). From a front end, use the publishable key
and include the payment’s client_secret in the body.
You can update a payment only while its status is requires_payment_method,
requires_confirmation or requires_customer_action. Send only the fields you want to change.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_id | string | Yes | The payment to update. |
Body parameters
The update body accepts the same fields as Create a payment, all optional. The ones you will use most:
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | integer | No | New amount in the currency’s smallest unit. |
currency | string | No | Three-letter ISO 4217 code. Send it with amount so the amount is never read in the wrong currency. |
customer_id | string | No | Attach the payment to a customer. |
description | string | No | New description. |
metadata | object | No | New key-value data. |
return_url | string | No | New return URL. |
capture_method | string | No | automatic or manual. |
payment_method, payment_method_type, payment_method_data | — | No | Attach or change the payment method. |
client_secret | string | No | Required when you call with the publishable key. |
Example request
Response
{
"payment_id": "pay_8f2c1d9e0a",
"status": "requires_payment_method",
"amount": 180000,
"amount_capturable": 180000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "automatic",
"client_secret": "pay_8f2c1d9e0a_secret_Xk2pQ9rTz4",
"description": "Order #1001 (updated)",
"metadata": { "order_id": "1001", "coupon": "none" },
"next_action": null,
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}Response
The updated payment object.
Retrieve a payment
Returns the current state of a payment. Use it to confirm the outcome on your server after a redirect, or when a webhook is late.
GET /payments/{payment_id}
Authentication: secret API key (api-key header). From a front end, use the publishable key
with the client_secret query parameter.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_id | string | Yes | The payment to retrieve. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
force_sync | boolean | No | true asks the payment provider for the latest status before answering, instead of returning what VNSwitch already knows. Use it when the payment is not in a final status, for example after the customer returns from a redirect. Default false. |
client_secret | string | No | The payment’s client_secret. Required when you call with the publishable key. |
expand_attempts | boolean | No | true adds the list of processing attempts (attempts). |
expand_captures | boolean | No | true adds the list of captures on the latest attempt (captures). |
Example request
Response
{
"payment_id": "pay_8f2c1d9e0a",
"status": "succeeded",
"amount": 150000,
"amount_capturable": 0,
"amount_received": 150000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "automatic",
"payment_method": "card",
"payment_method_type": "credit",
"client_secret": "pay_8f2c1d9e0a_secret_Xk2pQ9rTz4",
"next_action": null,
"refunds": null,
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}Response
The payment object, including refunds and disputes made on it. See
Payment object fields.
Confirm a payment
Sends a created payment to the payment provider for authorisation, with the payment method you attach now or attached earlier.
POST /payments/{payment_id}/confirm
Authentication: secret API key (api-key header). From a front end, use the publishable key
and include the payment’s client_secret in the body.
You can confirm a payment in status requires_payment_method or requires_confirmation. If an
earlier attempt failed and the payment shows manual_retry_allowed: true, you can confirm it
again with the same or another payment method. You cannot confirm a payment that is
requires_customer_action; finish the customer step instead (see
Redirects and customer actions).
The example response shows a card that needs 3-D Secure: send the customer to
next_action.redirect_to_url. Cards without 3-D Secure return succeeded (or requires_capture
with manual capture) directly.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_id | string | Yes | The payment to confirm. |
Body parameters
The confirm body accepts the same fields as Create a payment, all optional. The ones you will use most:
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_method | string | No | Payment method group, for example card. Required if no method is attached yet. |
payment_method_type | string | No | Specific method, for example credit. |
payment_method_data | object | No | The payment method details. Required if no method or saved token is attached yet. |
payment_token | string | No | A saved payment method’s token, used instead of payment_method_data. See
Payment methods. |
return_url | string | No | Where to send the customer after a redirect. |
setup_future_usage | string | No | on_session or off_session to save the payment method. |
customer_acceptance | object | No | Proof that the customer agreed to save the method or to a mandate. Send it when you save a method for off_session use. See
Saved payment methods and recurring payments. |
browser_info | object | No | The customer’s browser details. Some providers need them for 3-D Secure. |
client_secret | string | No | Required when you call with the publishable key. |
Example request
Response
{
"payment_id": "pay_8f2c1d9e0a",
"status": "requires_customer_action",
"amount": 150000,
"amount_capturable": 150000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "automatic",
"payment_method": "card",
"payment_method_type": "credit",
"client_secret": "pay_8f2c1d9e0a_secret_Xk2pQ9rTz4",
"return_url": "https://merchant.example.vn/checkout/return",
"next_action": {
"type": "redirect_to_url",
"redirect_to_url": "https://sandbox.api.vnswitch.nivc.ai/payments/redirect/pay_8f2c1d9e0a/merchant_demo/pay_8f2c1d9e0a_1"
},
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}Response
The payment object. Handle status as described in
Create and confirm a payment in one call.
Capture a payment
Collects money that was authorised earlier. Use it with payments created with
capture_method: "manual", for example to charge only when you ship the order.
POST /payments/{payment_id}/capture
Authentication: secret API key (api-key header).
A manual-capture payment stops at requires_capture once it is authorised. Capture it before
the authorisation expires; the provider decides how long that is, and capture_before shows the
deadline when the provider gives one. If you do not want the money any more, release the
authorisation with
Cancel a payment.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_id | string | Yes | The payment to capture. Its status must be requires_capture (or partially_captured_and_capturable / partially_authorized_and_requires_capture). |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount_to_capture | integer | No | How much to capture, in the currency’s smallest unit. Must not exceed amount_capturable. If omitted, the full amount_capturable is captured. |
statement_descriptor_suffix | string | No | Text added to the descriptor on the customer’s card statement, if the provider supports it. |
Example request
Response
{
"payment_id": "pay_8f2c1d9e0a",
"status": "succeeded",
"amount": 150000,
"amount_capturable": 0,
"amount_received": 150000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "manual",
"payment_method": "card",
"payment_method_type": "credit",
"next_action": null,
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}Response
The payment object. status is succeeded after a full capture. After a partial capture with
capture_method: manual it is partially_captured, and the rest of the authorisation can no
longer be captured. amount_received shows how much was captured.
Cancel a payment
Cancels a payment that has not collected money, or releases an authorisation you no longer want to capture.
POST /payments/{payment_id}/cancel
Authentication: secret API key (api-key header).
You can cancel a payment in requires_payment_method, requires_confirmation,
requires_customer_action, requires_capture or partially_authorized_and_requires_capture. You cannot
cancel a payment that is succeeded, failed, cancelled, processing,
requires_merchant_action or review. To give money back after a successful payment, create a
refund instead (see
Refunds).
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_id | string | Yes | The payment to cancel. |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
cancellation_reason | string | No | Free-text reason, returned in cancellation_reason. |
Example request
Response
{
"payment_id": "pay_8f2c1d9e0a",
"status": "cancelled",
"amount": 150000,
"amount_capturable": 0,
"amount_received": null,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "manual",
"cancellation_reason": "requested_by_customer",
"next_action": null,
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}Response
The payment object with status: "cancelled".
List payments
Returns your payments, newest first, optionally filtered by customer and creation time. Use it for reconciliation and back-office screens.
GET /payments/list
Authentication: secret API key (api-key header).
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | No | Only payments of this customer. |
limit | integer | No | Number of payments to return, 1 to 100. Default 10. |
starting_after | string | No | A payment_id. Returns the page of payments after this one. |
ending_before | string | No | A payment_id. Returns the page of payments before this one. |
created | string | No | Only payments created at exactly this time (ISO 8601, for example 2026-11-01T09:30:00Z). |
created.lt | string | No | Only payments created before this time. |
created.gt | string | No | Only payments created after this time. |
created.lte | string | No | Only payments created at or before this time. |
created.gte | string | No | Only payments created at or after this time. |
The time filters are written with a dot: created.gte, not
created_gte. Unknown query parameters are rejected with an error.
To page through results, take the payment_id of the last item in data and pass it as
starting_after in the next request. Stop when size is smaller than limit.
Example request
Response
{
"size": 1,
"data": [
{
"payment_id": "pay_8f2c1d9e0a",
"status": "succeeded",
"amount": 150000,
"amount_received": 150000,
"currency": "VND",
"customer_id": "cus_demo_001",
"capture_method": "automatic",
"payment_method": "card",
"payment_method_type": "credit",
"description": "Order #1001",
"attempt_count": 1,
"created": "2026-11-01T09:30:00Z"
}
]
}Response
An object with size (the number of payments in this page) and data (an array of payment
objects, see
Payment object fields).