Skip to content

รายการฝากที่ยังค้างอยู่ ​

"ค้าง" ในหน้านี้หมายถึงรายการที่ยังไม่จบและยังกินโควตาของผู้ใช้อยู่

statusค้างในความหมายไหนผู้ใช้ทำอะไรได้
awaiting_liquidityยังหาปลายทางอยู่รอ หรือกดยกเลิก
awaiting_paymentมี QR แล้ว รอผู้ใช้โอนโอน หรือกดยกเลิก (ตราบใดที่ยังไม่มีสลิป)
slip_processingโอนแล้ว กำลังตรวจรอ — ยกเลิกไม่ได้แล้ว
under_reviewโอนแล้ว รอเจ้าหน้าที่รอ — ยกเลิกไม่ได้แล้ว
expired / canceled ที่ is_terminal: falseปิดไปแล้ว แต่ยังรับสลิปย้อนหลังได้อัปสลิปย้อนหลังจนถึง late_slip_accepted_until

ทำไมต้องหาก่อนสร้างใบใหม่ ​

ระบบมีเพดานรายการค้างต่อผู้ใช้ ถ้าเกินคุณจะสร้างรายการไม่ได้

ส่งอะไรมาใน client{}นับกลุ่มยังไงเพดาน
ครบ (device_fingerprint + ip)(merchant, end_user_ref, device_fingerprint)2 ใบ
ไม่ส่ง(merchant, end_user_ref) เท่านั้น🔴 1 ใบ

และมีกฎอีกข้อที่รัดกุมกว่านั้น: ห้ามมีรายการ awaiting_payment ที่ยอดเท่ากันและบัญชีผู้โอนเดียวกันค้างอยู่พร้อมกันสองใบ

กฎนี้มีไว้ป้องกันคุณ

ถ้าผู้ใช้เปิดสองรายการยอด 5,000 บาทเท่ากันจากบัญชีเดียวกัน แล้วโอนไปหนึ่งครั้ง ระบบจะไม่มีทางรู้เลยว่าสลิปใบนั้นเป็นของรายการไหน — เพราะทั้ง 6 มิติที่ใช้ตรวจตรงกันหมดทั้งสองใบ ⇒ ทั้งสองใบเข้าคิวเจ้าหน้าที่ (AMBIGUOUS_MATCH) เราจึงตัดปัญหาที่ต้นทางด้วยการไม่ให้สร้างใบที่สอง


วิธีที่ 1 — อ่านจาก 409 ตอนสร้าง (แม่นที่สุด) ​

เมื่อชนกฎ เราคืน id ของรายการที่ค้างอยู่มาให้เลย ไม่ต้องไปค้นเอง

409 duplicate_open_deposit ​

json
{
  "error": {
    "code": "duplicate_open_deposit",
    "type": "invalid_request_error",
    "message": "An open deposit with the same amount and sender already exists.",
    "message_th": "มีรายการฝากยอดเท่ากันจากบัญชีผู้โอนเดียวกันค้างอยู่แล้ว",
    "retryable": false,
    "retry_after_seconds": null,
    "request_id": "req_01JQA5TC6P8R0T2V4X6Z8B0D2F",
    "details": {
      "open_deposit_ids": ["dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B"]
    }
  }
}

ต้องทำ: พาผู้ใช้กลับไปที่รายการเดิม อย่าสร้างใบใหม่ อย่าเปลี่ยนยอดให้เขาเองแบบเงียบ ๆ

409 too_many_open_deposits ​

json
{
  "error": {
    "code": "too_many_open_deposits",
    "type": "invalid_request_error",
    "message": "Too many unpaid deposits are open for this end user.",
    "message_th": "มีรายการฝากที่ยังไม่ได้ชำระค้างอยู่หลายรายการ",
    "retryable": false,
    "retry_after_seconds": null,
    "request_id": "req_01JQA5TD8Q0S2U4W6Y8A0C2E4G",
    "details": {
      "open_deposit_ids": [
        "dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B",
        "dep_01K3M8B2VQ7HYD4NKR0XF9TW6E"
      ]
    }
  }
}

ต้องทำ: แสดงรายการที่ค้างให้ผู้ใช้เห็น แล้วให้เขาเลือกว่าจะ "โอนต่อ" ใบไหน หรือจะยกเลิกใบไหน

js
// Node — จัดการทั้งสอง 409 ในที่เดียว
async function startTopup(order) {
  try {
    return await createDeposit(order)
  } catch (err) {
    if (err.code === 'duplicate_open_deposit' || err.code === 'too_many_open_deposits') {
      const ids = err.details.open_deposit_ids ?? []
      const open = await Promise.all(ids.map(getDeposit))
      return { kind: 'resume', open }        // ❌ ห้ามสร้างใบใหม่ ❌ ห้าม retry เงียบ ๆ
    }
    throw err
  }
}
php
<?php
// PHP — เวอร์ชันเดียวกัน
try {
    return createDeposit($order, $headers);
} catch (ApiError $e) {
    if (in_array($e->code, ['duplicate_open_deposit', 'too_many_open_deposits'], true)) {
        $open = [];
        foreach ($e->details['open_deposit_ids'] ?? [] as $id) {
            $open[] = getDeposit($id, $headers);
        }
        return ['kind' => 'resume', 'open' => $open];
    }
    throw $e;
}

หน้าจอที่ควรขึ้น

คุณมีรายการเติมเงินที่ยังไม่เสร็จ

  5,000.00 บาท   กสิกรไทย xxx-x-x1234   หมดเวลาใน 12:31
     [ โอนต่อ ]   [ ยกเลิกรายการนี้ ]

