ตัวเลือกของผู้ใช้
เริ่มจากสิ่งที่เลือกไม่ได้
ผู้ใช้เลือกบัญชีปลายทางเองไม่ได้ และไม่มี endpoint ให้เลือก
ปลายทางที่ผู้ใช้จะโอนคือ บัญชีของผู้ถอนอีกรายที่ระบบจับคู่ให้ ไม่ใช่บัญชีของ gateway และไม่ใช่รายการที่เปิดให้เลือกจากลิสต์ · การจับคู่เกิดขึ้นตอน POST /v1/pay/deposits แล้วผลลัพธ์มาใน legs[] ครั้งเดียว
⇒ ห้ามสร้างหน้าจอที่ให้ผู้ใช้ "เลือกธนาคารปลายทาง" หรือ "เลือกบัญชีที่จะโอนเข้า" · ห้ามให้ผู้ใช้กด "ขอ QR ใหม่" เพื่อหวังปลายทางอื่น (นั่นคือการสร้างรายการซ้ำ)
ถ้าคุณย้ายมาจาก gateway ที่ให้ผู้ใช้เลือกช่องทาง (โอนธนาคาร / พร้อมเพย์ / เคาน์เตอร์) หน้าจอนั้นต้องหายไป · สิ่งที่มาแทนคือหน้าเลือก บัญชีผู้โอน ซึ่งเป็นคนละเรื่องกันโดยสิ้นเชิง
กับดัก: legs[] หลายรายการไม่ใช่ตัวเลือก
legs.length > 1 แปลว่าผู้ใช้ต้องโอน หลายครั้ง ไม่ใช่เลือกโอนอันใดอันหนึ่ง
{
"id": "dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B",
"status": "awaiting_payment",
"amount_minor": 500000,
"leg_count": 2,
"legs": [
{
"leg_ref": "leg_7QF2H8KM",
"sequence": 1,
"amount_minor": 300000,
"expires_at": "2026-08-21T04:14:41.000Z",
"leg_status": "pending",
"method": "promptpay_qr",
"qr_image_url": "https://pay.example-wl.com/qr/dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B/1.png",
"receiver": {
"bank_code": "014",
"bank_name_th": "ธนาคารไทยพาณิชย์",
"account_number_masked": "xxx-x-x1234",
"account_name_masked": "ส*** ใ***"
}
},
{
"leg_ref": "leg_7QF2H8KN",
"sequence": 2,
"amount_minor": 200000,
"expires_at": "2026-08-21T04:14:41.000Z",
"leg_status": "pending",
"method": "promptpay_qr",
"qr_image_url": "https://pay.example-wl.com/qr/dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B/2.png",
"receiver": {
"bank_code": "004",
"bank_name_th": "ธนาคารกสิกรไทย",
"account_number_masked": "xxx-x-x9887",
"account_name_masked": "ว*** ป***"
}
}
]
}3,000 + 2,000 = 5,000 บาท · ผู้ใช้ต้องโอน ทั้งสองครั้ง ให้ครบ
| ❌ อย่าทำ | ✅ ทำ |
|---|---|
แสดงแค่ legs[0] | แสดงทุก leg เรียงตาม sequence |
| ให้ผู้ใช้เลือกว่าจะโอน leg ไหน | บอกว่า "ขั้นที่ 1 จาก 2" แล้วพาไปทีละขั้น |
ใช้ amount_minor ของรายการเป็นยอดใน QR | ใช้ legs[].amount_minor ของแต่ละ leg |
ใช้ expires_at ของรายการอย่างเดียว | แต่ละ leg มี expires_at ของตัวเอง — ใช้ค่าของ leg ที่กำลังแสดง |
leg_status ห้ามเอาไปแปลเป็นสถานะเงิน
leg_status มีแค่ pending กับ complete · ใช้ทำ progress bar ได้ แต่ 🔴 complete ไม่ได้แปลว่าเงินเข้า · ตัวเดียวที่แปลว่าเงินเข้าคือ status = "settled" ของทั้งรายการ
leg_ref ก็เป็นค่าภายในสำหรับอ้างอิงบรรทัดหนึ่ง ๆ ไม่ใช่เลขที่รายการที่จะเอาไปโชว์ผู้ใช้
จุดที่ให้เลือกได้จริง มี 4 จุด
1. ยอดที่แนะนำ ตอนรอสภาพคล่อง
เมื่อได้ 202 awaiting_liquidity เราส่ง liquidity.suggested_amounts_minor[] มาด้วย นี่คือยอดที่จับคู่ได้ทันทีในขณะนั้น
{
"id": "dep_01K3M8B2VQ7HYD4NKR0XF9TW6E",
"status": "awaiting_liquidity",
"amount_minor": 500000,
"legs": [],
"liquidity": {
"wait_started_at": "2026-08-21T04:10:47.902Z",
"estimated_wait_ms": 25000,
"retry_interval_ms": 10000,
"suggested_amounts_minor": [300000, 400000, 1000000]
},
"retry_after_seconds": 10
}หน้าจอที่ควรขึ้น
กำลังจัดหาช่องทางโอนสำหรับ 5,000.00 บาท
โดยทั่วไปใช้เวลาไม่เกินครึ่งนาที
ถ้าไม่อยากรอ ยอดเหล่านี้เริ่มโอนได้ทันที
[ 3,000.00 ] [ 4,000.00 ] [ 10,000.00 ]เปลี่ยนยอด = ยกเลิกใบเดิม แล้วสร้างใบใหม่
ยอดของรายการที่สร้างแล้วแก้ไม่ได้ · ถ้าผู้ใช้เลือกยอดที่แนะนำ ให้
POST /v1/pay/deposits/{id}/cancelใบเดิมPOST /v1/pay/depositsใบใหม่ด้วยยอดใหม่ และIdempotency-Keyใหม่ — body เปลี่ยนเมื่อไหร่ key ต้องเปลี่ยน ไม่งั้นได้409 idempotency_key_reused
🔴 อย่ายิงใบใหม่โดยไม่ยกเลิกใบเดิม จะไปชนเพดานรายการค้างแล้วได้ 409 too_many_open_deposits
// Node — ผู้ใช้เลือกยอดที่แนะนำ
async function switchToSuggestedAmount(deposit, newAmountMinor, order) {
await cancelDeposit(deposit.id, 'user_changed_amount')
return createDeposit({
...order,
amountMinor: newAmountMinor,
ref: `${order.ref}-R2`, // merchant_reference ต้องไม่ซ้ำ
idempotencyKey: `topup-${order.ref}-R2`, // 🔴 key ใหม่เสมอ
})
}2. บัญชีผู้โอน
นี่คือทางเลือกที่สำคัญที่สุดของผู้ใช้ และเป็นทางเลือกเดียวที่กระทบผลการตรวจสลิปโดยตรง — มีหน้าของตัวเองที่ บัญชีผู้โอน
หลังสร้างรายการแล้ว การเปลี่ยนบัญชีผู้โอนทำได้ทางเดียวคือ ยกเลิกแล้วสร้างใหม่ และทำได้เฉพาะตอนที่ยังไม่มีสลิป
3. ช่องทางที่คุณยอมรับ — fill_preference
ตัวนี้ merchant เป็นคนเลือก ไม่ใช่ผู้ใช้ ส่งตอน POST /v1/pay/deposits
| ค่า | ผล |
|---|---|
"any" (default) | ระบบใช้ช่องทางไหนก็ได้ที่จับคู่ได้เร็วที่สุด · liquidity_source ที่ได้กลับมาอาจเป็น internal หรือ external_gateway |
"internal_only" | จำกัดให้ใช้เฉพาะสภาพคล่องภายใน ⇒ โอกาสได้ 202 awaiting_liquidity และรอนานขึ้น แต่ liquidity_source จะเป็น internal เสมอ |
เลือกยังไง
ใช้ "any" ถ้าไม่มีเหตุผลเฉพาะ · "internal_only" เหมาะกับกรณีที่นโยบายภายในของคุณกำหนดช่องทางไว้ แลกกับอัตราการเข้าเส้น 202 ที่สูงขึ้น
liquidity_source มีสองค่าเท่านั้นคือ internal และ external_gateway · เป็นข้อมูลสำหรับ reconcile ฝั่งคุณ ไม่ใช่ค่าที่ควรเอาไปแสดงให้ผู้ใช้เห็น และไม่มีผลต่อวิธีเครดิต
4. หน้าจ่ายเงิน — checkout_url หรือหน้าของคุณเอง
ทุก response ที่จับคู่ได้จะมี checkout_url มาให้
ใช้ checkout_url | ทำหน้าเอง | |
|---|---|---|
| งานที่ต้องทำ | redirect อย่างเดียว | ทำ QR + countdown + กล่องเตือนบัญชีผู้โอน + หน้า multi-leg เอง |
qr_payload | ไม่ต้องแตะ | ต้องรับมาแสดง 🔴 ห้าม log ห้ามเก็บลง DB |
| ความเสี่ยงเรื่องข้อมูลรั่ว | ต่ำสุด | คุณรับผิดชอบเอง |
| ควบคุมแบรนด์ | หน้าของผู้ให้บริการคุณ | เต็มที่ |
เราแนะนำ checkout_url เป็นค่าเริ่มต้น โดยเฉพาะตอนต่อครั้งแรก · ทำหน้าเองเมื่อคุณพร้อมรับผิดชอบทั้ง countdown ที่อ่านจาก expires_at จริง กล่องเตือนบัญชีผู้โอน และหน้าจอ multi-leg แล้ว
ตัวเลือกอีกตัวที่ไม่ใช่ของผู้ใช้ — min_ttl_seconds
ส่งมาตอนสร้างรายการ ถ้าระบบจับคู่ได้แต่เวลาที่เหลือของช่องทางนั้นสั้นกว่าค่านี้ ระบบจะข้ามไปหาช่องทางอื่น หรือคง awaiting_liquidity ไว้แทน
ใช้ตอนที่ผู้ใช้ของคุณต้องสลับไปเปิดแอปธนาคารซึ่งกินเวลา — ตั้ง min_ttl_seconds ให้พอกับเวลาจริงที่ผู้ใช้ใช้โอน ดีกว่าปล่อยให้ได้ QR ที่หมดอายุใน 90 วินาที
ค่าที่รับได้คือ 0–86,400 วินาที และต้องสั้นพอที่จะอยู่ในอายุรายการหลังระบบ clamp; ถ้าไม่พอดี API ตอบ 422 ตั้งแต่สร้างรายการ
{
"amount_minor": 500000,
"currency": "THB",
"merchant_reference": "ORD-9985",
"end_user_ref": "u_88213",
"sender_id": "snd_01K2QF7V8H3NPD0YXW4RJ6ZB5C",
"fill_preference": "any",
"min_ttl_seconds": 300,
"client": {
"device_fingerprint": "df_9a1c7f2b0e",
"ip": "203.0.113.44",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)"
}
}🔴 ไม่ว่าจะตั้ง min_ttl_seconds เท่าไหร่ countdown ต้องอ่านจาก expires_at ที่ได้กลับมาจริงเสมอ ค่านี้เป็นแค่เงื่อนไขตอนจับคู่ ไม่ใช่การรับประกันเวลา
สรุปเป็นตาราง
| ผู้ใช้เลือกได้ไหม | อะไร | ทำยังไง |
|---|---|---|
| ✅ | บัญชีผู้โอน | เลือกจาก GET /v1/pay/senders ก่อนสร้างรายการ · เปลี่ยนทีหลัง = cancel + สร้างใหม่ |
| ✅ | ยอด (ตอนรอสภาพคล่อง) | เลือกจาก liquidity.suggested_amounts_minor[] = cancel + สร้างใหม่ |
| ⚠️ | หน้าจ่ายเงิน | คุณเลือก ไม่ใช่ผู้ใช้ — checkout_url หรือหน้าของคุณ |
| ⚠️ | ช่องทางสภาพคล่อง | คุณเลือกด้วย fill_preference ไม่ใช่ผู้ใช้ |
| ❌ | บัญชี/ธนาคารปลายทาง | ระบบจับคู่ให้ ไม่มี endpoint ให้เลือก |
| ❌ | จะโอน leg ไหน | ต้องโอนครบทุก leg ตาม sequence |
| ❌ | ยอดของรายการที่สร้างแล้ว | แก้ไม่ได้ ต้อง cancel + สร้างใหม่ |
| ❌ | ต่ออายุ expires_at | ต่อไม่ได้ · หมดเวลาแล้วยังอัปสลิปย้อนหลังได้ |
ขั้นต่อไป
- บัญชีผู้โอน — ตัวเลือกเดียวที่กระทบผลการตรวจสลิปโดยตรง
- สร้างรายการฝาก —
fill_preference·min_ttl_seconds· และเส้น202เต็ม ๆ - ยกเลิกรายการฝาก — ขั้นแรกของทุกการ "เปลี่ยนใจ"
- สถานะรายการฝาก —
leg_statusกับstatusต่างกันยังไง
