Select Payment Method
Select a payment method for a checkout session. Returns the payment details the customer needs to complete the transfer.
POST /v1/checkout/session/:sessionId/payment-methodRequired permission: checkout
Authentication
Requires an API key. Origin/domain validation is enforced for public keys.
Path parameters
| Parameter | Type | Description |
|---|---|---|
sessionId | string | The checkout session ID |
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
payment_method | string | Yes | "bank_transfer" or "payid" |
Example request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/session/ecc48011-e36e-4741-9b99-657f4a1ee86e/payment-method \
-H "Content-Type: application/json" \
-H "x-api-key: pk_test_your_public_key" \
-d '{"payment_method": "bank_transfer"}'Response: Bank Transfer
When payment_method is "bank_transfer", a temporary virtual account is created. Show the customer account_number, bank_name and account_name exactly as returned. ZevPay mints these accounts through partner banks and rotates partners over time, so the bank differs between sessions.
{
"success": true,
"data": {
"payment_method": "bank_transfer",
"account_number": "5000973258",
"bank_name": "9 Payment Service Bank",
"bank_code": "000036",
"account_name": "Your Business Name",
"amount": 500000,
"expires_at": "2026-03-07T19:37:00.000Z",
"banks": [
{ "provider": "psb9", "bank_name": "9 Payment Service Bank", "bank_code": "000036", "current": true },
{ "provider": "palmpay", "bank_name": "PalmPay", "bank_code": "100039", "current": false }
]
}
}| Field | Type | Description |
|---|---|---|
payment_method | string | Always "bank_transfer" |
account_number | string | Virtual account number for the transfer |
bank_name | string | Bank the customer must select. Not fixed: read it per session, never hardcode it |
bank_code | string | NIP code for the bank, for UIs that match on code |
account_name | string | Merchant's business name (or "ZevPay Checkout" if not configured) |
amount | integer | Amount in kobo the customer should transfer |
expires_at | string | ISO 8601 timestamp: when the virtual account expires |
banks | array | Every bank this session can be paid into, with the active one flagged current |
Letting the customer change bank
Some customers cannot complete a transfer to a given bank. The banks array is the list you can offer them. Render it as a "change bank" control and call Switch Bank with the chosen provider.
The list is served per session, so a bank added on our side appears in your checkout with no change on yours. Never hardcode it.
Switch Bank
Move a pending bank transfer to another bank without starting a new session.
POST /v1/checkout/session/:sessionId/switch-bankRequired permission: checkout
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | A provider value from the banks array |
Example request
curl -X POST https://api.zevpaycheckout.com/v1/checkout/session/ecc48011-e36e-4741-9b99-657f4a1ee86e/switch-bank \
-H "Content-Type: application/json" \
-H "x-api-key: pk_test_your_public_key" \
-d '{"provider": "palmpay"}'The response is the same bank transfer payload shown above, carrying the new account_number, bank_name and refreshed banks list.
The old account number stops working
Switching retires the previous account before issuing the new one, so the same session can never be paid twice. Replace the account number on screen as soon as the call returns, and never show two at once.
The amount, reference and session are unchanged. Payment webhooks and verification behave exactly as they would have on the original bank.
Switch bank errors
| Status | Code | Cause |
|---|---|---|
400 | BANK_UNAVAILABLE | The provider is not in the session's banks list |
503 | BANK_SWITCH_FAILED | The previous account could not be retired. Retry shortly |
Response: PayID
When payment_method is "payid", a dynamic PayID is created:
{
"success": true,
"data": {
"payment_method": "payid",
"pay_id": "5de2.dypid",
"qr_data": "https://zevpay.me/5de2.dypid",
"amount": 500000,
"expires_at": "2026-03-07T19:37:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
payment_method | string | Always "payid" |
pay_id | string | Dynamic PayID for the payment |
qr_data | string | URL for QR code generation (https://zevpay.me/{payId}) |
amount | integer | Amount in kobo |
expires_at | string | ISO 8601 timestamp: when the dynamic PayID expires |
Behavior
- Calling this endpoint multiple times for the same method reuses the existing virtual account or PayID (as long as it's still active/pending)
- Switching methods (e.g., from
bank_transfertopayid) logs amethod_switchedevent and increments the attempt counter - Switching methods also retires what the customer left behind, so a session is never payable in two places at once. Stop showing the previous account number as soon as you switch: it no longer accepts payment
- The session must be
active; expired, completed, or failed sessions return an error
Errors
| Status | Message | Cause |
|---|---|---|
400 | Session is {status}. | Session is not active (e.g., "Session is completed.") |
400 | Session has expired. | Session's expiry time has passed |
400 | Unsupported payment method: | Invalid payment_method value |
400 | Session does not belong to this API key. | API key mismatch |
401 | Invalid API key | Key not found or inactive |
404 | Session not found. | Invalid session ID |