Skip to content

ค้นหารายการ ​

หน้านี้คือเครื่องมือที่ทีมของคุณใช้ตอนลูกค้าทัก ไม่ใช่ทางที่โค้ดใช้ขับ business logic

การตัดสินใจเรื่องเงินใช้ webhook + version guard เท่านั้น

GET เป็น fallback ของ webhook ไม่ใช่ตัวหลัก · ถ้าจะ poll ให้ backoff 3 → 5 → 10 → 30 วินาที แล้วหยุดเมื่อรายการเป็น terminal poll ทุก 1 วินาทีทุกออร์เดอร์จะชน 429 แล้ว backoff ทั้ง API key ⇒ ออร์เดอร์อื่นสร้างไม่ได้ตามไปด้วย

ดูรายการฝากรายใบ ​

bash
curl -sS "https://api.example-wl.com/v1/pay/deposits/dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B" \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "PG-Timestamp: 1755749553" \
  -H "PG-Nonce: 01JQA5T8V0X2Z4B6D8F0H2J4L6" \
  -H "PG-Version: 2026-08-01" \
  -H "PG-Signature: v1=8f2c1ab6e0d74f9b3c5a7e1d0f6b8a4c2e9d7b5a3c1f0e8d6b4a2c0e8f6d4b2a"
json
{
  "id": "dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B",
  "object": "deposit_order",
  "version": 6,
  "status": "under_review",
  "is_terminal": false,
  "merchant_reference": "ORD-9981",
  "amount_minor": 500000,
  "currency": "THB",
  "created_at": "2026-08-21T04:10:47.902Z",
  "expires_at": "2026-08-21T04:30:47.902Z",
  "liquidity_source": "internal",
  "review": {
    "opened_at": "2026-08-21T04:19:08.601Z",
    "escalated": false,
    "four_eyes_required": false,
    "estimated_resolution_bucket": "under_1h",
    "reasons": [
      {
        "code": "SENDER_NAME_MISMATCH",
        "display_th": "ชื่อบัญชีผู้โอนในสลิปไม่ตรงกับชื่อที่แจ้งไว้ตอนสร้างรายการ เงินของคุณไม่หาย ไม่ต้องโอนซ้ำ",
        "display_en": "The sender name on the slip does not match the name registered for this order.",
        "actionable_by": "end_user",
        "detail": { "registered_name_masked": "ป*** ส***", "matched": false }
      }
    ]
  },
  "slip": { "received_at": "2026-08-21T04:19:02.180Z", "is_late": false },
  "verification": {
    "amount_exact": true,
    "within_time_window": true,
    "sender_bank_match": true,
    "sender_account_match": true,
    "sender_name_match": false,
    "receiver_match": true
  },
  "sender": {
    "bank_code": "002",
    "account_number_masked": "xxx-x-x5567",
    "account_name_th_masked": "ป*** ส***"
  },
  "metadata": { "user_id": "u_88213", "order_id": "ORD-9981" }
}

verification{} — 6 มิติที่ตัดสินว่าผ่านอัตโนมัติหรือไม่ ​

เป็น boolean ล้วน 6 ตัว ไม่มีค่าที่อ่านได้จากสลิปจริง

ฟิลด์ตรวจอะไร
amount_exactยอดตรงเป๊ะ ไม่มี tolerance
within_time_windowเวลาบนสลิปอยู่ในหน้าต่างของรายการ
sender_bank_matchธนาคารผู้โอนตรงกับที่แจ้ง
sender_account_matchเลขบัญชีผู้โอนตรงกับที่แจ้ง
sender_name_matchชื่อผู้โอนตรงกับที่แจ้ง
receiver_matchบัญชีปลายทางตรงกับที่ระบบกำหนดให้

ยอดตรงต้องผ่านทั้ง 6 ตัว. ถ้า non-amount อีก 5 ตัวผ่านแต่ยอดไม่ตรง ระบบจะปิดอัตโนมัติทั้งสองทิศ: short-pay settled ที่ A=V<X, partial:true, settled_via:'auto'; same-slip overpay settled ตามยอดที่สร้างพร้อม overpayment{}. Amount mismatch อย่างเดียวไม่เปิด review

ทำไมเราไม่บอกว่า "ชื่อจริงบนสลิปคืออะไร"

เพราะผู้โอนจริงอาจเป็นคนละคนกับที่ merchant แจ้ง (สามีโอนแทนภรรยา) — ค่านั้นเป็นข้อมูลส่วนบุคคลของบุคคลที่สาม เราส่งได้แค่ matched: false และ ชื่อที่คุณเป็นคนส่งมาเอง แบบ mask กลับไปให้ (registered_name_masked) ผลคือ: ทีมคุณไล่เคสจากสลิปที่ลูกค้าส่งให้คุณโดยตรง ไม่ใช่จาก payload ของเรา

