Skip to content

พจนานุกรม field ​

หน้านี้คือรายชื่อฟิลด์ทั้งหมดที่คุณจะเจอ ใช้เป็นที่อ้างอิงตอนเขียน schema ฝั่งคุณ

กติกา 6 ข้อที่ใช้กับทุกฟิลด์ ​

#กติกาตัวอย่าง
1ฟิลด์จำนวนเงินลงท้าย _minor ทุกตัว ไม่มีข้อยกเว้น และเป็น integer หน่วยสตางค์5,000.00 บาท = 500000 · 75.00 บาท = 7500
2เวลาเป็น RFC3339 UTC ลงท้าย Z มิลลิวินาที 3 หลักเสมอ"2026-08-21T04:12:33.881Z" · 🔴 ไม่มี offset ไทยในทุก payload
3"มีเสมอ" = รับประกันว่ามี key · optional = อาจไม่มี key นั้นเลย ไม่ใช่ nullif ('legs' in obj) ปลอดภัยกว่า obj.legs !== null
4ฟิลด์ที่เป็น null ได้ จะระบุไว้ชัดในคอลัมน์ "มีเสมอ"late_slip_accepted_until: null แปลว่าไม่มีหน้าต่างอัปสลิปย้อนหลัง
5open enum เพิ่มค่าใหม่ได้โดยไม่นับเป็น breaking change ⇒ ทุก switch ต้องมี default:reasons[].code, rejection.code, balance_transaction.type, ประเภทค่าธรรมเนียม
6ตัวเลขนับ (leg_count, settled_splits, attempt) เป็น integer ธรรมดา ไม่ใช่ _minorleg_count: 2

เลข minor ในภาษาที่ float ทำร้ายคุณได้

amount_minor เป็น integer ⇒ ห้ามแปลงเป็น float ทุกกรณี · JavaScript ปลอดภัยถึง 9,007,199,254,740,991 สตางค์ (~90 ล้านล้านบาท) แต่ ห้ามใช้ parseFloat แล้วคูณ 100 กลับไปกลับมา

PHP: ใช้ int และ intdiv() ไม่ใช่ / · MySQL/Postgres: คอลัมน์ต้องเป็น BIGINT ไม่ใช่ DECIMAL และไม่ใช่ INT (ยอดสะสมรายเดือนของ merchant ขนาดกลางล้น INT ภายในปีเดียว)


🔴 สี่ยอด X/V/A/E ที่สับสนกันบ่อยที่สุด ​

นี่คือจุดที่ทำให้ merchant เสียเงินจริงมากที่สุดในเอกสารทั้งชุด อ่านให้จบก่อนเขียนโค้ดเครดิต

ฟิลด์แปลว่าอะไรเอาไปทำอะไร
ordered_amount_minor (X)ยอดที่ สั่งไว้ ตอนสร้างรายการใช้เทียบว่าโอนครบไหม · แสดงในใบเสร็จ
transferred_amount_minor (V)ยอดที่ธนาคารยืนยันว่า โอนจริงตรวจสอบ exact/short/over; ห้ามใช้เครดิตโดยตรงเมื่อ overpay
amount_received_minor (A)ยอดที่ระบบ รับเข้า order นี้🔴 เครดิตให้ผู้ใช้ปลายทางด้วยตัวนี้ · ไม่เกินยอดที่สั่ง
overpayment.amount_minor (E)ยอดส่วนเกิน; ไม่มี/ถือเป็น 0 เมื่อไม่ overpayเปิด claim ให้ผู้ใช้ติดต่อ support · ห้ามเครดิตหรือคืนอัตโนมัติ

ค่าธรรมเนียมและยอดกระเป๋าเป็นอีกสองค่าคนละหน้าที่: fee_total_minor และ credited_amount_minor โดยความสัมพันธ์นี้เป็นจริงทุกใบ ไม่มีข้อยกเว้น

0 < amount_received_minor = min(transferred_amount_minor, ordered_amount_minor)
0 ≤ fee_total_minor ≤ amount_received_minor
amount_received_minor − fee_total_minor = credited_amount_minor
same-slip overpay: transferred_amount_minor = amount_received_minor + overpayment.amount_minor

transfer เพิ่มหลังรายการ settled (D17) สร้าง claim/flag ใหม่ แต่ไม่แก้ transferred_amount_minor หรือ amount_received_minor ของ settlement evidence เดิม

ตัวอย่างจริง — ผู้ใช้เติม 5,000.00 บาท ค่าธรรมเนียม 75.00 บาท

json
{
  "ordered_amount_minor": 500000,
  "transferred_amount_minor": 500000,
  "amount_received_minor": 500000,
  "fee_total_minor": 7500,
  "credited_amount_minor": 492500,
  "partial": false,
  "settled_via": "auto"
}
  • ผู้ใช้ปลายทางต้องได้ 5,000.00 บาท (amount_received_minor) — exact จึงเท่ากับยอดโอนจริง; ค่าธรรมเนียมเป็นของ merchant
  • กระเป๋า merchant ขยับ +4,925.00 บาท (credited_amount_minor)

ตัวอย่างเคสโอนขาดที่ผิดเฉพาะยอด — สั่ง 5,000.00 โอนจริง 3,000.00 และ non-amount อีก 5 มิติผ่าน ระบบรับยอดจริงอัตโนมัติ

json
{
  "ordered_amount_minor": 500000,
  "transferred_amount_minor": 300000,
  "amount_received_minor": 300000,
  "fee_total_minor": 4500,
  "credited_amount_minor": 295500,
  "partial": true,
  "settled_via": "auto"
}

ตัวอย่างโอนเกิน — สั่ง 5,000.00 แต่ธนาคารยืนยัน 5,300.00; รายการสำเร็จทันทีที่ 5,000.00

