Skip to content

ยกเลิกรายการฝาก ​

POST /v1/pay/deposits/{id}/cancel ปิดรายการที่ผู้ใช้ยังไม่ได้โอน

กฎเดียวที่ตัดสินทุกอย่าง

มีสลิปเข้ามาแล้ว = ยกเลิกไม่ได้ ไม่ว่าสถานะจะเป็นอะไร ไม่ว่าจะเพิ่งอัปมาเมื่อวินาทีที่แล้ว

เพราะเมื่อมีสลิป แปลว่าเงินออกจากบัญชีผู้ใช้ไปแล้ว การยกเลิกจะทำให้เงินก้อนนั้นไม่มีรายการรองรับ


ยกเลิกได้เมื่อไหร่ ​

statusยกเลิกได้เงื่อนไข
awaiting_liquidity✅ได้เสมอ — ยังไม่เคยมีปลายทางให้โอนด้วยซ้ำ
awaiting_payment✅เฉพาะเมื่อยังไม่มีสลิป · บางรายการติดเงื่อนไขปลายทางชั่วคราวและยกเลิกไม่ได้ → 409
slip_processing❌โอนแล้ว กำลังตรวจ
under_review❌โอนแล้ว รอเจ้าหน้าที่
settled❌เครดิตไปแล้ว
expired · canceled · rejected · reversed❌ปิดไปแล้ว

Request ​

bash
curl -X POST https://api.p2peasypay.com/v1/pay/deposits/dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B/cancel \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "PG-Timestamp: 1755750330" \
  -H "PG-Nonce: 01JQA5TF2S4U6W8Y0A2C4E6G8J" \
  -H "PG-Version: 2026-08-01" \
  -H "Idempotency-Key: cancel-ORD-9981-01" \
  -H "PG-Signature: v1=3a70...b2c5" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "user_changed_sender" }'
fieldชนิดบังคับหมายเหตุ
reasonstring✅เหตุผลฝั่งคุณ เก็บไว้ให้ support อ่าน · เขียนให้เข้าใจได้ เช่น user_changed_sender · user_changed_amount · abandoned_checkout

Response 200 ​

json
{
  "id": "dep_01K3M7Y9RS5TQ0WH2E7XKJ4C6B",
  "object": "deposit_order",
  "version": 3,
  "status": "canceled",
  "is_terminal": false,
  "merchant_reference": "ORD-9981",
  "amount_minor": 500000,
  "currency": "THB",
  "canceled_by": "merchant",
  "cancel_reason_code": "merchant_request",
  "canceled_at": "2026-08-21T04:25:30.118Z",
  "late_slip_accepted_until": "2026-08-22T04:30:47.902Z",
  "created_at": "2026-08-21T04:10:47.902Z",
  "metadata": { "campaign": "aug-cashback", "order_id": "ORD-9981" }
}
fieldค่าที่เป็นไปได้ความหมาย
canceled_bymerchant · end_user · systemใครเป็นคนปิด
cancel_reason_codemerchant_request · end_user_request · no_liquidity_available · approval_ttl_expired🔴 enum เปิด — switch ต้องมี default:
late_slip_accepted_untiltimestamp หรือ nullถึงเมื่อไหร่ที่ยังรับสลิปย้อนหลังได้ · null = ปิดสนิทแล้ว
is_terminalfalse หรือ true🔴 ตัวนี้เท่านั้นที่บอกว่าปิดออร์เดอร์ได้หรือยัง

canceled ไม่ใช่จุดจบเสมอไป ​

เคสที่ทำให้ผู้ใช้เสียเงินจริง

ผู้ใช้กดยกเลิกบนหน้าเว็บ ในขณะที่นิ้วอีกข้างกดยืนยันโอนในแอปธนาคารพอดี — เกิดสัปดาห์ละหลายครั้งในระบบที่มี volume จริง

ถ้าคุณปิดออร์เดอร์ทันทีที่เห็นคำว่า canceled ผู้ใช้คนนั้นจะไม่มีที่ให้อัปสลิป และเงินที่โอนไปแล้วจะกลายเป็นเรื่องที่ต้องตามคืนด้วยมือ