RECEIVER_MISMATCH เป็นเหตุผลเดียวที่ ไม่มี detail เลย — การบอกว่า "ควรเป็นบัญชี xxx1234" คือการยืนยันบัญชีของอีกฝ่ายให้คนที่โอนผิดรู้

ดูสลิป ​

http
GET /v1/pay/deposits/{id}/slip

คืนไฟล์ภาพตรง ๆ (ต้องมี auth และรายการต้องอยู่ในขอบเขตบัญชีคุณ)

ไม่มี presigned URL ให้

เราไม่ส่ง URL ของที่เก็บไฟล์ออกไปเลย เพราะ URL ที่หลุดแปลว่าใครก็โหลดสลิปของลูกค้าได้ ถ้าหน้าเว็บของคุณต้องแสดงภาพ ให้ proxy ผ่าน backend ของคุณเอง อย่าฝัง URL ตรงในหน้า

ดูรายการถอนรายใบ ​

bash
curl -sS "https://api.example-wl.com/v1/pay/withdrawals/wdr_01K3PB2M7Q4RW8YT0NBVXH5DKC" \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "PG-Timestamp: 1755749553" \
  -H "PG-Nonce: 01JQA5T8V0X2Z4B6D8F0H2J4L6" \
  -H "PG-Version: 2026-08-01" \
  -H "PG-Signature: v1=8f2c1ab6e0d74f9b3c5a7e1d0f6b8a4c2e9d7b5a3c1f0e8d6b4a2c0e8f6d4b2a"
json
{
  "id": "wdr_01K3PB2M7Q4RW8YT0NBVXH5DKC",
  "object": "withdrawal_order",
  "version": 7,
  "status": "partially_filled",
  "amount_total_minor": 1000000,
  "fee_total_minor": 3000,
  "filled_minor": 400000,
  "remaining_minor": 600000,
  "refunded_minor": 0,
  "fee_recognized_minor": 1200,
  "settled_splits": 1,
  "max_splits": 4,
  "currency": "THB",
  "expires_at": "2026-08-21T16:12:00.000Z",
  "fills": [
    {
      "leg_ref": "leg_7QF2H8KM",
      "amount_minor": 400000,
      "fee_recognized_minor": 1200,
      "settled_at": "2026-08-21T05:02:11.045Z",
      "receipt_state": "awaiting_confirm"
    }
  ]
}

remaining_minor ไม่ได้เท่ากับ amount_total_minor − filled_minor เสมอ

สูตรจริงคือ remaining = amount_total − filled − reservedreserved คือส่วนที่ มีคนกำลังโอนให้อยู่ แต่ยังไม่ settle ⇒ ใบที่ filled_minor เป็น 400000 และมีคนกำลังโอนอีก 200000 จะได้ remaining_minor เท่ากับ 400000 ไม่ใช่ 600000

ค้นหาแบบเป็นชุด ​

http
GET /v1/pay/deposits?status=under_review&updated_after=2026-08-21T00:00:00.000Z&cursor=&limit=200
GET /v1/pay/withdrawals?updated_after=2026-08-21T00:00:00.000Z&cursor=&limit=200

ใช้แทนการ poll รายตัว · ดูกฎ cursor ที่ รายงานและการกระทบยอด

ให้ลูกค้าอ้างอิงด้วย deposit_id เสมอ

หน้าจอของผู้ใช้ปลายทางต้องแสดง deposit_id / withdrawal_id ให้กดคัดลอกได้ merchant_reference ของคุณเราค้นให้ไม่ได้ข้ามระบบ และรายการที่วิ่งผ่านช่องทางสำรองจะไม่มีเลขอ้างอิงอื่นให้เลย

ต่อเวลา ​

รายการฝากที่ยัง awaiting_payment แต่ลูกค้ากำลังจะโอนไม่ทัน ต่อเวลาได้จากระบบหลังบ้าน

bash
curl -sS -X POST "https://api.example-wl.com/v1/bo/transactions/dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B/add-timeout" \
  -H "Authorization: Bearer $BO_OIDC_ACCESS_TOKEN" \
  -H "X-PG-Step-Up-Token: $BO_STEP_UP_TOKEN" \
  -H "Idempotency-Key: bo-timeout-dep01K3M7Y9-01"

