POST https://<your-api-domain>/payment/create

Creates a deposit order and returns a PromptPay QR code for the customer. When the customer pays, you receive a webhook callback to your notify_url.

สร้างคำสั่งฝากเงินและส่งกลับ PromptPay QR ให้ — เมื่อลูกค้าจ่ายเงินสำเร็จ ระบบจะ POST callback ไปที่ notify_url

QR vs bank transfer
QR vs โอนผ่านธนาคาร

Use this endpoint to issue a scannable PromptPay QR code. If the receiving deposit account doesn't support PromptPay — or if a customer specifically needs to transfer by account number — use /payment/create-transfer instead.

ใช้ endpoint นี้สำหรับสร้าง PromptPay QR ที่ลูกค้าสแกนได้ — ถ้าบัญชีรับเงินไม่รองรับ PromptPay (หรือกรณีพิเศษที่ลูกค้าจำเป็นต้องโอนด้วยเลขบัญชี) ให้ใช้ /payment/create-transfer แทน

Request

Request

Body fields

Body fields

FieldTypeRequiredNotes ฟิลด์ประเภทบังคับหมายเหตุ
merchant_id string Yes Your merchant identifier. merchant_id string Yes รหัส merchant ของคุณ
token string Yes Your API token. token string Yes API token ของคุณ
time number | string Yes Unix epoch (seconds). See Authentication. time number | string Yes Unix epoch (วินาที) — ดู Authentication
merchant_order_id string Yes Your unique order identifier. Alphanumeric, max 40 chars. Must be unique per merchant for at least 7 days. merchant_order_id string Yes รหัสคำสั่งฝั่งคุณ ตัวอักษร/ตัวเลข ไม่เกิน 40 ตัว ห้ามซ้ำใน 7 วัน
amount number | string Yes Amount in THB. Minimum 20.00. 2 decimal places. amount number | string Yes จำนวนเงิน (THB) ขั้นต่ำ 20.00 ทศนิยม 2 ตำแหน่ง
bank string Yes Customer's bank code, uppercase. See the full list at Bank codes. bank string Yes รหัสธนาคารของลูกค้า ตัวพิมพ์ใหญ่ — ดูรายการทั้งหมดที่ Bank codes
account_name string Yes Customer's name (for display / matching). account_name string Yes ชื่อลูกค้า (ใช้แสดง / จับคู่)
account_no string Yes Customer's account number, 10–15 digits. Non-digits are stripped automatically. account_no string Yes เลขบัญชีลูกค้า 10–15 หลัก — อักขระที่ไม่ใช่ตัวเลขจะถูกตัดออก
notify_url string Optional HTTPS URL we'll POST the callback to when status changes. If omitted, no callback is sent — you must poll /payment/query. notify_url string Optional HTTPS URL ที่จะให้ระบบ POST callback กลับ — ถ้าไม่ส่งจะไม่มี callback ต้องไป poll /payment/query

Code samples

ตัวอย่างโค้ด

TIME=$(date +%s)
BODY="{\"merchant_id\":\"AA12345678\",\"token\":\"YOUR_TOKEN\",\"time\":$TIME,\"merchant_order_id\":\"ORDER-2026-001\",\"amount\":\"500.00\",\"bank\":\"KBANK\",\"account_name\":\"สมชาย ใจดี\",\"account_no\":\"1234567890\",\"notify_url\":\"https://<your-merchant-webhook-URL>/payment-callback\"}"
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "YOUR_SECRET" | awk '{print $2}')

curl -X POST https://<your-api-domain>/payment/create \
  -H "Content-Type: application/json" \
  -H "X-SIGNATURE: $SIG" \
  -d "$BODY"
import crypto from 'node:crypto';

const SECRET = 'YOUR_SECRET';
const body = JSON.stringify({
  merchant_id:       'AA12345678',
  token:             'YOUR_TOKEN',
  time:             Math.floor(Date.now() / 1000),
  merchant_order_id: 'ORDER-2026-001',
  amount:            '500.00',
  bank:              'KBANK',
  account_name:      'สมชาย ใจดี',
  account_no:        '1234567890',
  notify_url:        'https://<your-merchant-webhook-URL>/payment-callback',
});
const sig = crypto.createHmac('sha256', SECRET).update(body).digest('hex');