json
{
  "ordered_amount_minor": 500000,
  "transferred_amount_minor": 530000,
  "amount_received_minor": 500000,
  "fee_total_minor": 7500,
  "credited_amount_minor": 492500,
  "partial": false,
  "overpayment": {
    "claim_id": "clm_01K3PB6QW1M8HN0DZ2XKQ7RV4T",
    "cause": "same_transfer_excess",
    "amount_minor": 30000,
    "slip_received_at": "2026-08-21T04:19:02.180Z",
    "status": "open",
    "action_required": "contact_support"
  }
}

เครดิตผู้ใช้และคิด fee เฉพาะ 500000; แสดงเลข claim และให้ติดต่อ support เรื่อง 30000 โดยไม่รอให้รายการฝากสำเร็จและไม่คืนอัตโนมัติ

สองความผิดพลาดที่ตรงข้ามกัน

เครดิตผู้ใช้ด้วย credited_amount_minor ⇒ ผู้ใช้ได้เงินขาดเท่ากับค่าธรรมเนียมทุกออร์เดอร์ ⇒ ลูกค้าร้องเรียนตั้งแต่วันแรก ขยับกระเป๋าด้วย amount_received_minor ⇒ ยอดในระบบคุณสูงกว่าความจริงเท่ากับค่าธรรมเนียมทั้งเดือน ⇒ รู้ตอนกระทบยอดสิ้นเดือนแล้วตามแก้ไม่ทัน

ทางกัน: เก็บ X/V/A/E แยกกัน และเก็บ fee/merchant credit แยกเพิ่ม แล้ว assert ทีละใบ ใน job รายวันว่า A = min(V,X), amount_received_minor − fee_total_minor = credited_amount_minor และกรณี same-slip overpay ให้ V = A + E

credited_amount_minor ยังถอนไม่ได้ทันที

ยอดที่เพิ่งเครดิตเข้าไปอยู่ในถัง pending_clearing ก่อน แล้วย้ายไป available ตอน clearing_available_at (ปกติ T+72 ชั่วโมง) — คุณจะได้ event balance.cleared ตอนนั้น; ถ้าเงินถูก hold ค่าเวลานี้เป็น null จนกว่าจะระบุเวลาปล่อยได้

🔴 balance.cleared คือ การย้ายถัง ไม่ใช่เงินเข้าใหม่ ⇒ ห้ามบวกยอดซ้ำ · สร้างรายการถอนก่อนถึงเวลานั้นจะได้ 402 insufficient_balance


ซองของทุก event (webhook envelope) ​

โครงนี้เหมือนกันทุก event ไม่มีข้อยกเว้น

ฟิลด์ชนิดมีเสมอความหมาย
idstring (evt_ + ULID)✅id ของ event · ไม่ซ้ำตลอดกาล · ค่าเดิมทุก attempt และทุกครั้งที่สั่งส่งซ้ำ ⇒ ใช้เป็น unique key กันซ้ำได้
objectstring✅ค่าคงที่ "event"
typestring (open enum)✅ชื่อ event เช่น deposit.settled · 🔴 บอกแค่ "มีอะไรเกิดขึ้น" ห้ามใช้ตัดสินใจเรื่องเงิน
api_versionstring YYYY-MM-DD✅เวอร์ชันที่คุณตรึงไว้ ณ ตอนสร้าง event
livemodeboolean✅false = sandbox · 🔴 event ของคนละโหมดไม่มีทางข้ามกัน — ตรวจให้ตรง environment
created_atstring RFC3339 UTC✅เวลาที่ สร้าง event ไม่เปลี่ยนตอน retry
sequenceinteger (int64)✅เพิ่มขึ้นเสมอต่อ merchant · ใช้ตรวจว่ามี event หายไหม · ช่องว่างชั่วคราวเป็นเรื่องปกติ (ของที่ยัง retry อยู่) · ช่องว่างที่ค้างเกิน 4 วัน = หายจริง
account.merchant_idstring (mch_ + ULID)✅บัญชี merchant ที่ event นี้เป็นของ
account.white_label_idstring (wl_ + ULID)✅แบรนด์ที่ merchant นี้สังกัด
request.idstring (req_ + ULID)optionalมีเมื่อ event เกิดจากการเรียก API · ตรงกับ request_id ในตัว error body
request.idempotency_keystringoptionalkey ที่คุณส่งมาตอนสร้าง
data.objectobject✅business object เต็มใบ ณ ขณะนั้น ⇒ ไม่ต้อง GET กลับมาถาม
data.previous_attributesobject | null✅ (ยกเว้น object ที่แก้ไม่ได้)เฉพาะ key ที่เปลี่ยนจริง + version เดิม · null สำหรับ event *.created · ไม่มี key นี้เลย สำหรับ balance_transaction

version คือหัวใจของการเขียนสถานะลง DB

data.object.version เป็น integer ที่เพิ่มทุกครั้งที่ object เปลี่ยน และ ไม่มีทางที่ event สองใบของ object เดียวกันจะมี version เท่ากัน

⇒ เขียนลง DB ด้วย UPDATE … WHERE id = $1 AND version < $new แล้วเช็ค rowCount · rowCount = 0 แปลว่า event เก่ามาทีหลัง ให้ตอบ 200 แล้วไม่ทำอะไร · ดู version guard


object deposit_order ​

แกนหลัก — มีในทุก event ฝั่งฝาก ​