ทั้ง employee OIDC (transactions:write), step-up token ที่ bind กับ request นี้ และ Idempotency-Key เป็นค่าบังคับ · actor มาจาก OIDC sub ห้ามส่ง employee id เอง และ endpoint นี้ไม่รับ merchant API key/HMAC

สิ่งที่ต้องรู้ก่อนกด

ข้อรายละเอียด
ต่อได้เฉพาะตอน awaiting_paymentมีสลิปเข้ามาแล้วต่อไม่ได้ — รายการเดินไปเส้นตรวจสอบแล้ว
ค่าใหม่ถูก clamp ด้วยนโยบายของชั้นบนขอ 60 นาทีอาจได้ 20 นาที ⇒ 🔴 อ่าน expires_at ที่คืนกลับมา อย่าคิดเอง
🔴 รายการที่ liquidity_source เป็น external_gateway ต่อเวลาไม่ได้expires_at ของรายการเหล่านี้มาจากช่องทางที่ใช้จริง (สั้นได้ถึง ~4 นาที) เราไม่มีสิทธิ์ยืดให้
หน้าต่างรับสลิปย้อนหลังขยับตามนับจาก expires_at ใหม่ ⇒ ต่อเวลา = ขยับ late_slip_accepted_until ตามไปด้วย

อย่า hardcode นับถอยหลังในหน้าเว็บ

เอา expires_at จาก response มาใช้เสมอ และ re-sync ทุกครั้งที่ผู้ใช้กลับเข้าหน้า (สลับแอปธนาคารไปแล้วกลับมา) countdown ที่ hardcode 15 นาทีจะเดินผิดทันทีที่รายการวิ่งผ่านช่องทางที่ TTL สั้นกว่านั้น

แก้รายการที่ติด under_review ​

POST /v1/bo/transactions/{id}/update-matching ถูกถอดออกและไม่ใช่ callable route เพราะไม่มี request contract ที่บอกได้ชัดว่าจะแก้อะไร ใช้ endpoint ที่มีสัญญาครบตามเหตุการณ์จริง 3 ทางด้านล่างแทน การตัดสินอนุมัติ/ปฏิเสธยังเกิดใน review decision endpoint ของเจ้าหน้าที่เท่านั้น

1. แนบหลักฐานเพิ่ม (ไม่รีเซ็ตสถานะ) ​

bash
curl -sS -X POST "https://api.example-wl.com/v1/pay/deposits/dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B/evidence" \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "PG-Timestamp: 1755749900" \
  -H "PG-Nonce: 01JQA5TH9C7E1G3J5L7N9Q1S3V" \
  -H "PG-Version: 2026-08-01" \
  -H "Idempotency-Key: evid-dep01K3M7Y9-01" \
  -H "PG-Signature: v1=1a3c5e70b9d2f486a0c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "bank_statement_line",
    "reference": "merchant-ledger-2026-08-21#line-004921",
    "note": "รายการเดินบัญชีที่ merchant เก็บไว้ยืนยันยอดโอน 5,000 บาท"
  }'

endpoint นี้รับเฉพาะ JSON reference ไม่รับไฟล์หรือ multipart · kind เป็น enum ปิด: bank_statement_line · chat_transcript · id_document_ref · other (id_document_ref คือเลขอ้างอิงในระบบ merchant ไม่ใช่รูปบัตร) การแนบเข้าเรื่องเดิม ไม่รีเซ็ต state และไม่ทำให้คิวเริ่มนับใหม่

2. แจ้งยอดที่โอนจริงในเคสที่ติด review เพราะเหตุอื่น (compatibility) ​

clean short-pay ที่ non-amount 5/5 ผ่านสำเร็จอัตโนมัติอยู่แล้ว จึงไม่ต้องเรียก endpoint นี้. ใช้ได้เฉพาะรายการที่ยังเป็น under_review เพราะมีเหตุ non-amount/ambiguity/policy อื่นจริง และยอดธนาคารยืนยันเป็น 0<V<X เท่านั้น

bash
curl -sS -X POST "https://api.example-wl.com/v1/pay/deposits/dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B/force-complete" \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "PG-Timestamp: 1755749950" \
  -H "PG-Nonce: 01JQA5TK1E9G3J5L7N9Q1S3V5X" \
  -H "PG-Version: 2026-08-01" \
  -H "Idempotency-Key: forcecomplete-dep01K3M7Y9" \
  -H "PG-Signature: v1=9f7d5b3a1c0e8f6d4b2a0c8e6f4d2b0a8c6e4f2d0b8a6c4e2f0d8b6a4c2e0f8d" \
  -H "Content-Type: application/json" \
  -d '{
    "verified_amount_minor": 480000,
    "note": "รายการติดตรวจชื่อผู้โอน ลูกค้าโอน 4,800 บาทแทน 5,000 บาท แนบหลักฐานเพื่อรับยอดจริงหลังตรวจชื่อ"
  }'
