Some payments cannot finish with a single API call. The customer has to do something first: pass a 3-D Secure check, approve the payment in a banking or wallet app, or scan a QR code. When that happens, VNSwitch returns the payment with status: "requires_customer_action" and a next_action object that tells you what to show.

The pattern is always the same:

  1. Confirm the payment (or create it with confirm: true). Include a return_url.
  2. If status is requires_customer_action, read next_action.type and act on it.
  3. The customer completes the step and, for redirects, comes back to your return_url.
  4. Your server checks the final status with VNSwitch before fulfilling the order.

Read the next action

next_action is only present while the payment is requires_customer_action. Branch on next_action.type:

typeWhat to doFields
redirect_to_urlSend the customer’s browser to the URL (full-page redirect, not an API call). Used for 3-D Secure and most bank and wallet pages.redirect_to_url
redirect_inside_popupOpen the URL in a pop-up window.popup_url, redirect_response_url
qr_code_informationShow the QR code for the customer to scan with their banking or wallet app.image_data_url (a ready-to-use image), qr_code_url, raw_qr_data, display_text, display_to_timestamp
fetch_qr_code_informationFetch the QR code from the given URL and show it.qr_code_fetch_url
display_bank_transfer_informationShow the bank account details the customer must transfer to.bank_transfer_steps_and_charges_details
display_voucher_informationShow the voucher or payment slip the customer must pay.voucher_details
wait_screen_informationShow a “waiting for approval” screen while the customer approves in another app.display_from_timestamp, display_to_timestamp
collect_otpAsk the customer for a one-time password.consent_data_required

A few other type values (third_party_sdk_session_token, invoke_sdk_client, three_ds_invoke, invoke_hidden_iframe, invoke_ddc, invoke_upi_intent_sdk, invoke_upi_qr_flow) are used only by specific payment providers. If your account uses one of them, VNSwitch onboarding will tell you. If you receive a type you do not handle, show a generic “could not complete payment” message and check the payment later; do not mark it as paid.

next_action for a redirect (3-D Secure, bank or wallet page)

{
  "payment_id": "pay_8f2c1d9e0a",
  "status": "requires_customer_action",
  "next_action": {
    "type": "redirect_to_url",
    "redirect_to_url": "https://sandbox.api.vnswitch.nivc.ai/payments/redirect/pay_8f2c1d9e0a/merchant_demo/pay_8f2c1d9e0a_1"
  }
}

next_action for a QR code

{
  "payment_id": "pay_8f2c1d9e0a",
  "status": "requires_customer_action",
  "next_action": {
    "type": "qr_code_information",
    "image_data_url": "data:image/png;base64,iVBORw0KGgo...",
    "qr_code_url": "https://provider.example/qr/8f2c1d9e0a"
  }
}

Handle a redirect

When next_action.type is redirect_to_url, redirect the customer’s browser to next_action.redirect_to_url, for example with an HTTP 302 from your server or window.location.assign(url) in the browser. Do not fetch the URL from your server: the customer has to see the page.

The customer then completes the step on the bank’s or provider’s page. VNSwitch receives the result and sends the customer back to your return_url.

Set return_url on every payment that might need a redirect. If you leave it out, VNSwitch uses the return URL set on your business profile. If neither is set, the customer cannot be sent back.

Customer returns to your return_url

VNSwitch adds these query parameters to your return_url when it sends the customer back:

ParameterDescription
payment_idThe payment the customer just completed.
statusThe payment status at that moment, for example succeeded, requires_capture, processing or failed.
payment_intent_client_secretThe payment’s client_secret.
amountThe payment amount in the currency’s smallest unit.
manual_retry_allowedtrue if you can confirm the payment again after a failure.

For example:

If your business profile has the payment response hash enabled, VNSwitch also adds signature and signature_algorithm (HMAC-SHA512), signed with your payment response hash key. Your business profile can also be set to send these values to your return_url as an HTTP POST instead of query parameters; ask VNSwitch onboarding if you need that.

Treat the status in the URL as a hint for what to show the customer, never as proof of payment. Anyone can edit a URL. Also avoid writing the full URL to logs or analytics, because it contains the client_secret.

https://merchant.example.vn/checkout/return?status=succeeded&payment_id=pay_8f2c1d9e0a&payment_intent_client_secret=pay_8f2c1d9e0a_secret_Xk2pQ9rTz4&amount=150000&manual_retry_allowed=false

Verify the payment after the customer returns

When the customer lands on your return_url, take payment_id from the query string and retrieve the payment from your server with your secret key. Add force_sync=true so VNSwitch asks the provider for the latest status. Do not call Confirm a payment again: the payment is already confirmed, and VNSwitch rejects a second confirm while it waits for the customer.

Then act on the retrieved status:

  • succeeded (or requires_capture with manual capture): check that amount and currency match your order, then fulfil it.
  • processing: show “payment pending”. Wait for the webhook (see Webhooks).
  • failed: show an error. If manual_retry_allowed is true, let the customer try again with Confirm a payment.
  • requires_customer_action: the customer did not finish. Let them try again with the same next_action, or cancel the payment.

The customer may also close the browser and never come back. Always rely on webhooks as well, so your order is updated even without a return. See Payment lifecycle.

Response

{
  "payment_id": "pay_8f2c1d9e0a",
  "status": "succeeded",
  "amount": 150000,
  "amount_received": 150000,
  "currency": "VND",
  "customer_id": "cus_demo_001",
  "payment_method": "card",
  "payment_method_type": "credit",
  "next_action": null
}

QR payments in Vietnam

Availability depends on the payment providers enabled for your account. Contact info@nivc.ai.

QR methods such as VietQR do not use a browser redirect in most cases. The customer scans a code with their banking app and approves the transfer there. To start a VietQR payment, use payment_method: "real_time_payment", payment_method_type: "viet_qr" and payment_method_data: {"real_time_payment": {"viet_qr": {}}}:

Depending on the provider, next_action is:

  • qr_code_information: show image_data_url as an image (<img src="...">). Hide the code after display_to_timestamp if it is set.
  • fetch_qr_code_information: get the QR code from qr_code_fetch_url and show it.
  • redirect_to_url: some providers host the QR code on their own page. Redirect the customer to it as described in Handle a redirect.

While the QR code is on screen, the customer stays on your page, so there may be no return to your return_url. Learn the result from the payment_succeeded or payment_failed webhook, or retrieve the payment with force_sync=true every few seconds while the code is shown. Stop polling when the status is final or the code expires.

{
  "amount": 150000,
  "currency": "VND",
  "confirm": true,
  "customer_id": "cus_demo_001",
  "return_url": "https://merchant.example.vn/checkout/return",
  "payment_method": "real_time_payment",
  "payment_method_type": "viet_qr",
  "payment_method_data": { "real_time_payment": { "viet_qr": {} } }
}