Skip to content

ตัวเลือกของผู้ใช้ ​

เริ่มจากสิ่งที่เลือกไม่ได้ ​

ผู้ใช้เลือกบัญชีปลายทางเองไม่ได้ และไม่มี endpoint ให้เลือก

ปลายทางที่ผู้ใช้จะโอนคือ บัญชีของผู้ถอนอีกรายที่ระบบจับคู่ให้ ไม่ใช่บัญชีของ gateway และไม่ใช่รายการที่เปิดให้เลือกจากลิสต์ · การจับคู่เกิดขึ้นตอน POST /v1/pay/deposits แล้วผลลัพธ์มาใน legs[] ครั้งเดียว

⇒ ห้ามสร้างหน้าจอที่ให้ผู้ใช้ "เลือกธนาคารปลายทาง" หรือ "เลือกบัญชีที่จะโอนเข้า" · ห้ามให้ผู้ใช้กด "ขอ QR ใหม่" เพื่อหวังปลายทางอื่น (นั่นคือการสร้างรายการซ้ำ)

ถ้าคุณย้ายมาจาก gateway ที่ให้ผู้ใช้เลือกช่องทาง (โอนธนาคาร / พร้อมเพย์ / เคาน์เตอร์) หน้าจอนั้นต้องหายไป · สิ่งที่มาแทนคือหน้าเลือก บัญชีผู้โอน ซึ่งเป็นคนละเรื่องกันโดยสิ้นเชิง


กับดัก: legs[] หลายรายการไม่ใช่ตัวเลือก ​

legs.length > 1 แปลว่าผู้ใช้ต้องโอน หลายครั้ง ไม่ใช่เลือกโอนอันใดอันหนึ่ง

json
{
  "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[] มาด้วย นี่คือยอดที่จับคู่ได้ทันทีในขณะนั้น

json
{
  "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 ]

เปลี่ยนยอด = ยกเลิกใบเดิม แล้วสร้างใบใหม่

ยอดของรายการที่สร้างแล้วแก้ไม่ได้ · ถ้าผู้ใช้เลือกยอดที่แนะนำ ให้

  1. POST /v1/pay/deposits/{id}/cancel ใบเดิม
  2. POST /v1/pay/deposits ใบใหม่ด้วยยอดใหม่ และ Idempotency-Key ใหม่ — body เปลี่ยนเมื่อไหร่ key ต้องเปลี่ยน ไม่งั้นได้ 409 idempotency_key_reused

🔴 อย่ายิงใบใหม่โดยไม่ยกเลิกใบเดิม จะไปชนเพดานรายการค้างแล้วได้ 409 too_many_open_deposits

js
// 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 ตั้งแต่สร้างรายการ

json
{
  "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ต่อไม่ได้ · หมดเวลาแล้วยังอัปสลิปย้อนหลังได้

ขั้นต่อไป ​

เอกสารชุดนี้อิงสัญญา API เวอร์ชัน 2026-08-01