json
{
  "id": "dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B",
  "object": "deposit_order",
  "version": 7,
  "status": "under_review",
  "is_terminal": false
}

202 ไม่ใช่การอนุมัติ

ชื่อ force-complete ทำให้เข้าใจผิดได้ง่าย — มันคือการ ขอให้เจ้าหน้าที่ตัดสิน ไม่ใช่การสั่งให้ปิดรายการ สถานะที่คืนกลับมาคือ under_review และเงินยังไม่ถูกเครดิต เงื่อนไข: รายการต้องติด review เพราะเหตุอื่นอยู่ก่อน · ต้องมีหลักฐาน authoritative ที่ derive 0<V<X · note อย่างน้อย 20 ตัวอักษร. ถ้า clean short-pay ปิดอัตโนมัติแล้ว endpoint จะตอบ 409 deposit_not_reviewable

หลังเจ้าหน้าที่ resolve เหตุอื่นและอนุมัติ reviewed short-pay คุณจะได้ deposit.settled ที่มี partial:true, settled_via:'review' และ ordered_amount_minor — เคสนี้ amount_received_minor (A) เท่ากับยอดธนาคารยืนยัน V และต่ำกว่า X; เครดิตผู้ใช้ด้วย A ไม่ใช่ยอดที่สั่งไว้. ส่วน clean short-pay จะได้ payload เดียวกันแต่ settled_via:'auto' โดยไม่ผ่านขั้นนี้

3. เจ้าหน้าที่ตัดสิน External route conflict ​

เส้นนี้ไม่ใช่ generic review approval และไม่ทำให้รายการฝาก settle. ใช้ได้เฉพาะ review ของ deposit ที่มีเหตุผลเดียว provider_conflict, มี authoritative External route, แถวล่าสุดมี can_decide=true และเจ้าหน้าที่ White Label/Agent คนปัจจุบันถือ live lease อยู่. Merchant ตัดสินรายการของตัวเองไม่ได้; slip, short-pay, overpayment, mixed reason และ Owner Tier-4 ยังใช้ workflow ของตนเอง.

Brand UI ขอ request-bound OIDC step-up จาก user gesture แล้วส่งผ่าน cookie session แบบ same-origin เท่านั้น — browser ห้ามถือหรือส่ง OIDC bearer token:

js
const reviewId = '40000000-0000-4000-8000-000000000001'
const body = {
  decision: 'approve',
  reason_code: 'EXTERNAL_RESULT_ACCEPTED',
  decision_note: 'ตรวจผล External route และหลักฐานที่เกี่ยวข้องครบแล้ว ยืนยันผลรายการนี้',
}

await fetch(`/v1/bo/reviews/${reviewId}/decisions`, {
  method: 'POST',
  credentials: 'same-origin',
  cache: 'no-store',
  headers: {
    'Content-Type': 'application/json',
    'X-PG-BO-Client': '1',
    'X-PG-Step-Up-Token': requestBoundAssertion,
    'If-Match': '"3"',
    'Idempotency-Key': 'review-decision-4000-approve-01',
  },
  body: JSON.stringify(body),
})

Reject ใช้ URL เดิมและเปลี่ยน decision เป็น reject; proof ถูก bind เป็น review.approve/review.reject จึงสลับผลหลัง step-up ไม่ได้. สำเร็จจะปิด review task เป็น resolved, ล้าง lease และคืน ETag + Idempotent-Replay; endpoint ไม่แก้ deposit, route, evidence, match, reservation, journal, ledger, fee หรือ reserve. Legacy /approve และ /reject ถูก supersede และไม่มี runtime route.

สิ่งที่แก้ไม่ได้ — และทำอะไรแทน ​

อยากแก้แก้ได้ไหมทำแทนอย่างไร
บัญชีผู้โอนของรายการฝาก❌ยังไม่โอน → ยกเลิกแล้วสร้างใหม่ · โอนไปแล้ว → อัปสลิปตามปกติ แล้วให้เข้าคิวตรวจสอบ
ยอดของรายการฝาก❌ยังไม่โอน → ยกเลิกแล้วสร้างใหม่ · โอนไปแล้วผิดเฉพาะยอด → รอผล deposit.settled อัตโนมัติ · ใช้ force-complete เฉพาะเมื่อรายการติด review เพราะเหตุอื่น
ยอด/บัญชีปลายทางของรายการถอนที่ pending_approval❌ยกเลิกแล้วสร้างใหม่ (เงินถูกล็อกด้วยยอดเดิมไปแล้ว)
บัญชีปลายทางของรายการฝาก❌ระบบเป็นคนกำหนด ไม่ใช่คุณ และเปลี่ยนไม่ได้
merchant_reference❌unique ต่อบัญชีคุณ ตั้งครั้งเดียวตอนสร้าง

