Create Payment
Creates a new payment transaction. The payment will be processed asynchronously, and you'll receive a nextAction in the response indicating what action to take next.
Endpoint
POST /api/v1/payments
Authentication
This endpoint requires HMAC Integration Authentication.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Amount as decimal string (e.g., "1000.00" = 10.00 THB) |
currency | string | No | Currency code. Defaults to "THB" |
referenceId | string | Yes | Your unique reference ID (alphanumeric, underscores, hyphens, dots, 1-255 chars) |
paymentMethod | string | Yes | Payment method: "promptpay" or "bank_transfer" |
responseMode | string | No | Instruction fields to return: "both" (default), "payment_link", or "qr_code" |
customerBankAccountName | string | Yes | Customer's bank account name |
customerBankAccountNumber | string | Yes | Customer's bank account number |
customerBankCode | string | Yes | 3-digit Thai bank code (e.g., "004" for KBANK) |
description | string | No | Payment description (max 255 characters) |
merchantReference | string | No | Your invoice, order, or internal transaction reference |
invoiceNo | string | No | Alias for merchantReference if your system already uses this field name |
metadata | object | No | Additional metadata (key-value pairs) |
idempotencyKey | string | No | Unique key to prevent duplicate payments (recommended) |
expiresInMinutes | number | No | Payment expiration time in minutes (5-1440, default based on payment method) |
Integration Context
Your merchantId and integrationId are automatically provided from your integration
credentials. You don't need to include them in the request.
Example Request
import crypto from 'crypto';
const API_KEY = process.env.PAYMENT_API_KEY!;
const API_SECRET = process.env.PAYMENT_API_SECRET!;
async function createPayment() {
const method = 'POST';
const path = '/api/v1/payments';
const body = JSON.stringify({
amount: '1000.00',
currency: 'THB',
referenceId: `order-${Date.now()}`,
paymentMethod: 'promptpay',
// Return a payment URL without QR code data.
responseMode: 'payment_link',
customerBankAccountName: 'John Doe',
customerBankAccountNumber: '1234567890',
customerBankCode: '004',
description: 'Payment for order #12345',
merchantReference: 'order-12345',
metadata: {
order_id: '12345',
},
idempotencyKey: `order-12345-${Date.now()}`,
});
const timestamp = Date.now().toString();
const message = `${timestamp}.${body}`;
const signature = crypto
.createHmac('sha256', API_SECRET)
.update(message)
.digest('hex');
const response = await fetch(`https://api.example.com${path}`, {
method,
headers: {
'X-API-Key': API_KEY,
'X-Timestamp': timestamp,
'X-Signature': `sha256=${signature}`,
'Content-Type': 'application/json',
},
body,
});
const result = await response.json();
if (result.success) {
console.log('Payment created:', result.data.id);
console.log('Status:', result.data.status);
console.log('Next action:', result.data.nextAction);
// Handle nextAction
if (result.data.nextAction?.type === 'use_payment_app') {
// Redirect customer to payment app
window.location.href = result.data.nextAction.paymentAppUrl;
} else if (result.data.nextAction?.type === 'redirect') {
// Redirect to custom URL
window.location.href = result.data.nextAction.redirectUrl;
}
} else {
console.error('Error:', result.error);
}
}
createPayment();Response
Response modes
responseMode only changes the payment instructions inside nextAction. All other payment fields remain in the response. It does not change provider processing or the QR data stored by BroPay.
| Value | nextAction projection |
|---|---|
both | Default, backward-compatible response. Returns paymentAppUrl and QR data when available. |
payment_link | Returns nextAction.paymentAppUrl without nextAction.qrCode. QR copies are also omitted from response metadata. Use this when your system only redirects to a URL. |
qr_code | Returns only nextAction.qrCode when QR generation has completed. |
To receive a payment link without QR code data, include this field in the create request:
{
"responseMode": "payment_link"
}The returned payment object will contain:
{
"nextAction": {
"type": "use_payment_app",
"paymentAppUrl": "https://pay.example.com/docs/pay/secure-token-here"
}
}For qr_code, QR generation is asynchronous, so the initial create response can contain nextAction: null. Retrieve the payment with GET /api/v1/payments/{id}?responseMode=qr_code after it reaches processing.
Omitting responseMode is identical to sending "both", so existing integrations are unaffected.
Success Response (201 Created)
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"amount": "1000.00",
"currency": "THB",
"status": "processing",
"paymentMethod": "promptpay",
"referenceId": "order-12345",
"description": "Payment for order #12345",
"metadata": {
"order_id": "12345"
},
"clientSecret": null,
"nextAction": {
"type": "use_payment_app",
"paymentAppUrl": "https://pay.example.com/docs/pay/secure-token-here"
},
"confirmedAt": null,
"capturedAt": null,
"canceledAt": null,
"expiresAt": "2024-01-01T01:00:00.000Z",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
}Error Response (400 Bad Request)
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "amount must be a valid decimal number",
"metadata": {
"field": "amount"
}
}
}Next Action Types
After creating a payment, the response includes a nextAction field that tells you what to do next:
use_payment_app
Redirect the customer to One2Pays's payment app to complete the payment:
{
"type": "use_payment_app",
"paymentAppUrl": "https://pay.example.com/docs/pay/secure-token-here"
}Action: Redirect the customer to paymentAppUrl.
redirect
Redirect to a custom URL (if your integration has a custom redirect URL configured):
{
"type": "redirect",
"redirectUrl": "https://your-app.com/payment/{paymentId}"
}Action: Redirect the customer to redirectUrl.
display_bank_transfer_instructions
Display bank transfer instructions to the customer:
{
"type": "display_bank_transfer_instructions",
"bankTransferInstructions": {
"bankName": "Kasikorn Bank",
"accountNumber": "1234567890",
"accountName": "Example Company Ltd.",
"amount": "1000.00",
"reference": "ORDER-12345"
}
}Action: Display the bank transfer instructions to the customer.
Payment Creation Flow
Payment Status
After creation, payments typically start in one of these statuses:
processing- Payment is being processedrequires_payment_method- Payment method needs to be attachedrequires_confirmation- Payment needs to be confirmedrequires_action- Additional action required from customer
Order References and Active Payment Instructions
Use a unique referenceId for every payment create request. For each new customer
deposit or order, also send a unique business reference in merchantReference or
invoiceNo, and keep that value in your system for reconciliation.
Do not rely only on free-form metadata when the value is your primary deposit,
invoice, or order reference.
If a customer starts another PromptPay payment while a previous payment instruction
is still valid, the API may return the existing valid payment instruction instead
of creating a second payment. Always store the returned payment id, referenceId,
and nextAction, and use webhooks to reconcile the final payment status.
Idempotency
Use Idempotency Keys
Always include an idempotencyKey in your requests to prevent duplicate payments if you need to
retry a request.
If you make the same request twice with the same idempotencyKey, you'll receive the same payment object. This is useful for handling network errors and retries.
For a retry of the same create request, reuse the same idempotencyKey. For a new
deposit or order, generate a new idempotencyKey.
If you don't provide an idempotencyKey, the system will auto-generate one, but you won't be able to retry safely.
Error Codes
| Code | Description |
|---|---|
VALIDATION_ERROR | Invalid request parameters |
INVALID_AMOUNT | Amount must be a valid decimal number |
INVALID_CURRENCY | Currency must be 'THB' |
INVALID_REFERENCE_ID | Reference ID format is invalid |
INVALID_BANK_CODE | Bank code must be a valid 3-digit Thai bank code |
DUPLICATE_REFERENCE_ID | Reference ID already exists for this merchant |
INVALID_CREDENTIALS | Authentication failed |
Related Endpoints
- Retrieve Payment - Get payment details
- List Payments - List all payments
- Cancel Payment - Cancel a payment
See Also
- Payment Methods
- Webhooks - Receive payment status updates
- Error Handling