ฟิลด์ชนิดหน่วยมีเสมอความหมาย
idstring (dep_ + ULID)—✅id ของรายการฝาก · 🔴 ห้าม parse ห้ามเดา prefix · คอลัมน์ฝั่งคุณต้องเป็น text ไม่ใช่ varchar(20)
objectstring—✅ค่าคงที่ "deposit_order"
versioninteger—✅ตัวนับการเปลี่ยนแปลง ใช้ทำ version guard
statusstring (closed enum 9 ค่า)—✅สถานะ ดู State machine
is_terminalboolean—✅🔴 จบจริงหรือยัง — ผูกกับ late_slip_accepted_until ไม่ใช่กับชื่อสถานะ
merchant_referencestring ≤ 64—✅เลขอ้างอิงฝั่งคุณที่ส่งมาตอนสร้าง
amount_minorintegerสตางค์✅ยอดที่สั่งไว้ตอนสร้างรายการ
currencystring—✅ปัจจุบัน "THB" เท่านั้น
created_atstring RFC3339 UTC—✅เวลาที่สร้างรายการ
expires_atstring RFC3339 UTC—✅🔴 หมดเวลาเมื่อไหร่ — ใช้ค่านี้เสมอ ห้าม hardcode ค่าจริงอาจสั้นเพียง ~4 นาที และ เปลี่ยนได้ เมื่อรายการเลื่อนจาก awaiting_liquidity ไป awaiting_payment
checkout_urlstring URL—optionalหน้าชำระเงินสำเร็จรูปบนโดเมนแบรนด์ · เป็นทางที่ปลอดภัยกว่าและเป็นค่าที่แนะนำ
liquidity_source"internal" | "external_gateway"—optionalที่มาของช่องทาง · 🔴 ห้ามใช้ตัดสินใจเครดิต ใช้ทำรายงานเท่านั้น
leg_countinteger—optionalจำนวนปลายทางที่ต้องโอน · > 1 ได้
legs[]array—optionalรายการปลายทาง ดูตารางถัดไป · ว่าง [] ตอน awaiting_liquidity
senderobject—✅บัญชีผู้โอนที่คุณแจ้งไว้ (ถูก mask แล้ว)
metadataobject ≤ 20 key—optionalค่าที่คุณส่งมาเอง เราคืนกลับเหมือนเดิม · ค่าละ ≤ 500 ไบต์

legs[] — ปลายทางที่ผู้ใช้ต้องโอนไป ​

ฟิลด์ชนิดหน่วยมีเสมอความหมาย
leg_refstring—✅id ของก้อนนี้ที่คุณเห็น · 🔴 ฝั่งผู้ถอนได้ค่าคนละตัว จับคู่ข้าม merchant ไม่ได้โดยตั้งใจ
sequenceinteger—✅ลำดับที่เท่าไหร่ (1, 2, …) ใช้ทำ "ขั้นที่ 1 จาก 2"
amount_minorintegerสตางค์✅ยอดของก้อนนี้ · ผลรวมทุก leg = amount_minor ของรายการ
expires_atstring RFC3339 UTC—✅หมดอายุของก้อนนี้
leg_status"pending" | "complete"—✅🔴 ไม่ใช่สถานะเงิน ใช้ทำ progress เท่านั้น
methodstring—✅ช่องทาง เช่น "promptpay_qr"
qr_payloadstring—optionalสตริง QR ดิบ · 🔴 ห้าม log ห้ามเก็บลง DB ห้ามส่งต่อ ใช้แสดงผลแล้วทิ้ง · แบรนด์ปิดการส่งฟิลด์นี้ได้
qr_image_urlstring URL—optionalภาพ QR บนโดเมนของแบรนด์
receiver.bank_codestring 3 หลัก—✅รหัสธนาคารปลายทาง
receiver.bank_name_thstring—✅ชื่อธนาคารภาษาไทย
receiver.account_number_maskedstring—✅เลขบัญชีปลายทางแบบปิดบัง เช่น "xxx-x-x1234" · 🔴 ไม่มีเลขบัญชีเต็มให้ทุกกรณี
receiver.account_name_maskedstring—✅ชื่อผู้รับแบบปิดบัง เช่น "ส*** ใ***"
json
{
  "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": "ว*** ป***"
      }
    }
  ]
}

legs[0] ไม่ใช่ทั้งหมด

รายการเดียวถูกซอยเป็นหลายปลายทางได้ ⇒ UI ต้องรองรับ "ขั้นที่ 1 จาก 2" · โค้ดที่อ่าน legs[0].qr_payload แล้วจบ จะทำให้ผู้ใช้โอนไม่ครบ แล้วรายการเข้าคิวตรวจสอบทั้งหมด

sender — บัญชีผู้โอนที่คุณแจ้งไว้ ​

ฟิลด์ชนิดมีเสมอความหมาย
sender.bank_codestring 3 หลัก✅ธนาคารต้นทาง
sender.account_number_maskedstring✅เลขบัญชีที่คุณแจ้ง แสดงแบบปิดบัง
sender.account_name_th_maskedstring✅ชื่อที่คุณแจ้ง แสดงแบบปิดบัง

🔴 เราไม่ส่งชื่อหรือเลขบัญชีที่อ่านได้จากสลิปจริงกลับไปให้เลย — เพราะคนที่โอนจริงอาจไม่ใช่คนเดียวกับที่คุณแจ้ง (เช่นโอนจากบัญชีคู่สมรส) และนั่นเป็นข้อมูลส่วนบุคคลของบุคคลที่สาม · สิ่งที่ได้คือผลเทียบแบบ boolean ใน verification{}

verification{} — ผลตรวจ 6 มิติ (boolean ล้วน) ​

ฟิลด์ชนิดความหมาย
verification.amount_exactbooleanยอดตรงเป๊ะ (ไม่มี tolerance)
verification.within_time_windowbooleanเวลาโอนอยู่ในช่วงของรายการ
verification.sender_bank_matchbooleanธนาคารต้นทางตรง
verification.sender_account_matchbooleanเลขบัญชีต้นทางตรง
verification.sender_name_matchbooleanชื่อบัญชีต้นทางตรง
verification.receiver_matchbooleanปลายทางตรง

ยอดตรงต้องครบ 6 ข้อจึงเป็น exact · เมื่อ non-amount อีก 5 ข้อผ่าน short-pay จะ settle อัตโนมัติที่ A=V<X และ same-slip overpay จะ settle ที่ยอด order พร้อม claim · มีเหตุ non-amount/ambiguity/policy อื่นจริงจึงเข้า under_review ไม่ใช่ปฏิเสธ

