Skip to content

อภิธานศัพท์ ​

คำที่ใช้ทั้งเอกสาร พร้อมที่ที่คุณจะเจอมันจริง ๆ ในโค้ด

อ่านสามคู่นี้ก่อน ถ้ามีเวลาแค่นาทีเดียว

คู่ต่างกันอย่างไร
amount_received_minor ↔ credited_amount_minorตัวแรกเครดิตให้ ผู้ใช้ปลายทาง · ตัวหลังคือยอดที่เข้า กระเป๋า merchant ของคุณ
leg ↔ fillleg คือ ปลายทางที่ต้องโอนไป (ฝั่งฝาก) · fill คือ เงินที่ไหลเข้ามาแล้วก้อนหนึ่ง (ฝั่งถอน)
pending_clearing ↔ availableเครดิตแล้วแต่ ยังถอนไม่ได้ ↔ ถอนได้จริง (ห่างกัน 72 ชั่วโมง)

คนและรายการ ​

ผู้ฝาก (depositor) ผู้ใช้ปลายทางของคุณที่กำลังเติมเงิน · เป็นคนที่กดโอนจากแอปธนาคารจริง

ผู้ถอน (withdrawer) เจ้าของบัญชีธนาคารที่รับเงินโอน · เป็น ลูกค้าของ merchant อีกราย ไม่ใช่บัญชีของ gateway · นี่คือเหตุผลที่ระบบนี้ไม่มีบัญชีกลางในเส้นทางปกติ

รายการฝาก (deposit order) — dep_... ใบสั่งเติมเงินหนึ่งใบ · มี status 9 ค่า · id ห้าม parse ห้ามเดา prefix คอลัมน์ต้องเป็น text

รายการถอน (withdrawal order) — wdr_... ใบสั่งถอนหนึ่งใบ · มี status 7 ค่า · หนึ่งใบรับเงินจากผู้ฝากได้หลายคน

merchant_reference เลขอ้างอิงฝั่งคุณ ต้อง unique ต่อ merchant ยาวไม่เกิน 64 ตัวอักษร · ใช้ค้นย้อนกลับได้ แต่ deposit_id คือ primary key ที่ support ใช้ตามเรื่อง

end_user_ref id ของผู้ใช้ในระบบคุณ · ใช้ผูกกับ GET /v1/pay/senders และ GET /v1/pay/beneficiaries · 🔴 เป็นค่าที่คุณกรอกเอง ปลอมได้ ระบบจึงไม่เคยใช้ค่านี้เดี่ยว ๆ เป็นมิติของ rate limit


ฝั่งการโอน ​

leg — legs[]ปลายทางหนึ่งจุดที่ผู้ใช้ต้องโอนไป · ยอด 5,000 อาจถูกซอยเป็น 2 leg (3,000 + 2,000) ไปคนละบัญชี ⇒ UI ต้องรองรับ "ขั้นที่ 1 จาก 2" 🔴 legs[0] ไม่ใช่ทั้งหมด

leg_ref id ของ leg ที่คุณเห็น · เป็นค่าที่ผ่านการแปลงมาแล้ว ⇒ merchant สองรายที่อยู่คนละฝั่งของรายการเดียวกันจะได้ค่าคนละตัว join กันไม่ได้ · ใช้เป็น key ของบรรทัด timeline ฝั่งคุณ

leg_status — pending | complete สถานะภายในของ leg ที่สะท้อนออกมาให้เห็น · 🔴 complete ไม่ได้แปลว่าเงินเข้า ตัวที่แปลว่าเงินเข้าคือ status === 'settled' เท่านั้น · ใช้ทำ progress bar ได้ ห้ามใช้ตัดสินใจเรื่องเงิน

qr_payload สตริง PromptPay สำหรับ render QR · ข้างในมี PromptPay ID ของผู้ถอนโดยหลีกเลี่ยงไม่ได้ ⇒ 🔴 ห้าม log ห้ามเก็บลง DB ห้ามส่งต่อ ใช้แสดงผลแล้วทิ้ง · มีอายุเท่ากับ expires_at