สร้างรายการใหม่ได้เมื่อปิดรายการเดิมแล้ว

วิธีที่ 2 — ไล่ดูรายการค้างทั้งหมดของคุณ ​

bash
curl -G https://api.example-wl.com/v1/pay/deposits \
  --data-urlencode "status=awaiting_payment" \
  --data-urlencode "limit=200" \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "PG-Version: 2026-08-01" \
  -H "PG-Signature: v1=5c92...ae10"

filter ที่สัญญากำหนดไว้สำหรับ endpoint นี้คือ status · updated_after · cursor · limit เท่านั้น

json
{
  "data": [
    {
      "id": "dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B",
      "object": "deposit_order",
      "version": 2,
      "status": "awaiting_payment",
      "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",
      "sender": {
        "bank_code": "004",
        "account_number_masked": "xxx-x-x7890",
        "account_name_th_masked": "ส*** ใ***"
      },
      "metadata": { "user_id": "u_88213", "order_id": "ORD-9981" }
    }
  ],
  "has_more": false,
  "next_cursor": null
}

วิธีที่ 3 — ดัชนีฝั่งคุณเอง (จำเป็น ไม่ใช่ทางเลือก) ​

การหารายการค้าง "ของผู้ใช้คนหนึ่ง" ต้องอาศัยดัชนีฝั่งคุณ

ชุด filter ของ GET /v1/pay/deposits ที่สัญญากำหนดไว้ไม่มีการค้นด้วย end_user_ref ⇒ คุณต้องเก็บ mapping end_user_ref → deposit_id ไว้เองตั้งแต่วินาทีที่สร้างรายการ ทั้งเส้น 201 และเส้น 202

นี่ไม่ใช่ภาระเกินจำเป็น คุณต้องเก็บ deposit_id ลง DB อยู่แล้วเพื่อ reconcile และเพื่อให้ support ตามเรื่องได้ · การมี index บน (end_user_ref, status) เพิ่มอีกชั้นเดียวทำให้หน้า "รายการค้างของฉัน" ทำงานได้โดยไม่ต้องยิง API เลย

sql
-- ตารางฝั่ง merchant ที่ทำให้หน้ารายการค้างเร็วและไม่กิน rate limit
CREATE TABLE deposits (
  deposit_id            text PRIMARY KEY,          -- 🔴 text ไม่ใช่ varchar(20) ห้าม parse prefix
  merchant_reference    text NOT NULL UNIQUE,
  idempotency_key       text NOT NULL UNIQUE,
  end_user_ref          text NOT NULL,
  status                text NOT NULL,
  is_terminal           boolean NOT NULL DEFAULT false,
  version               bigint NOT NULL,
  amount_minor          bigint NOT NULL,
  expires_at            timestamptz,
  late_slip_accepted_until timestamptz,
  created_at            timestamptz NOT NULL DEFAULT now(),
  updated_at            timestamptz NOT NULL DEFAULT now()
);

-- หน้ารายการค้างของผู้ใช้คนหนึ่ง อ่านจากตารางนี้ ไม่ต้องยิง API
CREATE INDEX deposits_open_idx ON deposits (end_user_ref)
  WHERE is_terminal = false;
sql
-- รายการที่ยังกินโควตาของผู้ใช้คนนี้
SELECT deposit_id, status, amount_minor, expires_at
  FROM deposits
 WHERE end_user_ref = $1
   AND is_terminal = false
   AND status IN ('awaiting_liquidity', 'awaiting_payment', 'slip_processing', 'under_review')
 ORDER BY created_at DESC;

ให้ตารางนี้อัปเดตจาก webhook ผ่าน version guard เสมอ

sql
UPDATE deposits
   SET status = $2,
       is_terminal = $3,
       version = $4,
       late_slip_accepted_until = $5,
       updated_at = now()
 WHERE deposit_id = $1
   AND version < $4;

รายการค้างที่ต้องไม่หายไปจาก UI ​

สองสถานะนี้ยังต้องมีปุ่มอัปสลิป

expired และ canceled ที่ is_terminal: false ไม่ได้กินโควตาแล้ว แต่ยังต้องอยู่ในหน้า "รายการของฉัน" จนถึง late_slip_accepted_until เพราะผู้ใช้ที่โอนไปแล้วต้องกดอัปสลิปย้อนหลังได้

ถ้าหน้าจอคุณกรอง is_terminal = false อย่างเดียวก็จะรวมสองสถานะนี้อยู่แล้ว — แต่ถ้ากรองด้วย status IN (...) ต้องใส่ทั้งสองค่าเข้าไปด้วย ไม่งั้นผู้ใช้จะหาปุ่มอัปสลิปไม่เจอและจะโอนซ้ำ


ลำดับที่แนะนำ ตอนผู้ใช้กดเติมเงิน ​

  1. อ่านรายการค้างจากตารางของคุณ — มีค้างอยู่ไหม
  2. มี → ถามก่อนว่าจะ "โอนต่อ" หรือ "ยกเลิกแล้วสร้างใหม่"
  3. ไม่มี → เรียก GET /v1/pay/senders ให้เลือกบัญชีผู้โอน
  4. POST /v1/pay/deposits
  5. ถ้ายังได้ 409 อยู่ (เช่นผู้ใช้เปิดสองแท็บพร้อมกัน) → อ่าน details.open_deposit_ids[] แล้วกลับไปข้อ 2

ขั้นต่อไป ​

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