review{} — มีเมื่อ status = "under_review" ​

ฟิลด์ชนิดมีเสมอความหมาย
review.opened_atstring RFC3339 UTC✅เวลาที่เข้าคิว
review.escalatedboolean✅ถูกยกระดับแล้วหรือยัง · 🔴 การยกระดับ ไม่แตะเงินและไม่เปลี่ยนสถานะ แต่ version ขยับ
review.four_eyes_requiredboolean✅ต้องมีผู้อนุมัติสองคน ⇒ บอกผู้ใช้ได้ว่า "อาจใช้เวลานานขึ้น"
review.estimated_resolution_bucket"under_15m" | "under_1h" | "under_4h" | "extended"✅ช่วงเวลาโดยประมาณ · เอาไปแสดงให้ผู้ใช้ได้ตรง ๆ
review.reasons[]array✅เหตุผล เรียงตามความสำคัญ · reasons[0] คือตัวที่ควรแสดงถ้าจอมีที่เดียว

review.reasons[] แต่ละตัว

ฟิลด์ชนิดมีเสมอความหมาย
codestring (open enum)✅รหัสเหตุผล เช่น SENDER_NAME_MISMATCH
display_thstring✅🔴 ข้อความไทยที่เราเขียนมาให้แล้ว — เอาไปแสดงได้ทันที ไม่ต้องเขียนเอง
display_enstring✅ข้อความอังกฤษ
actionable_by"end_user" | "support"✅ใครแก้ได้ · end_user = ผู้ใช้ทำอะไรได้ · support = รอทีมงานอย่างเดียว
detailobjectoptionalรายละเอียดเพิ่ม ต่างกันตาม code · 🔴 RECEIVER_MISMATCH ไม่มี detail เด็ดขาด

slip{} และ liquidity{} ​

ฟิลด์ชนิดมีเสมอความหมาย
slip.received_atstring RFC3339 UTCoptionalเวลาที่ระบบรับสลิป
slip.is_latebooleanoptionaltrue = เป็นสลิปที่อัปย้อนหลังหลังรายการหมดอายุหรือถูกยกเลิก
liquidity.wait_started_atstring RFC3339 UTCoptionalเริ่มรอตั้งแต่เมื่อไหร่ (มีตอน awaiting_liquidity)
liquidity.estimated_wait_msinteger (มิลลิวินาที)optionalประมาณการเวลารอ
liquidity.retry_interval_msinteger (มิลลิวินาที)optionalระบบพยายามจับคู่ใหม่ทุกกี่มิลลิวินาที
liquidity.suggested_amounts_minor[]array of integerสตางค์ยอดที่จับคู่ได้ทันทีถ้าผู้ใช้ยอมเปลี่ยน ⇒ เสนอเป็นปุ่มให้กด

ฟิลด์ที่โผล่มาตอน settled ​

ฟิลด์ชนิดหน่วยมีเสมอความหมาย
ordered_amount_minorintegerสตางค์✅ยอดที่สั่งไว้ตอนสร้าง
transferred_amount_minorintegerสตางค์✅ยอดที่ธนาคารยืนยันว่าโอนจริง (V)
amount_received_minorintegerสตางค์✅🔴 ยอดที่ระบบรับเข้า order (A) — เครดิตผู้ใช้ด้วยตัวนี้
overpaymentobject | null—required เมื่อ settled/reversedflag/claim ของ E; เป็น object เมื่อโอนเกิน มิฉะนั้นเป็น null · ห้ามละ key ใน terminal financial snapshot
fee_total_minorintegerสตางค์✅ค่าธรรมเนียมรวมที่เก็บจาก merchant
credited_amount_minorintegerสตางค์✅🔴 ยอดเข้ากระเป๋า merchant — ขยับกระเป๋าด้วยตัวนี้
partialboolean—✅true = รับ short-pay ตามยอดจริง (A=V<X); ปกติ settled_via:'auto' เมื่อ non-amount 5/5 ผ่าน และเป็น 'review' เฉพาะเคสที่มีเหตุอื่นแล้วเจ้าหน้าที่อนุมัติ · same-slip overpay เป็น false และมี overpayment{}
settled_via"auto" | "review"—✅ผ่านการตรวจอัตโนมัติ หรือผ่านการตัดสินของเจ้าหน้าที่
balance_transaction_idstring (btxn_ + ULID)—✅รายการเคลื่อนไหวยอดคงเหลือที่คู่กัน
clearing_available_atstring RFC3339 UTC | null—✅เวลาที่ยอดนี้ย้ายไปถัง available; เป็น null ขณะที่เงินถูก hold และยังระบุเวลาปล่อยไม่ได้
fee_breakdown[]array—✅รายละเอียดค่าธรรมเนียม ดูด้านล่าง

fee_breakdown[] — แยกตามประเภทค่าธรรมเนียม

ฟิลด์ชนิดหน่วยมีเสมอความหมาย
componentstring (open enum)—✅ประเภท: "processing_fee" | "fixed_fee" | "vat"
amount_minorintegerสตางค์✅จำนวนของประเภทนี้
rate_bpsintegerbasis pointoptionalอัตราคิด เช่น 150 = 1.50% (มีเฉพาะประเภทที่คิดเป็นอัตรา)
json
{
  "fee_total_minor": 7500,
  "fee_breakdown": [
    { "component": "processing_fee", "amount_minor": 6500, "rate_bps": 130 },
    { "component": "fixed_fee",      "amount_minor": 1000 }
  ]
}

🔴 แยกตามประเภทเท่านั้น ไม่มีการแยกว่าเงินก้อนนี้ไปถึงใคร ⇒ เวลาโต้แย้งเรื่องค่าธรรมเนียม ให้เก็บ array นี้ดิบ ๆ ไว้ และ parse ด้วย component เสมอ · component เป็น open enum ⇒ ต้องมี default:

ฟิลด์ตอนหมดเวลา ยกเลิก ปฏิเสธ กลับรายการ และโอนซ้ำ ​

ฟิลด์ชนิดมีเสมอความหมาย
late_slip_accepted_untilstring RFC3339 UTC | null✅ ในเหตุการณ์หมดเวลา/ยกเลิก🔴 นาฬิกาเดียวของหน้าต่างอัปสลิปย้อนหลัง · null = ไม่เคยมีปลายทางให้โอน ⇒ ปิดออร์เดอร์ได้เลย
expired_reasonstringoptionalปัจจุบันมีค่าเดียว "no_payment"
canceled_by"merchant" | "end_user" | "system"optionalใครเป็นคนยกเลิก
cancel_reason_codestring (open enum)optional"no_liquidity_available" | "merchant_request" | "end_user_request" | "approval_ttl_expired"
rejection.codestring (open enum)optionalรหัสเหตุผลที่ไม่รับสลิป
rejection.display_th / display_enstringoptionalข้อความที่เอาไปแสดงได้ทันที · 🔴 เราไม่ส่งบันทึกภายในของเจ้าหน้าที่ออกมา
reversal.reason_codestring (open enum)optional"BANK_RECALL" | "ACCOUNT_FROZEN" | "SLIP_INVALIDATED_AFTER_SETTLEMENT" | "DUPLICATE_CREDIT" | "OPS_ERROR" | "MERCHANT_DISPUTE_LOST"
reversal.reverses_balance_transaction_idstring (btxn_)optionalชี้ไปรายการเงินเดิมที่ถูกกลับ ⇒ กลับรายการฝั่งคุณได้แม่นยำ
reversal.balance_transaction_idstring (btxn_)optionalรายการเงินของการกลับรายการเอง
overpayment.claim_idstring✅ เมื่อ overpayment เป็น objectเลขเรื่องของยอดเกิน
overpayment.causesame_transfer_excess | additional_transfer✅ เมื่อ overpayment เป็น objectเกินในสลิปเดียวหรือมี transfer เพิ่ม
overpayment.amount_minorinteger สตางค์✅ เมื่อ overpayment เป็น objectยอดส่วนเกิน
overpayment.slip_received_atstring RFC3339 UTC✅ เมื่อ overpayment เป็น objectเวลาที่ได้รับสลิปที่ทำให้เกิด flag
overpayment.statusopen✅ เมื่อ overpayment เป็น objectimmutable snapshot ตอนเปิดเรื่อง; สถานะล่าสุดอ่าน Claim.version
overpayment.action_requiredcontact_support✅ เมื่อ overpayment เป็น objectให้ผู้ใช้ติดต่อ support พร้อม claim id · ไม่มี auto-refund
overpayment.expected_response_atstring RFC3339 UTCoptionalเวลาตอบครั้งแรก ถ้ากำหนดแล้ว

overpayment{} มาแล้ว status ยังเป็น settled

same-slip overpay มากับ deposit.settled ใบเดียว โดยเครดิตไม่เกินยอดที่สร้าง; transfer เพิ่มภายหลังมาเป็น deposit.overpaid. ทั้งสองกรณีเปิด claim และ data.object.status ยังเป็น "settled" — ให้ผู้ใช้ติดต่อ support, อย่าบอกว่าคืนเงินแล้ว

⇒ โค้ดที่ทำ if (event.type === 'deposit.overpaid') markFailed() จะยกเลิกออร์เดอร์ที่สำเร็จไปแล้ว


object withdrawal_order ​

ฟิลด์ชนิดหน่วยมีเสมอความหมาย
idstring (wdr_ + ULID)—✅id ของรายการถอน
objectstring—✅ค่าคงที่ "withdrawal_order"
versioninteger—✅ตัวนับการเปลี่ยนแปลง
statusstring (closed enum 7 ค่า)—✅สถานะ ดู State machine
merchant_referencestring ≤ 64—✅เลขอ้างอิงฝั่งคุณ
amount_total_minorintegerสตางค์✅ยอดถอนที่สั่งไว้
fee_total_minorintegerสตางค์✅ค่าธรรมเนียมรวม ที่ล็อกไว้ตั้งแต่ตอนสร้าง · 🔴 ฝั่งถอนค่าธรรมเนียม บวกเพิ่ม ไม่ได้หักจากยอดถอน
total_locked_minorintegerสตางค์✅ ตอนสร้างยอดที่ถูกล็อกจริง = amount_total_minor + fee_total_minor
fee_recognized_minorintegerสตางค์✅ค่าธรรมเนียมที่ถูกใช้ไปแล้วตามสัดส่วนที่จ่ายออก
filled_minorintegerสตางค์✅🔴 ยอดที่จ่ายออกไปแล้วจริง — แหล่งความจริงเดียว
remaining_minorintegerสตางค์✅🔴 ยอดที่ยังเหลือ — แหล่งความจริงเดียว
refunded_minorintegerสตางค์✅ ตอนปิดใบยอดที่คืนกลับเข้ายอดคงเหลือจริง
settled_splitsinteger—✅จำนวนก้อนที่ปิดไปแล้ว
max_splitsinteger—✅ซอยได้สูงสุดกี่ก้อน
expires_atstring RFC3339 UTC—✅หลังเวลานี้ใบจะหลุดจากคิวจับคู่
fills[]array—optionalรายละเอียดก้อนที่ปิดแล้ว (มีใน GET)
disputeobject—optionalมีเมื่อผู้ถอนแจ้งว่ายังไม่ได้รับเงิน
refund_of_deposit_idstring (dep_)—optionalมีเมื่อใบนี้เป็นการคืนเงินให้ผู้ใช้ (การคืนเงินคือรายการถอนประเภทหนึ่ง)
replacement_of_withdrawal_idstring (wdr_)—optionalมีเมื่อใบนี้ถูกเปิดใหม่แทนใบเดิมที่ก้อนถูกกลับรายการ