const r = await fetch('https://<your-api-domain>/payment/create', {
  method:  'POST',
  headers: { 'Content-Type': 'application/json', 'X-SIGNATURE': sig },
  body
});
console.log(await r.json());
<?php
$secret = 'YOUR_SECRET';
$body = json_encode([
    'merchant_id'       => 'AA12345678',
    'token'             => 'YOUR_TOKEN',
    'time'              => time(),
    'merchant_order_id' => 'ORDER-2026-001',
    'amount'            => '500.00',
    'bank'              => 'KBANK',
    'account_name'      => 'สมชาย ใจดี',
    'account_no'        => '1234567890',
    'notify_url'        => 'https://<your-merchant-webhook-URL>/payment-callback',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

$sig = hash_hmac('sha256', $body, $secret);

$ch = curl_init('https://<your-api-domain>/payment/create');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        "X-SIGNATURE: $sig",
    ],
]);
echo curl_exec($ch);

Response

Response

Success — HTTP 200

สำเร็จ — HTTP 200

JSON
{
  "code":    200,
  "message": "Success",
  "data":    {
    "platform_order_id": "ABCP20260508abc123XYZ456",
    "merchant_order_id": "ORDER-2026-001",
    "uuid":              "0190b4a2-7c1d-7000-9f3a-2c8e5b1a4d6f",
    "order_datetime":    "2026-05-08 10:30:00",
    "expire_datetime":   "2026-05-08 10:45:00",
    "amount":            500.00,
    "transfer_amount":   500.03,
    "payment_type":      "QR",
    "qrcode":            "00020101021129370016A0000006770101110113006611234567895802TH540...",
    "payment_url":       "https://<your-payment-page-domain>/p/0190b4a2-7c1d-7000-9f3a-2c8e5b1a4d6f"
  },
  "success": true
}

Response fields

Response fields

FieldTypeDescription ฟิลด์ประเภทคำอธิบาย
data.platform_order_id string Order ID assigned by the gateway (24 chars). Use this for /payment/query and matching the callback's platform_order_id. data.platform_order_id string Order ID ที่ออกโดย gateway (24 ตัวอักษร) — ใช้กับ /payment/query และจับคู่ platform_order_id ใน callback
data.merchant_order_id string Echo of your input. data.merchant_order_id string Echo ค่าที่คุณส่งมา
data.uuid string (UUID v7) A globally-unique UUID for this order. Used as part of payment_url; you typically don't need to store it separately. data.uuid string (UUID v7) UUID v7 ของ order — ใช้ใน payment_url โดยทั่วไปฝั่งคุณไม่จำเป็นต้องเก็บแยก
data.order_datetime string Datetime when the order was created (YYYY-MM-DD HH:mm:ss in GMT+7). data.order_datetime string เวลาที่สร้าง order (YYYY-MM-DD HH:mm:ss GMT+7)
data.expire_datetime string Datetime after which this order is no longer accepted (YYYY-MM-DD HH:mm:ss GMT+7). Stop showing the QR to the customer once this passes. data.expire_datetime string เวลาที่ order หมดอายุ (YYYY-MM-DD HH:mm:ss GMT+7) — เลยเวลานี้ไม่ควรแสดง QR ให้ลูกค้าแล้ว
data.amount number The amount you requested. data.amount number จำนวนเงินที่คุณ request มา
data.transfer_amount number The amount the customer must actually transfer. May be slightly higher than amount (e.g. +0.01 increments) so we can uniquely match incoming transfers to this order. data.transfer_amount number จำนวนเงินที่ลูกค้าต้องโอนจริง — อาจสูงกว่า amount เล็กน้อย (เช่น +0.01) เพื่อให้ระบบจับคู่เงินโอนกลับเข้าออเดอร์นี้ได้
data.payment_type enum Always "QR" for this endpoint. data.payment_type enum เป็น "QR" เสมอสำหรับ endpoint นี้
data.qrcode string EMV-formatted PromptPay QR string. Render as a QR image and show to your customer. The destination bank account is encoded inside the QR. data.qrcode string PromptPay QR string ตามรูปแบบ EMV — ฝั่งคุณ render เป็น QR image แสดงให้ลูกค้า — ข้อมูลบัญชีปลายทางถูก encode ไว้ใน QR แล้ว
data.payment_url string | null A hosted payment page that displays the QR with a countdown. Useful if you'd rather redirect the customer than render the QR yourself. null if no payment-page domain has been configured for your account. data.payment_url string | null URL หน้า payment page ที่แสดง QR พร้อม countdown — ใช้แทนการ render QR เองโดย redirect ลูกค้าไปหน้านี้ — เป็น null ถ้าไม่ได้ตั้ง payment-page domain ไว้สำหรับบัญชีคุณ
Always show transfer_amount — not amount — to the customer
แสดง transfer_amount ให้ลูกค้า — ไม่ใช่ amount