expires_at เวลาหมดอายุจริงของรายการ · 🔴 อ่านจาก response ทุกครั้ง ห้าม hardcode — บาง route หมดอายุจริงใน ~4 นาที ไม่ใช่ 15–20 นาที


ฝั่งการรับเงิน ​

fillเงินหนึ่งก้อนที่ไหลเข้ารายการถอน · ถอน 10,000 ที่ได้ 4,000 + 6,000 = 2 fill ⇒ webhook withdrawal.filled สองครั้ง ไม่ใช่ครั้งเดียวตอนจบ · withdrawal.filled เป็น event เดียวในระบบที่ยิงหลายครั้งต่อ object เดียว

allocation การจับคู่ระหว่างรายการฝากหนึ่งใบกับรายการถอนหนึ่งใบที่ระบบทำอยู่ภายใน · 🔴 merchant ไม่เห็น object นี้เลย ทั้งชื่อ id และสถานะของมัน — สิ่งที่คุณเห็นคือเงาของมันสองรูปแบบ: legs[] ฝั่งฝาก และ fills[] / withdrawal.filled ฝั่งถอน ⇒ ไม่ต้องมีตารางรองรับ allocation ในฐานข้อมูลของคุณ

filled_minor / remaining_minor / reserved_minor ยอดที่ได้แล้ว / ยอดที่ยังขาด / ยอดที่มีคนกำลังโอนมาแต่ยังไม่ถึง 🔴 แหล่งความจริงคือค่าที่มากับ payload ผ่าน version guard ห้าม SUM() ตารางฝั่งคุณ ห้าม +=

settled_splits / max_splits จำนวนก้อนที่ปิดไปแล้ว / เพดานจำนวนก้อนของใบนั้น · ใช้ทำ UI ว่า "ได้แล้ว 2 จาก 4 ก้อน"

partial fill สภาพที่รายการถอนได้เงินมาบางส่วน · status เป็น partially_filled · 🔴 ปิดรายการได้เมื่อ status === 'settled' และ remaining_minor === 0 เท่านั้น

fee_recognized_minor ค่าธรรมเนียมส่วนที่รับรู้ไปแล้วตามสัดส่วนที่ปิดจริง · ค่าธรรมเนียมแบบคงที่ผูกกับ ใบ ไม่ใช่ต่อ fill ⇒ ซอย 4 ก้อนไม่ได้จ่ายคงที่ 4 เท่า 🔴 อย่าคำนวณค่าธรรมเนียมเองต่อ fill


เงินในกระเป๋า ​

balance_transaction — btxn_... บรรทัดการเคลื่อนไหวของกระเป๋าคุณ · immutable ⇒ ไม่มี version ไม่มี previous_attributes ⇒ dedupe ด้วย id และ สะสมยอด ห้ามเขียนทับด้วย balance_after

bucket — available | pending_clearing | reserved

bucketหมายถึงถอนออกได้ไหม
pending_clearingเพิ่งเครดิต ยังอยู่ในหน้าต่าง 72 ชั่วโมง❌ ได้ 402
availableผ่าน clearing แล้ว✅
reservedถูกล็อกไว้กับรายการถอนที่เปิดอยู่❌

clearing กระบวนการย้ายยอดจาก pending_clearing ไป available เมื่อครบ 72 ชั่วโมง นับจาก settled_at

  • เวลาที่จะย้ายบอกไว้ล่วงหน้าที่ clearing_available_at ในตัวรายการฝาก
  • ตอนย้ายจริงคุณจะได้ webhook balance.cleared
  • 🔴 balance.cleared คือ การย้ายช่อง ไม่ใช่เงินเข้าใหม่ — บวกยอดซ้ำตอนนี้ = ยอดเป็นสองเท่า

ทำไมต้องมี 72 ชั่วโมง — ผู้ถอนโต้แย้งได้ภายในหน้าต่างนี้ ถ้าเรื่องนั้นจริงต้องกลับรายการ · การกลับรายการภายใน 72 ชั่วโมงตัดจาก pending_clearing ⇒ ยอด available ของคุณไม่ติดลบ


เมื่อเรื่องไม่เป็นไปตามแผน ​