รายการที่มีสลิปแล้ว ยกเลิกไม่ได้

POST /v1/pay/deposits/{id}/cancel จะได้ 409 deposit_not_cancelable เหตุผลตรงไปตรงมา: มีคนโอนเงินจริงไปแล้ว การยกเลิกรายการไม่ได้ทำให้เงินกลับมา เส้นทางที่ถูกคือปล่อยให้เดินไป settled หรือ rejected แล้วถ้าเงินต้องคืน ให้ไปที่ เรื่องร้องเรียน (claims)

ปลดบล็อกผู้ใช้ที่สร้างรายการไม่ได้ ​

ผู้ใช้ที่มีรายการค้างเกินเพดานจะได้ 409 too_many_open_deposits พร้อม details.open_deposit_ids[]

bash
curl -sS -X POST "https://api.example-wl.com/v1/bo/users/u_88213/clear-block" \
  -H "Authorization: Bearer $BO_OIDC_ACCESS_TOKEN" \
  -H "X-PG-Step-Up-Token: $BO_STEP_UP_TOKEN" \
  -H "Idempotency-Key: bo-clear-block-u88213-01" \
  -H "Content-Type: application/json" \
  -d '{
    "reason_code": "OPEN_ORDERS_REVIEWED",
    "note": "ตรวจรายการค้างและปิดรายการที่ไม่ใช้แล้วครบถ้วน"
  }'

คำสั่งนี้บังคับ employee OIDC (users:write) + step-up ที่ bind กับ request + Idempotency-Key; actor มาจาก OIDC sub และ immutable audit log ไม่รับ merchant API key/HMAC

ก่อนกดปลดบล็อก ให้ปิดรายการค้างก่อน

เพดานมีไว้กันผู้ใช้สร้างรายการยอดเดียวกันหลายใบ แล้วโอนครั้งเดียว — ซึ่งจะกลายเป็นรายการกำกวมที่จับคู่ไม่ได้ทั้งกอง วิธีที่ถูกคือ ยกเลิกรายการเก่าที่ผู้ใช้ไม่ได้จะโอนแล้ว ตาม open_deposit_ids[] ไม่ใช่ปลดเพดานทิ้ง

แผนที่สถานะสำหรับทีม support ​

statusเงินอยู่ไหนบอกลูกค้าว่าอย่างไร
awaiting_liquidityยังไม่โอนกำลังจัดหาช่องทาง ยังไม่ต้องโอน 🔴 ไม่มี QR
awaiting_paymentยังไม่โอนโอนได้เลย โอนจากบัญชีที่แจ้งไว้เท่านั้น
slip_processingโอนแล้ว รอตรวจกำลังตรวจสลิป 🔴 ห้ามปล่อยของ
under_reviewโอนแล้ว รอตรวจใช้ข้อความจาก review.reasons[0].display_th ตรง ๆ + "เงินไม่หาย ไม่ต้องโอนซ้ำ"
settledเครดิตแล้วสำเร็จ
settled + overpayment{}เครดิตตามยอดที่สร้าง; ส่วนเกินอยู่ใน claimรายการสำเร็จแล้ว · แจ้งยอดเกิน/claim id และให้ลูกค้าติดต่อ support · ห้ามบอกว่าคืนแล้ว
expiredปกติยังไม่โอนถ้า is_terminal เป็น false → ยังมีปุ่ม "ฉันโอนไปแล้วแต่หมดเวลา"
canceledปกติยังไม่โอนเหมือน expired ถ้ามี late_slip_accepted_until
rejectedไม่เครดิตใช้ rejection.display_th + แสดง deposit_id
reversedหักคืนแล้วต้องมี handler ตั้งแต่วันแรก event นี้ปิดไม่ได้

เช็ค is_terminal ไม่ใช่ชื่อสถานะ

expired และ canceled ไม่ใช่จุดจบ ถ้ายังมี late_slip_accepted_until ทีมที่ปิดเคสทันทีที่เห็น canceled จะเจอ deposit.settled ของรายการที่ปิดไปแล้ว 20 นาทีต่อมา

ขั้นต่อไป ​

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