filled_minor / remaining_minor ห้ามคำนวณเอง

คุณจะได้ withdrawal.filled หลายครั้งต่อหนึ่งใบ และลำดับที่มาถึงไม่การันตี

⇒ ห้ามเขียน remaining += x และห้าม SUM() จากตาราง event ฝั่งคุณ · ให้เขียนทับด้วยค่า filled_minor / remaining_minor จาก payload ผ่าน version guard เท่านั้น · ตารางก้อนย่อยฝั่งคุณมีไว้ทำ timeline และตรวจสอบย้อนหลัง ไม่ใช่ไว้ขับตรรกะเรื่องเงิน

fills[] และ fill{} ใน event withdrawal.filled ​

ฟิลด์ชนิดหน่วยมีเสมอความหมาย
fill.leg_refstring—✅🔴 id ของก้อนนี้ ใช้เป็น unique key ของบรรทัด fill ฝั่งคุณ
fill.sequenceinteger—✅ก้อนที่เท่าไหร่
fill.amount_minorintegerสตางค์✅ยอดของก้อนนี้
fill.fee_recognized_minorintegerสตางค์✅ค่าธรรมเนียมที่ถูกใช้ไปกับก้อนนี้
fill.settled_atstring RFC3339 UTC—✅เวลาที่ก้อนนี้ปิด
fill.balance_transaction_idstring (btxn_)—✅รายการเคลื่อนไหวยอดคงเหลือที่คู่กัน
fill.auto_confirm_atstring RFC3339 UTC—✅ถ้าผู้ถอนไม่กดอะไร ระบบจะถือว่ารับเงินแล้วเมื่อถึงเวลานี้ (3 ชั่วโมง)
fill.receipt_statestring—optional (ใน GET)สถานะการยืนยันรับเงินของก้อนนี้

วิธีเก็บบรรทัด fill ที่ทนต่อการสลับลำดับ

  • บรรทัด fill: INSERT … ON CONFLICT (leg_ref) DO NOTHING — leg_ref ไม่ซ้ำ ⇒ ได้ event เดิมซ้ำก็ไม่เพิ่มบรรทัด
  • ยอดรวมของใบ: UPDATE … SET filled_minor = $x, remaining_minor = $y WHERE id = $1 AND version < $v — เขียนทับเฉพาะเมื่อ version สูงกว่า

สองอย่างนี้ทำคนละหน้าที่ ต้องมีทั้งคู่ · ดู หนึ่งใบรับได้หลายก้อน

dispute{} ​

ฟิลด์ชนิดมีเสมอความหมาย
dispute.idstring✅เลขข้อพิพาท
dispute.reason_codestring (open enum)✅เหตุผลที่เปิด
dispute.amount_minorinteger สตางค์✅ยอดที่พิพาท
dispute.opened_atstring RFC3339 UTC✅เปิดเมื่อไหร่
dispute.respond_bystring RFC3339 UTC✅🔴 ต้องส่งหลักฐานก่อนเวลานี้ ผ่าน POST /v1/pay/disputes/{id}/evidence
dispute.evidence_urlstring URLoptionalลิงก์หน้าส่งหลักฐาน
funds_on_holdboolean✅true = ยอดที่เกี่ยวข้องถูกพักไว้ชั่วคราว · status ของใบ ไม่เปลี่ยน

object balance_transaction ​

object นี้ แก้ไม่ได้ ⇒ ไม่มี version และไม่มี previous_attributes ⇒ กันซ้ำด้วย id แล้ว สะสม ไม่ใช่เขียนทับ

ฟิลด์ชนิดหน่วยมีเสมอความหมาย
idstring (btxn_ + ULID)—✅🔴 unique key สำหรับกันบันทึกซ้ำ
objectstring—✅ค่าคงที่ "balance_transaction"
typestring (open enum)—✅"deposit_settled" | "clearing_released" | "withdrawal_reserved" | "withdrawal_settled" | "withdrawal_released" | "fee" | "reversal" | "adjustment"
direction"credit" | "debit"—✅ทิศทาง
amount_minorintegerสตางค์✅จำนวน (ค่าบวกเสมอ ทิศทางอยู่ที่ direction)
currencystring—✅"THB"
bucket_from"available" | "pending_clearing" | "reserved" | null—✅ย้ายออกจากถังไหน
bucket_toเหมือน bucket_from—✅ย้ายเข้าถังไหน
available_atstring RFC3339 UTC—optionalยอดนี้จะถอนได้เมื่อไหร่
balance_afterobject—✅ยอดคงเหลือ 3 ถังหลังรายการนี้ · 🔴 ใช้ตรวจสอบเท่านั้น ห้ามใช้เขียนทับยอดในระบบคุณ (event สลับลำดับได้)
source.type / source.idstring—✅ต้นทาง เช่น {"type":"deposit","id":"dep_…"}
reverses_balance_transaction_idstring (btxn_)—optionalมีเฉพาะรายการกลับรายการ
reason_code / reason_display_thstring—optionalเหตุผลของการกลับรายการ
created_atstring RFC3339 UTC—✅เวลาที่เกิดรายการ
json
{
  "id": "btxn_01K3PB6QW1M8HN0DZ2XKQ7RV4T",
  "object": "balance_transaction",
  "type": "reversal",
  "direction": "debit",
  "amount_minor": 492500,
  "currency": "THB",
  "bucket_from": "pending_clearing",
  "bucket_to": null,
  "balance_after": { "available_minor": 1284300, "pending_clearing_minor": 0, "reserved_minor": 200000 },
  "source": { "type": "deposit", "id": "dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B" },
  "reverses_balance_transaction_id": "btxn_01K3M9ZZ4P7QW2NHT0RXKD5V8C",
  "reason_code": "SLIP_INVALIDATED_AFTER_SETTLEMENT",
  "reason_display_th": "รายการถูกยกเลิกหลังจากตรวจพบว่าสลิปไม่ถูกต้อง",
  "created_at": "2026-08-21T09:41:19.884Z"
}