⇒ รายการที่ถูกยกเลิกโดยที่เคยมีปลายทางให้โอน จะมี late_slip_accepted_until และ is_terminal: false ต้องเหลือปุ่มอัปสลิปไว้จนถึงเวลานั้น

js
// ❌ ผิด
if (d.status === 'canceled') closeOrderAndHideUploadButton()

// ✅ ถูก
if (d.is_terminal) {
  closeOrder()
} else if (d.late_slip_accepted_until) {
  showUploadButton({ until: d.late_slip_accepted_until })
}

สลิปที่มาหลังยกเลิก จะเข้าคิวเจ้าหน้าที่เสมอ ​

อัปสลิปเข้ารายการที่ canceled ภายในหน้าต่างจะได้ 202 ตามปกติ แต่ผลลัพธ์ถูกบังคับให้เป็น under_review (CANCELED_ORDER_SLIP) เสมอ ไม่มีทางผ่านอัตโนมัติ — เพราะเป็นเคสที่ต้องมีคนดูว่าเงินก้อนนั้นเป็นของรายการนี้จริงไหม

แต่ก็จะ ไม่ถูกปฏิเสธอัตโนมัติเช่นกัน

เมื่อหน้าต่างปิดจริง ​

เมื่อถึง late_slip_accepted_until เราจะยิง deposit.canceled ซ้ำอีกครั้ง ด้วย is_terminal: true และ version ใหม่ · นั่นคือจุดที่ปิดออร์เดอร์และซ่อนปุ่มอัปสลิปได้

หลังจากนั้นการอัปสลิปจะได้ 410 late_slip_window_closed พร้อม details.claim_url


ยกเลิกโดยระบบ — ไม่มีสภาพคล่อง ​

รายการที่ค้าง awaiting_liquidity จนหมดเวลารอโดยไม่เจอปลายทางเลย จะถูกปิดเป็น canceled ไม่ใช่ expired

json
{
  "id": "dep_01K3M8B2VQ7HYD4NKR0XF9TW6E",
  "object": "deposit_order",
  "version": 3,
  "status": "canceled",
  "is_terminal": true,
  "merchant_reference": "ORD-9983",
  "amount_minor": 500000,
  "currency": "THB",
  "canceled_by": "system",
  "cancel_reason_code": "no_liquidity_available",
  "canceled_at": "2026-08-21T04:12:15.660Z",
  "late_slip_accepted_until": null,
  "created_at": "2026-08-21T04:10:47.902Z",
  "metadata": { "order_id": "ORD-9983" }
}

ทำไมไม่ใช่ expired

expired เปิดหน้าต่างอัปสลิปย้อนหลัง 24 ชั่วโมง และ UI จะขึ้นปุ่ม "ฉันโอนไปแล้ว" · แต่รายการนี้ไม่เคยมีปลายทางให้โอนเลย ผู้ใช้ไม่เคยเห็น QR ⇒ ปุ่มนั้นจะสร้างได้แค่ ticket เปล่าให้ support

สังเกตว่าเส้นนี้ late_slip_accepted_until เป็น null และ is_terminal เป็น true ตั้งแต่แรก

ข้อความที่ควรขึ้นให้ผู้ใช้: ขณะนี้ช่องทางโอนไม่ว่าง กรุณาลองใหม่อีกครั้ง พร้อมปุ่มสร้างรายการใหม่ · 🔴 อย่าขึ้นว่า "รายการถูกยกเลิก" ลอย ๆ เพราะผู้ใช้จะคิดว่าตัวเองทำอะไรผิด


Error ​