under_review สลิปเข้ามาแล้วแต่มีมิติ non-amount ไม่ผ่าน / กำกวม / ติด policy ⇒ เข้าคิวเจ้าหน้าที่; short-pay ที่ผิดเฉพาะยอดและอีก 5 มิติผ่านจะ settle อัตโนมัติที่ยอดจริง ส่วน overpay จะ settle ตามยอด order พร้อม claim/flag 🔴 ไม่ใช่ error ไม่ใช่การปฏิเสธเงิน · แสดง review.reasons[0].display_th ตรง ๆ · สีเหลือง/ฟ้า ไม่ใช่แดง · ห้ามมีปุ่ม "โอนใหม่"

6 มิติ — verification{}amount_exact · within_time_window · sender_bank_match · sender_account_match · sender_name_match · receiver_match · ทั้งหมดเป็น boolean ล้วน · exact ต้องครบ 6; เมื่ออีก 5 ตัวผ่าน short-pay จะ settle อัตโนมัติที่ A=V<X และ overpay จะ settle ตามยอด order พร้อม claim; amount mismatch อย่างเดียวไม่เข้า review

review.estimated_resolution_bucket — under_15m | under_1h | under_4h | extended ช่วงเวลาคร่าว ๆ ที่เอาไปบอกผู้ใช้ได้ · 🔴 นี่เป็น ตัวเดียว ที่ใช้บอกเวลาได้ ห้าม hardcode "ไม่เกิน 15 นาที" เพราะกรอบเวลาเป็นของ White Label แต่ละราย

review.escalated เรื่องค้างนานเกินกรอบแล้วถูกยกระดับ · จะได้ deposit.under_review ซ้ำ พร้อม version ใหม่ 🔴 ไม่มีการอนุมัติอัตโนมัติเมื่อเวลาหมด และจะไม่มีวันมี — ไม่มี event ชื่อ deposit.auto_approved

late-slip — late_slip_accepted_until หน้าต่างรับสลิปย้อนหลัง 24 ชั่วโมง สำหรับคนที่โอนแล้วปิดเบราว์เซอร์ หรือกด cancel แล้วเผลอโอน ⇒ expired และ canceled ไม่ใช่จุดจบ ถ้ายังมีค่านี้ · หมดหน้าต่างแล้วอัปสลิปจะได้ 410 late_slip_window_closed + claim_url

is_terminal 🔴 ตัวเดียวที่ใช้ตัดสินว่าปิดรายการได้หรือยัง — ไม่ใช่ชื่อ status · expired และ canceled ที่ยังรับ late-slip ได้ จะมีค่านี้เป็น false · settled เองก็เป็น false เพราะยังออกไป reversed ได้

หน้าต่างข้อพิพาท (dispute window) ช่วงเวลาที่ผู้ถอนกด "ยังไม่ได้รับเงิน" ได้ = 72 ชั่วโมง นับจากที่ fill ถูกปิด

  • ทันทีที่ปิด fill สถานะการรับเงินเป็น "รอยืนยัน" และ payload บอก auto_confirm_at มาด้วย — ผู้ถอนไม่กดอะไรเลย ระบบยืนยันให้เองที่ 3 ชั่วโมง (webhook withdrawal.receipt_confirmed ที่ confirmed_by: "auto")
  • 🔴 การยืนยันอัตโนมัตินี้ ไม่ใช่การอนุมัติเงิน เงินถูกปิดไปแล้วด้วยหลักฐานสลิปที่ตรวจกับธนาคาร มันแค่ปิดหน้าต่างโต้แย้งของผู้ใช้
  • เปิดข้อพิพาทเมื่อไหร่ ⇒ withdrawal.disputed (funds_on_hold: true · status ไม่เปลี่ยน) และยอดฝั่งผู้ฝากถูกแช่ไว้ไม่ให้ผ่าน clearing
  • ตอบกลับก่อน dispute.respond_by ด้วย POST /v1/pay/disputes/{id}/evidence · ผลออกมาเป็น withdrawal.dispute_resolved ที่ outcome ∈ confirmed | reversed