ยอดคงเหลือ 3 ถัง (GET /v1/pay/balance) ​

ฟิลด์ชนิดหน่วยความหมาย
available_minorintegerสตางค์🔴 ยอดที่สร้างรายการถอนได้จริง — คำนวณเงินถอนจากตัวนี้เท่านั้น
pending_clearing_minorintegerสตางค์ยอดที่เครดิตแล้วแต่ยังไม่ครบ 72 ชั่วโมง ⇒ ยังถอนไม่ได้
reserved_minorintegerสตางค์ยอดที่ถูกล็อกไว้กับรายการถอนที่เปิดค้างอยู่
currencystring—"THB"
as_ofstring RFC3339 UTC—ยอดนี้เป็นของเวลาไหน

แสดงให้ผู้ใช้เห็น ใช้ available_minor + pending_clearing_minor · คำนวณว่าถอนได้เท่าไหร่ ใช้ available_minor อย่างเดียว


ฟิลด์ที่คุณส่งมาให้เรา ​

POST /v1/pay/deposits ​

ฟิลด์ชนิดบังคับหมายเหตุ
amount_minorinteger สตางค์✅🔴 ยอดกลม ไม่มีเศษสตางค์ — เราจับคู่ด้วยตัวตนผู้โอน ไม่ใช่ด้วยเศษสตางค์
currencystring✅"THB"
merchant_referencestring ≤ 64✅ไม่ซ้ำต่อ merchant
end_user_refstring ≤ 64✅id ผู้ใช้ในระบบคุณ · ใช้กับ GET /v1/pay/senders ด้วย
sender{} หรือ sender_idobject / string✅🔴 ต้องมีอย่างใดอย่างหนึ่ง ไม่มี = 422 sender_required
sender.bank_codestring 3 หลัก✅ (ถ้าใช้ sender{})จาก GET /v1/pay/banks
sender.account_numberstring ตัวเลขล้วน✅ (ถ้าใช้ sender{})ตัด - และช่องว่างออกก่อนส่ง
sender.account_name_thstring✅ (ถ้าใช้ sender{})🔴 ห้ามมีคำนำหน้า (นาย/นาง/น.ส./คุณ)
sender.account_name_enstringoptionalช่วยให้ผ่านการตรวจอัตโนมัติมากขึ้น
client.device_fingerprintstringoptional จริง ๆ แต่ต้องส่งไม่ส่ง = เพดานรายการเปิดพร้อมกันเหลือ 1 และตกไป bucket rate limit รวม
client.ipstringเหมือนบน
client.user_agentstringเหมือนบน
fill_preference"any" | "internal_only"optionalค่าเริ่มต้น "any"
min_ttl_secondsinteger (วินาที)optional0–86,400; ถ้าไม่พอดีกับอายุรายการหลัง clamp จะได้ 422
expires_in_secondsinteger (วินาที)optional60–86,400 และถูกจำกัดด้วยนโยบายของแบรนด์อีกชั้น
return_urlstring URLoptional
metadataobject ≤ 20 keyoptionalค่าละ ≤ 500 ไบต์

POST /v1/pay/withdrawals ​

ฟิลด์ชนิดบังคับหมายเหตุ
amount_minorinteger สตางค์✅ยอดถอน (ยังไม่รวมค่าธรรมเนียม)
currencystring✅"THB"
merchant_referencestring ≤ 64✅
end_user_refstring ≤ 64✅
beneficiary_id หรือ beneficiary{}string / object✅🔴 บัญชีต้อง verified แล้ว ไม่งั้น 409 beneficiary_not_verified
beneficiary.bank_code / account_number / account_name_th / promptpay_idstring✅ (ถ้าใช้ beneficiary{})promptpay_id เป็น optional
client{}objectเหมือนฝั่งฝาก
metadataobjectoptional

Header ของทุก request ​

Headerบังคับหมายเหตุ
Authorization: Bearer sk_live_*✅เก็บใน secret manager หมุนได้โดยไม่ต้อง deploy
PG-Timestamp✅unix seconds · ต่างจากเวลาจริงได้ ±300 วินาที
PG-Nonce✅🔴 ใหม่ทุก attempt ห้ามซ้ำภายใน 10 นาที
PG-Version✅เวอร์ชัน API ที่คุณตรึงไว้ เช่น 2026-08-01 · 🔴 อยู่ใน canonical string ด้วย
Idempotency-Key✅ เฉพาะ POST🔴 คงเดิมตลอด logical operation · อยู่ใน canonical string ด้วย
PG-Signature✅v1= + HMAC-SHA256 hex ตัวพิมพ์เล็ก

Header ของ webhook ที่เรายิงไปหาคุณ ​

Headerหมายเหตุ
X-PG-Signatureรูปแบบ v1,kid=<kid>,t=<unix_seconds>,s=<base64url ไม่มี padding> · 🔴 parse แบบ key=value ห้าม parse ด้วยตำแหน่ง
X-PG-Event-Idต้องตรงกับ id ใน body
X-PG-Event-Typeชื่อ event
X-PG-Delivery-Idid ของการส่งครั้งนี้
X-PG-Endpoint-Idendpoint ปลายทางของการส่งครั้งนี้
X-PG-Attemptครั้งที่เท่าไหร่ (1–11)
X-PG-Resendoptional · ไม่มีใน delivery ปกติ · manual เมื่อกดส่งซ้ำ
X-PG-Api-Versionเวอร์ชันที่ render payload
X-PG-Livemodetrue / false

business event อยู่ใน body และ immutable ส่วน delivery metadata อยู่ใน header เท่านั้น Header กลุ่ม delivery ไม่อยู่ใน canonical signature จึงใช้เพื่อ log/trace หลัง verify เท่านั้น ห้ามใช้ตัดสินเรื่องเงิน สิทธิ์ หรือ state