If the customer pays the displayed amount instead of transfer_amount, our settlement system may not match the deposit to this order.

ถ้าลูกค้าจ่ายตาม amount แทนที่จะเป็น transfer_amount ระบบของเราอาจจับคู่เงินกลับเข้า order นี้ไม่ได้

Errors

Errors

HTTP Error code When it happens Error code เกิดเมื่อ
400 invalid-inputs Missing/invalid field, amount < 20, invalid bank, malformed URL. invalid-inputs Field ขาด/ผิดรูปแบบ, amount < 20, bank ไม่ถูกต้อง, URL ผิด
409 duplicate-entry A previous order used the same merchant_order_id within the past 7 days. duplicate-entry merchant_order_id ซ้ำกับ order เดิมในช่วง 7 วันที่ผ่านมา
403 permission-denied Payment is disabled on your merchant account. permission-denied Merchant ของคุณถูกปิดการรับชำระเงิน
503 service-unavailable No deposit account is currently available to accept this payment. Try again shortly. service-unavailable ไม่มีบัญชีรับเงินที่ว่างขณะนี้ — ลองใหม่ในอีกสักครู่
429 channel-limit-reached The deposit channel reached its hourly or daily limit. channel-limit-reached Channel รับเงินถึงโควตารายชั่วโมงหรือรายวันแล้ว
404 not-found Partner configuration not found for this merchant — contact your admin. not-found ไม่พบ Partner config ของ merchant — ติดต่อ admin

Behavior & notes

พฤติกรรม & หมายเหตุ

  • Idempotency by merchant_order_id. Calling with the same merchant_order_id within 7 days returns duplicate-entry.
  • Idempotency ด้วย merchant_order_id — เรียกซ้ำด้วย merchant_order_id เดิมในช่วง 7 วันจะได้ duplicate-entry
  • Order TTL. The order remains open until the customer pays or until the configured order expiration window elapses.
  • Order TTL — order จะอยู่สถานะ open จนกว่าลูกค้าจะจ่าย หรือเลยระยะเวลาหมดอายุที่ตั้งไว้สำหรับบัญชีคุณ
  • Slot allocation. The system reserves a unique transfer_amount slot per deposit account for ~15 minutes to disambiguate concurrent deposits.
  • Slot allocation — ระบบจอง slot ของ transfer_amount บนบัญชีรับเงิน ~15 นาที เพื่อแยกการโอนพร้อมกัน
  • Fees are calculated automatically and applied only when the order completes successfully — they don't appear in this response.
  • ค่าธรรมเนียม คำนวณและตัดอัตโนมัติเฉพาะเมื่อ order สำเร็จ ไม่แสดงใน response นี้
  • Display the destination account info to your customer in case the QR cannot be scanned (e.g., banking app issues, network).
  • โชว์ข้อมูลบัญชีปลายทางให้ลูกค้าด้วย เผื่อกรณีสแกน QR ไม่ได้ (เช่น แอปธนาคารมีปัญหา, เน็ตช้า)