HTTPcodeเกิดเมื่อต้องทำอะไร
409deposit_not_cancelableมีสลิปเข้ามาแล้ว หรือรายการติดเงื่อนไขปลายทางที่ยกเลิกไม่ได้ชั่วคราวปล่อยให้เดินต่อ อย่า retry เป็นลูป · อ่านสถานะล่าสุดด้วย GET แล้วอัปเดตหน้าจอ
404resource_not_foundid ไม่มี หรือไม่ใช่ของ merchant คุณตรวจ id
409idempotency_in_flightผู้ใช้กดยกเลิกรัว ๆรอ 1 วินาทีแล้วยิงซ้ำด้วย key เดิม
js
// Node — ยกเลิกแบบที่รับมือ 409 ได้
async function cancelDeposit(depositId, reason) {
  try {
    return await post(`/v1/pay/deposits/${depositId}/cancel`, { reason })
  } catch (err) {
    if (err.code === 'deposit_not_cancelable') {
      // สายไปแล้ว — ผู้ใช้โอนไปแล้ว ให้แสดงสถานะจริงแทนการขึ้น error
      const d = await getDeposit(depositId)
      return { canceled: false, deposit: d }
    }
    throw err
  }
}

หน้าจอที่ควรขึ้นเมื่อได้ 409 deposit_not_cancelable

ยกเลิกไม่ได้แล้ว เพราะเราได้รับสลิปของคุณแล้ว
กำลังตรวจสอบ เงินของคุณไม่หาย

เคสที่ใช้ cancel เป็นขั้นแรก ​

การ "แก้ไข" รายการที่สร้างแล้วทำไม่ได้เลย ทุกการเปลี่ยนใจคือ ยกเลิกใบเดิม แล้วสร้างใบใหม่

ผู้ใช้อยากทำอะไรขั้นตอน
เปลี่ยนบัญชีผู้โอนcancel → POST /v1/pay/deposits ด้วย sender ใหม่ + Idempotency-Key ใหม่
เปลี่ยนยอด (เช่นเลือกยอดที่แนะนำตอนรอสภาพคล่อง)cancel → สร้างใหม่ด้วยยอดใหม่ + merchant_reference ใหม่ + key ใหม่
ปิดรายการค้างเพื่อสร้างใบใหม่cancel ใบที่ค้างก่อน ไม่งั้นชนเพดานแล้วได้ 409 too_many_open_deposits

body เปลี่ยน = Idempotency-Key ต้องเปลี่ยน

ใช้ key เดิมกับ body ใหม่จะได้ 409 idempotency_key_reused · และการ gen key ใหม่ทุกครั้งที่ retry ก็ผิดคนละทาง — จะได้รายการซ้ำและ QR สองใบ

กติกา: หนึ่งเจตนา = หนึ่ง key เขียนลง DB ก่อนยิง แล้ว retry อ่านค่าเดิมจาก DB

ยกเลิกแล้วสร้างใหม่ทันทีอาจติดคิว

รายการบางเส้นทางมีช่วงพักสั้น ๆ (~3 นาที) หลังยกเลิก ก่อนที่ยอดเดิมจากบัญชีผู้โอนเดิมจะจับคู่ได้อีกครั้ง · ถ้าคุณทำปุ่ม "เปลี่ยนบัญชีผู้โอน" หรือ "เปลี่ยนยอด" ให้ track เวลาที่ยกเลิกไว้ฝั่งคุณ แล้วบอกผู้ใช้ตรง ๆ ว่ารายการใหม่อาจใช้เวลาจัดหาช่องทางนานกว่าปกติ แทนที่จะปล่อยให้เขางงกับหน้ารอ


ต้องยิง cancel เองไหม ถ้าผู้ใช้แค่ปิดเบราว์เซอร์ ​

ไม่ต้อง — รายการจะหมดอายุเองที่ expires_at แล้วกลายเป็น expired

ให้ยิง cancel เฉพาะเมื่อ ผู้ใช้ตั้งใจจะไม่โอน เช่น กดปุ่มยกเลิก หรือเลือกเปลี่ยนบัญชี/เปลี่ยนยอด · การยิง cancel กับรายการที่ผู้ใช้แค่หายไปเฉย ๆ ไม่ได้ช่วยอะไร และทำให้แยกไม่ออกว่าเคสไหนคือเจตนาจริง


ขั้นต่อไป ​

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