reversal — deposit.reversed / withdrawal.fill_reversed / balance.debited การดึงหรือแก้ยอดหลังจากเครดิตไปแล้ว โดย event ที่เกิดขึ้นขึ้นกับเหตุผล:

  • true transfer failure (BANK_RECALL / ACCOUNT_FROZEN / SLIP_INVALIDATED_AFTER_SETTLEMENT) อาจมี withdrawal.fill_reversed
  • deposit-only correction (DUPLICATE_CREDIT / OPS_ERROR / MERCHANT_DISPUTE_LOST) ส่ง deposit.reversed + balance.debited เท่านั้น ไม่ส่ง withdrawal.fill_reversed และไม่เปลี่ยน withdrawal, fill หรือ fee ฝั่งถอน

🔴 event ทั้งสามชนิดปิดรับไม่ได้ ต้องมี handler ตั้งแต่วันแรก แต่ห้ามสมมติว่าทุกเหตุผลจะส่งครบทั้งสามใบ

overpayment / claim มีสองทาง: ยอดเกินในสลิปเดียวมากับ deposit.settled ใบเดียวพร้อม overpayment{cause:'same_transfer_excess'}; ส่วนการโอนเพิ่มหลัง settle มาเป็น deposit.overpaid พร้อม cause:'additional_transfer' · ทั้งคู่ คง status: 'settled' เครดิตผู้ใช้เฉพาะ amount_received_minor ซึ่งไม่เกินยอดที่สร้าง 🔴 ส่วนเกินเปิด claim ที่ status:'open' และ action_required:'contact_support' — ห้ามแตะรายการเดิม ห้ามเครดิตส่วนเกินเข้ากระเป๋า merchant และห้ามบอกว่าคืนเงินแล้ว เพราะไม่มี auto-refund

claim — POST /v1/bo/claims ช่องทางเปิดเรื่องด้วยมือ สำหรับเคสที่ระบบกู้อัตโนมัติไม่ได้ (โอนแล้วไม่เคยอัปสลิป · พ้นหน้าต่าง 24 ชั่วโมง · โอนเข้า QR เก่านอกรายการ) · ผลลัพธ์ที่เป็นไปได้: credited / refund_issued / rejected แจ้งผ่าน webhook claim.updated


สภาพคล่องและช่องทาง ​

liquidity_source — internal | external_gateway ที่มาของเงินฝั่งปลายทาง มีสองค่านี้เท่านั้น 🔴 ห้ามใช้ค่านี้ตัดสินใจเรื่องเครดิต — ใช้ทำสถิติหรือ debug ได้ แต่ตัวที่ตัดสินคือ status เสมอ

external gateway ช่องทางสำรองที่ใช้เมื่อไม่มีผู้ถอนในระบบมารับ · จากมุมของคุณต่างจากปกติแค่สองอย่าง: liquidity_source เป็น external_gateway และ expires_at มักสั้นกว่ามาก (~4 นาที) — flow, event, สถานะ และวิธีเครดิตเหมือนกันทุกประการ รายการที่วิ่งผ่านช่องทางนี้ ไม่มีเลขอ้างอิงของบุคคลที่สามให้ ⇒ ให้ผู้ใช้อ้างอิงด้วย deposit_id ทุกช่องทาง

suggested_amounts_minor[] ยอดที่จับคู่ได้ทันทีในขณะนั้น · มากับ 202 awaiting_liquidity ⇒ เสนอให้ผู้ใช้เลือกแทนที่จะให้รอเฉย ๆ


บัญชีของทั้งสองฝั่ง ​

sender / sender_id บัญชีที่ผู้ใช้ จะกดโอนจริง — ไม่ใช่ "เจ้าของ account ในระบบคุณ" · สามีโอนให้ภรรยา ⇒ ส่งบัญชีของสามี 🔴 บังคับ ไม่ส่งได้ 422 sender_required · sender_id จาก GET /v1/pay/senders การันตีว่าไม่เข้าคิวเพราะชื่อหรือเลขบัญชีไม่ตรง

beneficiary_id บัญชีผู้รับเงินถอน · ต้องผ่าน verify ชื่อกับธนาคารก่อน (webhook beneficiary.verified / beneficiary.rejected) ไม่งั้นสร้างรายการถอนได้ 409 beneficiary_not_verified และไม่มีการล็อกเงิน


ศัพท์ของ API ​