ตารางคู่ที่สับสนกันบ่อย ​

คู่ที่สับสนต่างกันยังไงจำยังไง
transferred_amount_minor vs amount_received_minorsettlement evidence V vs ยอดที่รับเข้า/เครดิต Aต่างกันเมื่อ same-slip overpay; additional transfer อยู่ใน E แยกและไม่ทำให้ V สะสม
amount_minor vs amount_received_minoramount_minor = ยอดที่ สั่ง · amount_received_minor = ยอดที่ระบบรับเข้าต่างกันเมื่อ partial:true; overpay จะเท่ากันและมี flag
amount_received_minor vs credited_amount_minorยอดของ ผู้ใช้ vs ยอดของ merchantผู้ใช้ได้ A · merchant ได้ A หลังหักค่าธรรมเนียม
ordered_amount_minor vs amount_minorค่าเดียวกัน แต่ ordered_amount_minor โผล่มาใน deposit.settled เพื่อให้เทียบกับยอดธนาคารยืนยันและยอดรับเข้าได้ในใบเดียวใช้ transferred_amount_minor จำแนก short/exact/over แล้วเครดิต amount_received_minor
fee_total_minor (ฝาก) vs fee_total_minor (ถอน)ฝาก: หักออก จากยอดที่เข้ากระเป๋า · ถอน: บวกเพิ่ม จากยอดที่ต้องมีฝาก received − fee = credited · ถอน ต้องมี available ≥ amount + fee
fee_total_minor vs fee_recognized_minorล็อกไว้ทั้งก้อนตอนสร้าง vs ถูกใช้จริงตามสัดส่วนที่จ่ายออกแล้วใบที่หมดอายุกลางทางจะคืนส่วนต่างของสองค่านี้
filled_minor vs amount_total_minorจ่ายไปแล้ว vs สั่งไว้ทั้งหมดปิดใบได้เมื่อเท่ากัน และ remaining_minor = 0
remaining_minor vs reserved_minorยังไม่มีใครจอง vs มีคนกำลังโอนอยู่total = filled + reserved + remaining เสมอ
refunded_minor vs amount_total_minorยอดที่คืนจริง vs ยอดที่สั่ง🔴 ใบ expired ที่เคยจ่ายไปบางส่วน คืนแค่ refunded_minor
available_minor vs pending_clearing_minorถอนได้ vs ยังถอนไม่ได้จนครบ 72 ชั่วโมงแสดงผลใช้ผลรวม · คำนวณการถอนใช้ available_minor
expires_at (รายการ) vs legs[].expires_atหมดเวลาทั้งรายการ vs หมดเวลาของก้อนนั้นนับถอยหลังบนหน้าจอใช้ของรายการ
late_slip_accepted_until vs expires_atหน้าต่างอัปสลิปย้อนหลัง vs เวลาที่ต้องโอนให้เสร็จlate_slip_accepted_until ยาวกว่าเสมอ (อีก 24 ชั่วโมง)
is_terminal vs statusจบจริงหรือยัง vs ชื่อสถานะ🔴 เช็ค is_terminal ไม่ใช่ชื่อสถานะ
leg_ref vs deposit_id / id ของใบถอนid ของก้อนย่อย vs id ของใบleg_ref ของสองฝั่ง ไม่เท่ากัน โดยตั้งใจ
event.type vs data.object.statusมีอะไรเกิดขึ้น vs ความจริงตอนนี้🔴 ตัดสินใจจาก data.object.status
event.id vs X-PG-Delivery-Idid ของเหตุการณ์ใน body (คงเดิมทุก attempt) vs id ของการส่งใน header (เปลี่ยนทุกครั้ง)กันซ้ำด้วย event.id เท่านั้น
sequence vs versionลำดับ event ทั้งบัญชี vs ลำดับการเปลี่ยนแปลงของ object นั้นตรวจ event หายใช้ sequence · กัน event เก่าทับใหม่ใช้ version
merchant_reference vs idเลขของคุณ vs เลขของเรา🔴 เปิด ticket และกระทบยอดใช้ id ของเรา
balance.credited vs balance.clearedเงินเข้าใหม่ vs ย้ายถังจาก pending_clearing ไป available🔴 balance.cleared ห้ามบวกยอดซ้ำ

ชื่อที่เลิกใช้แล้ว ​

ถ้าเจอชื่อฝั่งซ้ายในเอกสารเก่าหรือในโค้ดของคุณ ให้เปลี่ยนเป็นฝั่งขวา

เลิกใช้ชื่อที่ถูก
amount_received · fee_total · credited_amountamount_received_minor · fee_total_minor · credited_amount_minor
payment_instructions[] · bank_account · promptpaylegs[]
allocation_idleg_ref
sourceliquidity_source
late_slip_untillate_slip_accepted_until
cancel_reasoncancel_reason_code
reversal_of · ledger_txn_idreversal.reverses_balance_transaction_id และ reversal.balance_transaction_id
reference · order_refmerchant_reference (ชื่อนี้ใช้ได้เฉพาะฝั่งคุณ)
decision_sourcesettled_via
เวลาแบบมี offset +07:00RFC3339 UTC ลงท้าย Z มิลลิวินาที 3 หลัก

ขั้นต่อไป ​

ถ้าคุณกำลัง…ไปที่
ออกแบบตารางฝั่งคุณState machine เพื่อดูว่าต้องมีคอลัมน์อะไรบ้าง
เขียน handler ของ webhookภาพรวมและซองข้อมูล · รายการ event ทั้งหมด
เจอค่ายอดถอนแสดงผิดลำดับไม่การันตี — version guard
อยากรู้ว่า error แต่ละตัวมี details อะไรError code ทั้งหมด
ย้ายมาจาก gateway เจ้าอื่นย้ายจากผู้ให้บริการเดิม
กำลังจะขึ้นจริงเช็กลิสต์ก่อน go-live

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