_minor ทุก field ที่เป็นจำนวนเงินลงท้ายด้วย _minor และเป็น integer หน่วย สตางค์ ไม่มีข้อยกเว้น · 5,000.00 บาท = 500000

version เลขที่เพิ่มขึ้นทุกครั้งที่ object เปลี่ยน · ระบบรับประกันว่า ไม่มีสอง event ของ object เดียวกันที่ version เท่ากัน ⇒ ใช้เป็น guard ใน UPDATE ... WHERE version < $new แล้วผูก side effect กับ rowCount ได้อย่างปลอดภัย

sequence เลขลำดับ event ต่อ merchant เพิ่มขึ้นตาม เวลาที่สร้าง ไม่ใช่เวลาที่ส่งถึง · ช่องว่างชั่วคราวเป็นเรื่องปกติ (event ที่ยัง retry อยู่) · ช่องว่างที่ค้างเกิน 4 วัน = หายจริง ให้ไป reconcile

event.id — evt_... ค่าเดิมทุก attempt และทุกครั้งที่สั่งส่งซ้ำด้วยมือ ⇒ ใช้เป็น unique key ใน transactional inbox · เก็บ ≥ 31 วัน 🔴 เจอซ้ำต้องตอบ 200 ไม่ใช่ 409

livemodefalse = sandbox · true = live · event สองฝั่งข้ามกันไม่ได้เด็ดขาด ⇒ handler ต้องตรวจว่าตรงกับ environment ตัวเอง

api_version รูปแบบ YYYY-MM-DD ที่คุณ pin ไว้ · การเพิ่ม field ใหม่และการเพิ่มค่าใหม่ใน enum ที่ประกาศว่า "open" ไม่นับเป็น breaking change ⇒ 🔴 ทุก switch ของ status, reasons[].code, balance_transaction.type, fee_breakdown[].component ต้องมี default:

canonical string สตริง 9 บรรทัดที่ใช้เป็น input ของลายเซ็น HMAC-SHA256 บน request ขาออกของคุณ — ดู authentication

Idempotency-Key กับ PG-Nonce สองตัวนี้ทำหน้าที่ตรงข้ามกัน · PG-Nonce ต้อง ใหม่ทุก attempt · Idempotency-Key ต้อง คงเดิมตลอด logical operation

magic amount ยอดพิเศษ 100.00–116.00 บาทบน sandbox ที่ใช้บังคับให้รายการเดินเส้นทางที่ต้องการ — ดู sandbox


คำที่ระบบนี้ไม่มี และจะไม่มี ​

คำที่คุณอาจคุ้นจาก gateway เจ้าอื่นของเราคืออะไร
เศษสตางค์เพื่อจับคู่รายการจับคู่ด้วย ตัวตนของผู้โอน ยอดเป็นเลขกลมเสมอ
payment_instructions[] / bank_account / promptpaylegs[]
409 duplicate_slip แบบตอบทันทีdurable intake สำเร็จตอบ 202; ผลตรวจซ้ำมาทีหลังทาง webhook
callback envelope {code, message, result} ที่คืน HTTP 200 เสมอHTTP status จริง (201 202 402 409 410 422 429 503)
ยอดเข้ากระเป๋าตัวเลขเดียว4 ยอดหลัก ordered_amount_minor (X) / transferred_amount_minor (V) / amount_received_minor (A) / overpayment.amount_minor (E, หรือ 0) และแยก fee_total_minor / credited_amount_minor เพิ่ม
"callback ครั้งเดียวจบ"ลำดับไม่การันตี · retry 11 ครั้งกินเวลา ~92 ชั่วโมง · ต้องมี version guard
อนุมัติอัตโนมัติเมื่อ SLA หมดไม่มี — มีแต่ review.escalated

ขั้นต่อไป ​

  • ยังไม่เห็นภาพว่าเงินวิ่งทางไหน → ภาพรวมระบบ
  • อยากเห็นของจริงวิ่ง → Quickstart
  • จะจำลองเคสที่ศัพท์เหล่านี้โผล่มา → Sandbox
  • ชื่อ field ตัวไหนใช้ตัวไหนเลิกใช้ → พจนานุกรม field

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