สำหรับนักพัฒนา

เชื่อมต่อ SlipDeaw กับเว็บของคุณเอง

คู่มือนี้สำหรับร้านที่มีเว็บอยู่แล้ว ไม่ว่าจะเป็น WordPress/WooCommerce, PHP, Node.js, Python, Next.js หรือระบบ no-code ที่ยิง HTTP ได้ ใช้แค่ API เดียวคือ POST /v1/verify ลูกค้าอัปโหลดสลิปบนเว็บร้าน ระบบตรวจกับธนาคารแล้วตอบผลใน 2-3 วินาที ออเดอร์เปลี่ยนเป็นชำระแล้วโดยแอดมินไม่ต้องนั่งเช็ก

สารบัญ

เริ่มต้น 3 ขั้น

  1. ขั้นที่ 1

    สมัครที่ my.slipdeaw.com

    สมัครด้วยอีเมลที่ my.slipdeaw.com ได้ทดลองฟรี 14 วัน ตรวจได้ 50 สลิป ไม่ต้องใส่บัตรเครดิต ครบแล้วค่อยเลือกแพ็กที่ต้องการ

  2. ขั้นที่ 2

    ใส่บัญชีรับเงินของร้าน

    ไปที่ตั้งค่า แล้วใส่เลขบัญชีธนาคารหรือพร้อมเพย์ที่ลูกค้าโอนเข้า (ใส่ได้หลายบัญชี) ขั้นนี้สำคัญที่สุด ถ้ายังไม่ใส่ สลิปทุกใบจะไม่ผ่านด้วยเหตุผล wrong_receiver เพราะระบบเทียบบัญชีปลายทางในสลิปกับบัญชีที่ร้านลงทะเบียนไว้

  3. ขั้นที่ 3

    รับ API key แล้วเก็บฝั่ง server

    ระบบแสดง key รูปแบบ sd_live_... ครั้งเดียวตอนสร้าง ให้เก็บใน env หรือไฟล์ config ฝั่ง server เท่านั้น ห้ามใส่ใน JavaScript หน้าเว็บ แอปมือถือ หรือตัวแปรที่ขึ้นต้นด้วย NEXT_PUBLIC_ ถ้า key หลุด ให้เข้าพอร์ทัลกดปิด (revoke) แล้วสร้างใหม่ได้ทันที

ลองยิงครั้งแรกใน 1 นาที เอา key ไปแทนที่ใน ตัวอย่าง curl ด้านล่าง แล้วส่งสลิปที่โอนเข้าบัญชีร้านจริง ถ้าได้ "status": "pass" แปลว่าพร้อมเชื่อมต่อกับเว็บแล้ว

แนวคิดที่ต้องรู้ก่อนเขียนโค้ด

กฎผ่าน 5 ข้อ

สลิปจะได้ "status": "pass" ก็ต่อเมื่อผ่านครบทุกข้อ ข้อใดไม่ผ่านจะได้ "fail" พร้อม reason บอกว่าตกข้อไหน

  1. สลิปจริง ระบบอ่าน QR บนสลิปแล้วตรวจกับธนาคารว่ามีรายการโอนนี้จริง (ไม่ผ่าน = fake)
  2. ยอดถึง ยอดในสลิปต้องไม่น้อยกว่ายอดออเดอร์ลบ tolerance ที่ร้านตั้ง (ค่าเริ่มต้น 0 = ต้องตรงหรือมากกว่า) โอนเกินถือว่าผ่าน (ไม่ผ่าน = amount_mismatch)
  3. เข้าบัญชีร้าน บัญชีปลายทางในสลิปต้องตรงกับบัญชีที่ร้านลงทะเบียน (ไม่ผ่าน = wrong_receiver)
  4. ไม่ซ้ำ เลขอ้างอิงของสลิปต้องไม่เคยผ่านมาก่อนในร้านนี้ (ไม่ผ่าน = duplicate พร้อม duplicate_of.order_id)
  5. ไม่เก่า เวลาโอนต้องไม่เกิน max_slip_age_hours (ค่าเริ่มต้น 24 ชั่วโมง) (ไม่ผ่าน = expired)

การนับโควตา

  • ทุกสลิปที่ระบบตรวจกับธนาคารได้ นับ 1 สลิป ไม่ว่าผ่านหรือไม่ผ่าน (fake, amount_mismatch, wrong_receiver, duplicate, expired ก็นับ เพราะเสียค่าตรวจไปแล้ว)
  • ไม่นับ เมื่ออ่านรูปไม่ได้ (invalid_image) และเมื่อระบบตรวจล่ม (503 provider_down)
  • รอบโควตาเป็นรายเดือน นับจากวันเริ่มใช้บริการ (ดู renew_at) จ่ายล่วงหน้า 6 หรือ 12 เดือน โควตาก็ยังรีเซ็ตทุกเดือน
  • ระบบยิง event quota.low เมื่อใช้ถึง 80% ของแพ็ก (แจ้งทาง LINE ได้ ดูหัวข้อแจ้งเตือน)

เกินโควตา 1 บาท/สลิป

ค่าเริ่มต้นเปิดให้ใช้เกินโควตาได้ ระบบไม่ตัดกลางเดือน ร้านขายต่อได้ตามปกติ สลิปที่เกินคิดเพิ่มใบละ 1 บาท และรวมยอดในใบแจ้งหนี้รอบถัดไป ถ้าร้านปิดการใช้เกินโควตาไว้ เมื่อโควตาหมด API จะตอบ 429 quota_exceeded ซึ่งเว็บควรตั้งออเดอร์เป็น "รอแอดมินตรวจ" เหมือนกรณีระบบล่ม

สลิปเดิม + ออเดอร์เดิม = retry ได้

ถ้าเว็บส่งสลิปใบเดิมกับ order_id เดิมซ้ำ (เช่น ลูกค้ากดสองครั้ง หรือเว็บ retry หลังเน็ตหลุดตอนรอคำตอบ) ระบบจะตอบ pass เหมือนครั้งแรกพร้อม "already_verified": true ไม่ถือว่าซ้ำ แต่จะถือว่าซ้ำเมื่อสลิปใบเดิมถูกใช้กับ order_id อื่น ดังนั้นควรส่ง order_id ที่คงที่ต่อออเดอร์เสมอ (การ retry ยังนับโควตาตามปกติ เพราะต้องตรวจกับธนาคารใหม่)

fallback เมื่อได้ 503

ระบบตรวจสลิปพึ่งพาธนาคารและผู้ให้บริการปลายทาง มีโอกาสล่มชั่วคราว เมื่อได้ HTTP 503 (หรือ 401, 429, หรือต่อไม่ติด) ห้ามบอกลูกค้าว่าสลิปไม่ผ่าน ให้ทำแบบนี้แทน

  1. รับสลิปไว้ เก็บรูปฝั่งร้าน (SlipDeaw ไม่ได้เก็บให้ในกรณีนี้)
  2. ตั้งออเดอร์เป็น "รอแอดมินตรวจ" และแจ้งแอดมินร้าน
  3. บอกลูกค้าว่า "รับสลิปแล้ว รอแอดมินตรวจสอบ" (ใช้ข้อความจาก message_th ได้)
  4. แอดมินตรวจด้วยตา หรือส่งซ้ำผ่าน API ภายหลังเมื่อระบบกลับมา (สลิปที่ล่มไม่ถูกนับโควตา)

REST API reference

Base URL https://api.slipdeaw.com ทุก request ต้องมี header X-Api-Key: sd_live_... ทุก response เป็น JSON เวลาเป็น ISO 8601 (UTC) เงินเป็นบาท

POST /v1/verify

ตรวจสลิป 1 ใบเทียบกับออเดอร์ 1 รายการ นี่คือ endpoint หลักที่ทุกเว็บต้องใช้

Request body (JSON)

fieldชนิดจำเป็นคำอธิบาย
order_idstringต้องมีรหัสออเดอร์ฝั่งร้าน (ไม่เกิน 100 ตัวอักษร) ใช้กันซ้ำและใช้ retry ต้องคงที่ต่อออเดอร์
amountnumberต้องมียอดที่ต้องชำระเป็นบาท ทศนิยม 2 ตำแหน่ง ดึงจากออเดอร์ฝั่ง server ห้ามรับจากเบราว์เซอร์
imagestringอย่างใดอย่างหนึ่งรูปสลิปเป็น base64 หรือ data URL (data:image/jpeg;base64,...) ขนาดรูปไม่เกิน 4MB รองรับ JPG/PNG จากแอปธนาคาร
qr_payloadstringอย่างใดอย่างหนึ่งข้อความใน QR บนสลิป (ถ้าเว็บอ่าน QR เองได้) ไม่ต้องส่งรูป เบาและเร็วกว่า ยาวไม่เกิน 2,000 ตัวอักษร ถ้าส่งมาพร้อม image ระบบใช้ qr_payload

Response 200 — ตรวจแล้ว (ผ่านหรือไม่ผ่าน)

fieldชนิดคำอธิบาย
status"pass" | "fail"ผลรวม ใช้ตัดสินว่าจะเปลี่ยนออเดอร์เป็นชำระแล้วหรือไม่
reasonstringok fake amount_mismatch wrong_receiver duplicate expired invalid_image ดูตารางด้านล่าง
message_thstringข้อความภาษาไทยที่โชว์ลูกค้าได้ทันที ใส่ยอดที่ต้องโอนเพิ่ม/ออเดอร์ที่ซ้ำให้แล้ว
slipobject | nullข้อมูลจากสลิป: id (รหัสสลิปใน SlipDeaw ขึ้นต้น slp_), amount, time, ref (เลขอ้างอิงธนาคาร), sender_bank, sender_name, receiver_bank, receiver_masked (เลขบัญชีปลายทางแบบปิดบางส่วน) เป็น null เมื่ออ่านสลิปไม่ได้
duplicate_ofobjectมีเมื่อ reason = duplicate: { order_id, time } ของออเดอร์ที่สลิปนี้เคยใช้ (order_id เป็น null ถ้าตรวจแบบ manual โดยไม่ระบุออเดอร์)
already_verifiedbooleanมีเป็น true เมื่อส่งสลิปเดิมกับออเดอร์เดิมซ้ำ (retry) ผลเป็น pass เหมือนครั้งแรก
quotaobject{ used, limit, overage, renew_at } โควตาหลังหักสลิปนี้แล้ว ไม่ควรส่งต่อไปเบราว์เซอร์

ตัวอย่าง: ผ่าน

{
  "status": "pass",
  "reason": "ok",
  "message_th": "ตรวจสลิปผ่าน ยืนยันการชำระเงินเรียบร้อย",
  "slip": {
    "id": "slp_01J8...",
    "amount": 590,
    "time": "2026-09-22T07:03:00.000Z",
    "ref": "2026092207030012345",
    "sender_bank": "KBANK",
    "sender_name": "นาย ก",
    "receiver_bank": "SCB",
    "receiver_masked": "xxx-x-x1234-x"
  },
  "quota": { "used": 121, "limit": 300, "overage": 0, "renew_at": "2026-10-15" }
}

ตัวอย่าง: ไม่ผ่านเพราะยอดขาด

{
  "status": "fail",
  "reason": "amount_mismatch",
  "message_th": "ยอดโอนไม่ตรงกับยอดสั่งซื้อ กรุณาโอนเพิ่ม 10 บาทและส่งสลิปใหม่",
  "slip": { "id": "slp_01J8...", "amount": 580, "time": "...", "ref": "...", "sender_bank": "KBANK", "sender_name": null, "receiver_bank": "SCB", "receiver_masked": "xxx-x-x1234-x" },
  "quota": { "used": 122, "limit": 300, "overage": 0, "renew_at": "2026-10-15" }
}

ตัวอย่าง: สลิปซ้ำ

{
  "status": "fail",
  "reason": "duplicate",
  "message_th": "สลิปนี้เคยใช้แล้วกับออเดอร์ A1001 กรุณาส่งสลิปของรายการโอนใหม่",
  "slip": { "id": "slp_...", "amount": 590, "...": "..." },
  "duplicate_of": { "order_id": "A1001", "time": "2026-09-20T10:12:00.000Z" },
  "quota": { "used": 123, "limit": 300, "overage": 0, "renew_at": "2026-10-15" }
}

reason ทั้งหมดและข้อความที่ควรโชว์ลูกค้า

คอลัมน์ message_th คือข้อความที่ API ส่งมาให้จริง (โชว์ได้เลย) ส่วนคอลัมน์ "สิ่งที่ลูกค้าควรทำ" เป็นข้อความเสริมที่แนะนำให้แสดงใต้ผล เพื่อให้ลูกค้ารู้ว่าต้องทำอะไรต่อ

reasonstatusนับโควตาmessage_th จาก APIสิ่งที่ลูกค้าควรทำ (แนะนำ)
okpassนับตรวจสลิปผ่าน ยืนยันการชำระเงินเรียบร้อย
(retry: สลิปนี้ยืนยันออเดอร์นี้ไว้แล้ว)
ไม่ต้องทำอะไรเพิ่ม ร้านจะดำเนินการออเดอร์ให้ต่อไป
fakefailนับไม่พบรายการโอนนี้ที่ธนาคาร กรุณาตรวจสอบว่าเป็นสลิปจริงและส่งใหม่อีกครั้งตรวจสอบว่าโอนสำเร็จแล้ว และอัปโหลดสลิปต้นฉบับจากแอปธนาคารโดยตรง
amount_mismatchfailนับยอดโอนไม่ตรงกับยอดสั่งซื้อ กรุณาโอนเพิ่ม 10 บาทและส่งสลิปใหม่
(ตัวเลขใส่ให้อัตโนมัติ)
โอนยอดส่วนที่ขาดเข้าบัญชีเดิม แล้วอัปโหลดสลิปใบใหม่ หรือติดต่อร้าน
wrong_receiverfailนับบัญชีปลายทางในสลิปไม่ใช่บัญชีของร้าน กรุณาตรวจสอบเลขบัญชีที่โอน
(ถ้าร้านยังไม่ตั้งบัญชี: ร้านยังไม่ได้ตั้งค่าบัญชีรับเงินในระบบตรวจสลิป กรุณาติดต่อร้านค้า)
ตรวจสอบเลขบัญชีของร้านแล้วโอนใหม่ หากโอนผิดบัญชีให้ติดต่อธนาคารต้นทาง
duplicatefailนับสลิปนี้เคยใช้แล้วกับออเดอร์ A1001 กรุณาส่งสลิปของรายการโอนใหม่ใช้สลิปของการโอนครั้งใหม่ หากคิดว่าเป็นความผิดพลาดให้ติดต่อร้าน
expiredfailนับสลิปเก่าเกิน 24 ชั่วโมง กรุณาโอนใหม่และส่งสลิปล่าสุดโอนใหม่และส่งสลิปทันทีหลังโอน หรือติดต่อร้านเพื่อตรวจสอบ
invalid_imagefailไม่นับอ่านสลิปไม่ได้ กรุณาส่งรูปสลิปที่ชัดและเห็น QR ครบอัปโหลดรูปสลิปต้นฉบับจากแอปธนาคาร ให้เห็น QR ชัด ไม่ครอป ไม่ใส่ฟิลเตอร์

กรณีที่ตรวจไม่ได้ (401 / 429 / 503 / เน็ตหลุด) แนะนำให้โชว์ข้อความกลาง ๆ ว่า "รับสลิปแล้ว รอแอดมินตรวจสอบ" ไม่ควรโชว์ข้อความเชิงเทคนิค เช่น API key ผิด ให้ลูกค้าเห็น

HTTP error

เมื่อไม่ใช่ 200 body จะเป็น { "error": "<code>", "message_th": "..." } ข้อความใน message_th ของกลุ่มนี้เขียนสำหรับแอดมินร้าน ไม่ใช่ลูกค้า

HTTPerrorความหมายเว็บควรทำอย่างไร
400bad_requestbody ไม่ครบหรือผิดรูปแบบ: ไม่มี order_id/amount, ไม่มีทั้ง image และ qr_payload, image ไม่ใช่ base64, รูปใหญ่เกิน 4MB, amount ติดลบบั๊กฝั่งร้าน แก้โค้ด (ถ้ารูปใหญ่เกิน ให้ย่อรูปก่อนส่ง)
401invalid_keykey ผิด ถูกปิด หรือไม่ได้ส่ง headerตั้งออเดอร์เป็นรอแอดมินตรวจ แจ้งแอดมินร้านตรวจ key ในพอร์ทัล
401shop_suspendedร้านหมดอายุเลยช่วงผ่อนผัน หรือถูกพักตั้งออเดอร์เป็นรอแอดมินตรวจ แจ้งแอดมินร้านต่ออายุที่พอร์ทัล
429quota_exceededโควตาเดือนนี้หมดและร้านปิดการใช้เกินโควตา (มี quota แนบมา)ตั้งออเดอร์เป็นรอแอดมินตรวจ แจ้งแอดมินร้านเปิดใช้เกินโควตาหรือเปลี่ยนแพ็ก
503provider_downระบบธนาคาร/ผู้ให้บริการตรวจสลิปขัดข้อง ไม่นับโควตาตั้งออเดอร์เป็นรอแอดมินตรวจ เก็บรูปไว้ ตรวจซ้ำภายหลังได้
422validationค่าที่ส่งไป /v1/settings หรือ /v1/events ไม่ถูกต้องดู message_th แล้วแก้ค่า
HTTP 400  { "error": "bad_request",    "message_th": "ต้องส่ง order_id และ amount" }
HTTP 400  { "error": "bad_request",    "message_th": "รูปใหญ่เกิน 4MB" }
HTTP 400  { "error": "bad_request",    "message_th": "ต้องส่ง image หรือ qr_payload" }
HTTP 401  { "error": "invalid_key",    "message_th": "API key ไม่ถูกต้องหรือถูกปิดใช้งาน" }
HTTP 401  { "error": "shop_suspended", "message_th": "บริการตรวจสลิปของร้านถูกพักชั่วคราว (ยังไม่ต่ออายุ)" }
HTTP 429  { "error": "quota_exceeded", "message_th": "โควตาตรวจสลิปของร้านเดือนนี้หมดแล้ว กรุณาติดต่อ SlipDeaw เพื่อต่อแพ็ก", "quota": { "used": 300, "limit": 300, "overage": 0, "renew_at": "2026-10-15" } }
HTTP 503  { "error": "provider_down",  "message_th": "ระบบตรวจสลิปขัดข้องชั่วคราว รับสลิปไว้แล้ว รอแอดมินตรวจสอบ", "slip": null }

POST /v1/verify/manual

ตรวจสลิปที่ลูกค้าส่งมาทางแชท/ไลน์ โดยแอดมินร้านกดตรวจจากหลังบ้านของร้าน body เหมือน /v1/verify แต่ order_id และ amount ไม่บังคับ ถ้าไม่ส่ง amount ระบบจะไม่เช็คยอด (เช็คแค่สลิปจริง เข้าบัญชีร้าน ไม่ซ้ำ ไม่เก่า) นับโควตาเหมือนกัน

POST /v1/verify/manual
X-Api-Key: sd_live_xxxxxxxx
Content-Type: application/json

{ "image": "<base64 หรือ data URL>" }                 // ไม่ส่ง amount = ไม่เช็คยอด
{ "order_id": "A1023", "amount": 590, "image": "..." } // ส่งครบ = ตรวจเหมือน /v1/verify

// ตอบเหมือน /v1/verify ทุกประการ (status / reason / message_th / slip / quota)

GET /v1/quota

ดูโควตารอบปัจจุบัน แพ็ก และสถานะร้าน เหมาะสำหรับโชว์ในหลังบ้านร้าน หรือเช็คก่อนแจ้งเตือนแอดมิน

GET /v1/quota
X-Api-Key: sd_live_xxxxxxxx

{
  "used": 121, "limit": 300, "overage": 0, "renew_at": "2026-10-15",
  "line_used": 12, "line_limit": 300, "line_remaining": 288,   // โควตาข้อความแจ้งเตือน LINE ของรอบนี้ (แยกจากสลิป)
  "status": "active",            // active | trial | grace | suspended
  "overage_enabled": true,       // false = เมื่อโควตาหมดจะได้ 429
  "plan": { "id": "standard", "name_th": "มาตรฐาน", "price": 299, "quota": 300, "line_quota": 300 }
}

line_used / line_limit คือโควตาข้อความแจ้งเตือน LINE ของรอบนี้ = ข้อความที่แถมในแพ็ก (plan.line_quota) บวกที่ซื้อเพิ่ม นับแยกจากโควตาสลิป ดูรายละเอียดที่หัวข้อแจ้งเตือนทาง LINE

GET /v1/slips

ประวัติสลิปที่ตรวจไปแล้ว (ทั้ง pass/fail/error) เรียงใหม่สุดก่อน แบ่งหน้าด้วย cursor query ที่รองรับ: limit (1-200 ค่าเริ่มต้น 50), cursor, status (pass | fail), order_id

GET /v1/slips?limit=50&status=fail&order_id=A1023&cursor=
X-Api-Key: sd_live_xxxxxxxx

{
  "items": [
    {
      "id": "slp_01J8...", "order_id": "A1023", "status": "fail", "reason": "amount_mismatch",
      "reason_th": "ยอดโอนไม่ตรงกับยอดสั่งซื้อ กรุณาโอนเพิ่ม 10 บาทและส่งสลิปใหม่",
      "amount_expected": 590, "amount_slip": 580, "slip_time": "2026-09-22T07:03:00.000Z",
      "trans_ref": "2026092207030012345", "sender_bank": "KBANK", "receiver_masked": "xxx-x-x1234-x",
      "source": "api", "created_at": "2026-09-22T07:03:04.000Z"
    }
  ],
  "next_cursor": "2026-09-22T07:03:04.000Z"   // null = หมดแล้ว · ส่งกลับใน ?cursor= เพื่อดูหน้าถัดไป
}

GET PUT /v1/settings

อ่านและแก้ค่าของร้าน: tolerance ยอดขาด, อายุสลิปสูงสุด, event ที่ต้องการรับแจ้ง และบัญชีรับเงิน (แก้จากพอร์ทัลได้เหมือนกัน endpoint นี้มีไว้ให้หลังบ้านของร้านตั้งค่าเองได้)

GET /v1/settings
X-Api-Key: sd_live_xxxxxxxx

{
  "amount_tolerance": 0,          // ยอดโอนขาดได้ไม่เกินกี่บาทยังถือว่าผ่าน (0-1000)
  "max_slip_age_hours": 24,       // อายุสลิปสูงสุดที่รับ (1-720 ชั่วโมง)
  "notify_events": ["slip.failed", "quota.low", "subscription.expiring", "subscription.suspended", "invoice.paid"],
  "accounts": [
    { "id": "acc_...", "bank_short": "KBANK", "account_number": "1234567890", "account_name": "บจก. ตัวอย่าง", "active": true }
  ]
}

PUT /v1/settings          (ส่งเฉพาะ field ที่ต้องการแก้ ยกเว้น accounts ต้องส่งทั้งชุด)
{
  "amount_tolerance": 5,
  "max_slip_age_hours": 48,
  "accounts": [
    { "bank_short": "KBANK", "account_number": "1234567890", "account_name": "บจก. ตัวอย่าง" },
    { "bank_short": "PromptPay", "account_number": "0812345678", "account_name": "บจก. ตัวอย่าง" }
  ]
}
// 422 { "error": "validation", "message_th": "..." } เมื่อค่าไม่ถูกต้อง เช่น ไม่มีบัญชีใช้งานเลย
ระวัง accounts เป็นการแทนที่ทั้งชุด บัญชีเดิมที่ไม่ได้ส่งมาจะหายไป ให้ GET มาก่อนแล้วส่งกลับพร้อมรายการที่เพิ่ม ต้องมีบัญชีใช้งานอย่างน้อย 1 บัญชี account_number เป็นตัวเลข 6-20 หลัก หรือเบอร์/เลขบัตรพร้อมเพย์ bank_short เช่น KBANK SCB BBL KTB TTB BAY GSB PromptPay

POST /v1/events

แจ้ง SlipDeaw เมื่อมีออเดอร์ใหม่ (type order.created เท่านั้น) ไม่บังคับ ใช้เพื่อให้ประวัติ event ในพอร์ทัลเห็นออเดอร์คู่กับสลิป และรองรับรายงาน/แจ้งเตือนในอนาคตโดยไม่ต้องแก้โค้ดตรวจสลิป

POST /v1/events
X-Api-Key: sd_live_xxxxxxxx
Content-Type: application/json

{ "type": "order.created", "order_id": "A1023", "amount": 590, "customer": "คุณเอ", "channel": "web", "note": "ส่งด่วน" }

// 201 { "ok": true, "event_id": "evt_..." }
// 422 { "error": "validation", "message_th": "ต้องมี order_id และ amount" }

ตัวอย่างโค้ด

ทุกตัวอย่างทำงานฝั่ง server และทำ 4 อย่างเหมือนกัน: ดึงยอดจากออเดอร์ในฐานข้อมูล, ส่งรูปไป /v1/verify, ถ้า pass ก็บันทึกออเดอร์เป็นชำระแล้วพร้อม slip.ref, ถ้าตรวจไม่ได้ก็ตั้งเป็นรอแอดมินตรวจ

ข้ามไปที่: curl · PHP · Node / Express · Python · Next.js + SDK · WordPress

curl

# แปลงรูปเป็น base64 (macOS ใช้ base64 -i · Linux ใช้ base64 -w0)
IMG=$(base64 -i slip.jpg | tr -d '\n')

curl -s -X POST https://api.slipdeaw.com/v1/verify \
  -H "X-Api-Key: sd_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d "{\"order_id\":\"A1023\",\"amount\":590,\"image\":\"$IMG\"}"

# ถ้าอ่าน QR บนสลิปมาแล้ว ส่ง qr_payload แทนรูปได้ (เบากว่ามาก)
curl -s -X POST https://api.slipdeaw.com/v1/verify \
  -H "X-Api-Key: sd_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"A1023","amount":590,"qr_payload":"0046000600000101030040220..."}'

# ดูโควตาที่เหลือ
curl -s https://api.slipdeaw.com/v1/quota -H "X-Api-Key: sd_live_xxxxxxxx"

PHP (cURL ล้วน)

ฟังก์ชันเดียวใช้ได้กับทุกเฟรมเวิร์ก (Laravel, CodeIgniter, PHP ธรรมดา) ต้องเปิด extension curl และ json ซึ่งโฮสต์ทั่วไปเปิดอยู่แล้ว

<?php
/**
 * ตรวจสลิปกับ SlipDeaw (PHP 7.4+ ใช้แค่ cURL ไม่ต้องลง library)
 * คืนค่า ['http' => int, 'body' => array]
 *   http 200 → ดู body['status'] ("pass" | "fail"), body['reason'], body['message_th']
 *   http 400 → ส่งข้อมูลผิด (body['message_th'] บอกสาเหตุ)
 *   http 401 / 429 / 503 / 0 → ตรวจไม่ได้ตอนนี้ ให้ตั้งออเดอร์เป็น "รอแอดมินตรวจ"
 */
function slipdeaw_verify(string $orderId, float $amount, string $imagePath): array {
  $apiKey = getenv('SLIPDEAW_API_KEY'); // เก็บใน env / config ฝั่ง server เท่านั้น
  $image  = base64_encode(file_get_contents($imagePath));

  $ch = curl_init('https://api.slipdeaw.com/v1/verify');
  curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 35,
    CURLOPT_HTTPHEADER     => [
      'Content-Type: application/json',
      'X-Api-Key: ' . $apiKey,
    ],
    CURLOPT_POSTFIELDS => json_encode([
      'order_id' => $orderId,
      'amount'   => $amount,
      'image'    => $image,
    ]),
  ]);
  $raw  = curl_exec($ch);
  $http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $err  = curl_error($ch);
  curl_close($ch);

  if ($raw === false) {
    // เครือข่ายล้ม / timeout → ถือเป็น pending ให้แอดมินตรวจ
    return ['http' => 0, 'body' => ['error' => 'network', 'message_th' => 'เชื่อมต่อระบบตรวจสลิปไม่ได้ (' . $err . ')']];
  }
  return ['http' => $http, 'body' => json_decode($raw, true) ?: []];
}

// ---------- ตัวอย่างการใช้ในหน้าอัปโหลดสลิป ----------
$order = load_order($_POST['order_id']);   // ดึงออเดอร์จากฐานข้อมูลของร้าน (ยอดต้องมาจากตรงนี้)
$r = slipdeaw_verify((string) $order['id'], (float) $order['total'], $_FILES['slip']['tmp_name']);
$b = $r['body'];

if ($r['http'] === 200 && ($b['status'] ?? '') === 'pass') {
  mark_order_paid($order['id'], $b['slip']['ref'], $b['slip']['id']);   // เก็บ ref + slip id ไว้กับออเดอร์
  echo 'ชำระเงินสำเร็จ';
} elseif ($r['http'] === 200) {
  echo htmlspecialchars($b['message_th']);   // fail: ข้อความบอกลูกค้าว่าต้องทำอะไรต่อ
} elseif ($r['http'] === 400) {
  echo 'ส่งสลิปไม่สำเร็จ: ' . htmlspecialchars($b['message_th'] ?? '');
} else {
  // 401 / 429 / 503 / network → รับสลิปไว้ก่อน ตั้งออเดอร์เป็น "รอแอดมินตรวจ"
  mark_order_pending_review($order['id'], $b['message_th'] ?? 'รอแอดมินตรวจสลิป');
  echo 'รับสลิปแล้ว รอแอดมินตรวจสอบ';
}

Node.js / Express (fetch)

ใช้ fetch ที่มีใน Node 18+ ไม่ต้องลง library เบราว์เซอร์ส่ง { order_id, image } มาที่ endpoint ของร้าน แล้ว server ค่อยยิงต่อไป SlipDeaw พร้อมยอดจากฐานข้อมูล

import express from "express";

const app = express();
app.use(express.json({ limit: "6mb" })); // รูป base64 ไม่เกิน 4MB + ข้อมูลอื่น

const API_KEY = process.env.SLIPDEAW_API_KEY; // อยู่ฝั่ง server เท่านั้น ห้ามส่งไปเบราว์เซอร์

app.post("/api/verify-slip", async (req, res) => {
  const { order_id, image } = req.body; // image = base64 หรือ data URL ที่เบราว์เซอร์ส่งมา
  const order = await db.orders.findById(order_id);
  if (!order) return res.status(404).json({ error: "order_not_found", message_th: "ไม่พบออเดอร์" });
  if (order.status === "paid") return res.json({ status: "pass", message_th: "ออเดอร์นี้ชำระเงินแล้ว" });

  let r;
  try {
    r = await fetch("https://api.slipdeaw.com/v1/verify", {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-Api-Key": API_KEY },
      // ยอดเอาจากฐานข้อมูล ไม่ใช่จากเบราว์เซอร์
      body: JSON.stringify({ order_id: String(order.id), amount: order.total, image }),
      signal: AbortSignal.timeout(30_000),
    });
  } catch {
    await db.orders.update(order.id, { status: "awaiting_review", review_reason: "network" });
    return res.json({ status: "pending", message_th: "รับสลิปแล้ว รอแอดมินตรวจสอบ" });
  }

  const body = await r.json();

  if (r.status === 200) {
    if (body.status === "pass") {
      await db.orders.update(order.id, {
        status: "paid", paid_at: body.slip.time, slip_id: body.slip.id, slip_ref: body.slip.ref,
      });
    }
    // ส่งเฉพาะที่ลูกค้าต้องรู้ (ไม่ต้องส่ง quota ไปเบราว์เซอร์)
    return res.json({ status: body.status, reason: body.reason, message_th: body.message_th });
  }
  if (r.status === 400) return res.status(400).json({ error: body.error, message_th: body.message_th });

  // 401 / 429 / 503 → รอแอดมินตรวจ
  await db.orders.update(order.id, { status: "awaiting_review", review_reason: body.error });
  return res.json({ status: "pending", message_th: "รับสลิปแล้ว รอแอดมินตรวจสอบ" });
});

app.listen(3000);

Python (requests)

import base64
import os
import requests

API_KEY = os.environ["SLIPDEAW_API_KEY"]  # ฝั่ง server เท่านั้น


def slipdeaw_verify(order_id: str, amount: float, image_path: str) -> tuple[int, dict]:
    """คืน (http_status, body) — http 0 = ต่อไม่ได้/timeout"""
    with open(image_path, "rb") as f:
        image = base64.b64encode(f.read()).decode()
    try:
        r = requests.post(
            "https://api.slipdeaw.com/v1/verify",
            headers={"X-Api-Key": API_KEY},
            json={"order_id": order_id, "amount": amount, "image": image},
            timeout=35,
        )
    except requests.RequestException as e:
        return 0, {"error": "network", "message_th": f"เชื่อมต่อระบบตรวจสลิปไม่ได้ ({e})"}
    try:
        return r.status_code, r.json()
    except ValueError:
        return r.status_code, {}


# ---------- ตัวอย่างการใช้ ----------
order = Order.get(order_id)  # ยอดที่ต้องชำระเอาจากฐานข้อมูล
http, body = slipdeaw_verify(str(order.id), float(order.total), "/tmp/slip.jpg")

if http == 200 and body.get("status") == "pass":
    order.mark_paid(slip_ref=body["slip"]["ref"], slip_id=body["slip"]["id"])
elif http == 200:
    show_customer(body["message_th"])  # fail: บอกลูกค้าว่าต้องทำอะไร
elif http == 400:
    show_customer("ส่งสลิปไม่สำเร็จ: " + body.get("message_th", ""))
else:
    order.set_awaiting_review(body.get("message_th"))  # 401 / 429 / 503 / network
    show_customer("รับสลิปแล้ว รอแอดมินตรวจสอบ")

Next.js ด้วย @slipdeaw/sdk

ถ้าเว็บเป็น Next.js (App Router) ใช้ SDK ได้เลย มี route handler สำเร็จรูปและ component <SlipUpload> ที่ย่อรูป พรีวิว และแสดงผล 3 สี (ผ่าน/ไม่ผ่าน/รอตรวจ) ให้ครบ SDK ยังไม่ขึ้น npm ทักไลน์ขอแพ็กเกจแล้วติดตั้งแบบ "@slipdeaw/sdk": "file:./packages/slipdeaw-sdk" รายละเอียดทุก option อยู่ในคู่มือ SDK ที่ให้ไปพร้อมแพ็กเกจ (docs/SDK.md)

.env.local

SLIPDEAW_API_KEY=sd_live_xxxxxxxx

route handler

// app/api/verify-slip/route.ts
import { createVerifyRouteHandler } from "@slipdeaw/sdk/next";
import { db } from "@/lib/db";

export const POST = createVerifyRouteHandler({
  apiKey: process.env.SLIPDEAW_API_KEY!,           // จาก .env.local ห้ามใช้ NEXT_PUBLIC_*
  resolveOrder: async (orderId) => {
    const order = await db.orders.findById(orderId);
    if (!order || order.status === "paid") return null; // null → 404 ไม่เปลืองโควตา
    return { amount: order.total };                     // ยอดจากฐานข้อมูล
  },
  onResult: async (result, { order_id }) => {
    if (result.status === "pass") await db.orders.markPaid(order_id, result.slip);
    else if (result.status === "pending") await db.orders.setStatus(order_id, "awaiting_review");
  },
});

component บนหน้าออเดอร์

// app/order/[id]/SlipSection.tsx
"use client";
import { SlipUpload } from "@slipdeaw/sdk/react";
import { useRouter } from "next/navigation";

export function SlipSection({ orderId, amount }: { orderId: string; amount: number }) {
  const router = useRouter();
  return (
    <SlipUpload
      orderId={orderId}
      amount={amount}                 // แสดงผลเท่านั้น ยอดจริงอยู่ที่ route handler
      endpoint="/api/verify-slip"
      onResult={(r) => { if (r.status === "pass") router.refresh(); }}
    />
  );
}

route handler รับได้ทั้ง JSON { order_id, image } และ multipart/form-data และจะไม่ throw เมื่อ SlipDeaw ล่ม/โควตาหมด/key ผิด แต่คืน status: "pending" ให้ onResult จัดการแทน

WordPress / WooCommerce

แนวทางนี้ไม่ต้องเขียนปลั๊กอินเอง ใช้ปลั๊กอิน Code Snippets (ฟรี ในคลังปลั๊กอิน WordPress) หรือวางใน functions.php ของ child theme ก็ได้ เหมาะกับร้านที่รับชำระแบบโอนผ่านธนาคาร (BACS) ซึ่ง WooCommerce ตั้งออเดอร์เป็น on-hold รอแอดมินยืนยัน

flow ที่ได้

  1. ลูกค้าเลือกชำระแบบโอนผ่านธนาคาร กดสั่งซื้อ WooCommerce พาไปหน้า thank-you (ออเดอร์สถานะ on-hold)
  2. ใต้เลขบัญชีของร้าน มีช่องอัปโหลดสลิปเพิ่มขึ้นมา (แสดงในหน้า "ดูออเดอร์" ในบัญชีลูกค้าด้วย)
  3. ส่งไป /v1/verify ผ่าน wp_remote_post ยอดใช้ $order->get_total()
  4. ผ่าน → $order->payment_complete() ออเดอร์เปลี่ยนเป็น processing/completed อัตโนมัติ พร้อมโน้ตยอด/ref/ธนาคาร
  5. ไม่ผ่าน → แสดง message_th ให้ลูกค้าแก้แล้วส่งใหม่ (ออเดอร์ยัง on-hold)
  6. 503/429/401/ต่อไม่ติด → เก็บรูปเข้า Media แล้ว update_status('on-hold', 'รอแอดมินตรวจสลิป') พร้อมลิงก์รูปในโน้ตออเดอร์

ขั้นที่ 1 เก็บ API key ใน wp-config.php

ห้ามใส่ key ในธีม ปลั๊กอิน หรือ Code Snippets เพราะไฟล์เหล่านั้นถูก export/แชร์ได้ง่าย ให้ใส่ใน wp-config.php เท่านั้น

// wp-config.php — วางเหนือบรรทัด /* That's all, stop editing! */
define('SLIPDEAW_API_KEY', 'sd_live_xxxxxxxx');

ขั้นที่ 2 วางโค้ดนี้ใน Code Snippets (ชนิด PHP) หรือ functions.php

<?php
/**
 * SlipDeaw x WooCommerce — ตรวจสลิปโอนเงิน (BACS) อัตโนมัติ
 * วางใน functions.php ของ child theme หรือใน plugin "Code Snippets" (ชนิด PHP, รันทุกที่)
 * ก่อนใช้: เพิ่มบรรทัดนี้ใน wp-config.php (ห้ามใส่ key ในธีมหรือ snippet)
 *   define('SLIPDEAW_API_KEY', 'sd_live_xxxxxxxx');
 */

if (!defined('SLIPDEAW_API_KEY')) {
  return; // ยังไม่ได้ตั้ง key — ไม่ทำอะไร
}

// 1) แสดงช่องอัปโหลดสลิปในหน้า thank-you (เฉพาะโอนผ่านธนาคาร) และหน้า "ดูออเดอร์" ในบัญชีลูกค้า
add_action('woocommerce_thankyou_bacs', 'slipdeaw_render_upload_form', 5);
add_action('woocommerce_view_order', 'slipdeaw_render_upload_form', 5);

function slipdeaw_render_upload_form($order_id) {
  $order = wc_get_order($order_id);
  if (!$order || $order->get_payment_method() !== 'bacs') return;

  // ข้อความผลตรวจจากการส่งครั้งล่าสุด (เก็บใน transient ตอน redirect กลับมา)
  $flash = get_transient('slipdeaw_msg_' . $order_id);
  if ($flash) {
    delete_transient('slipdeaw_msg_' . $order_id);
    $color = $flash['status'] === 'pass' ? '#2ECC71' : ($flash['status'] === 'fail' ? '#FF5C8A' : '#FFD23F');
    printf(
      '<div class="slipdeaw-msg" style="border-left:5px solid %s;padding:12px 16px;margin:16px 0;background:#FFF8E7">%s</div>',
      esc_attr($color), esc_html($flash['message'])
    );
  }

  if ($order->is_paid()) return; // ชำระแล้ว ไม่ต้องโชว์ช่องอัปโหลด
  if ($order->get_meta('_slipdeaw_pending')) {
    echo '<p class="slipdeaw-pending">รับสลิปแล้ว รอแอดมินตรวจสอบและยืนยัน</p>';
    return;
  }
  ?>
  <section class="slipdeaw-upload" style="margin:16px 0">
    <h3>อัปโหลดสลิปโอนเงิน</h3>
    <p>โอนเข้าบัญชีร้านตามที่แจ้งด้านบน แล้วแนบรูปสลิปจากแอปธนาคาร ระบบตรวจและยืนยันให้ทันที</p>
    <form method="post" enctype="multipart/form-data" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
      <input type="hidden" name="action" value="slipdeaw_upload">
      <input type="hidden" name="order_id" value="<?php echo esc_attr($order->get_id()); ?>">
      <input type="hidden" name="order_key" value="<?php echo esc_attr($order->get_order_key()); ?>">
      <?php wp_nonce_field('slipdeaw_upload_' . $order->get_id(), 'slipdeaw_nonce'); ?>
      <p><input type="file" name="slip" accept="image/*" required></p>
      <p><button type="submit" class="button alt">ส่งสลิปให้ระบบตรวจ</button></p>
    </form>
  </section>
  <?php
}

// 2) รับไฟล์ → ส่งไป SlipDeaw → อัปเดตออเดอร์ (ทำงานได้ทั้งลูกค้าที่ล็อกอินและไม่ล็อกอิน)
add_action('admin_post_slipdeaw_upload', 'slipdeaw_handle_upload');
add_action('admin_post_nopriv_slipdeaw_upload', 'slipdeaw_handle_upload');

function slipdeaw_handle_upload() {
  $order_id = isset($_POST['order_id']) ? absint($_POST['order_id']) : 0;
  $order    = $order_id ? wc_get_order($order_id) : null;

  // ตรวจ nonce + order_key กันคนอื่นยิงสลิปใส่ออเดอร์ที่ไม่ใช่ของตัวเอง
  $nonce = isset($_POST['slipdeaw_nonce']) ? sanitize_text_field(wp_unslash($_POST['slipdeaw_nonce'])) : '';
  $key   = isset($_POST['order_key']) ? sanitize_text_field(wp_unslash($_POST['order_key'])) : '';
  if (!$order || !wp_verify_nonce($nonce, 'slipdeaw_upload_' . $order_id) || !hash_equals($order->get_order_key(), $key)) {
    wp_die('คำขอไม่ถูกต้อง กรุณากลับไปหน้าออเดอร์แล้วลองใหม่', '', ['response' => 403]);
  }

  $back = $order->get_checkout_order_received_url();
  if ($order->is_paid()) {
    slipdeaw_flash($order_id, 'pass', 'ออเดอร์นี้ชำระเงินแล้ว');
    wp_safe_redirect($back); exit;
  }

  // ตรวจไฟล์: ต้องเป็นรูป และไม่เกิน 4MB (SlipDeaw รับสูงสุด 4MB)
  $file = isset($_FILES['slip']) ? $_FILES['slip'] : null;
  if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
    slipdeaw_flash($order_id, 'fail', 'อัปโหลดไฟล์ไม่สำเร็จ กรุณาลองใหม่'); wp_safe_redirect($back); exit;
  }
  if ($file['size'] > 4 * 1024 * 1024) {
    slipdeaw_flash($order_id, 'fail', 'รูปใหญ่เกิน 4MB กรุณาส่งรูปที่เล็กลง'); wp_safe_redirect($back); exit;
  }
  $type = wp_check_filetype_and_ext($file['tmp_name'], $file['name']);
  if (empty($type['type']) || strpos($type['type'], 'image/') !== 0) {
    slipdeaw_flash($order_id, 'fail', 'กรุณาส่งไฟล์รูปภาพ (JPG หรือ PNG)'); wp_safe_redirect($back); exit;
  }

  // ยอดที่ต้องชำระเอาจากออเดอร์ฝั่ง server เสมอ
  $res = wp_remote_post('https://api.slipdeaw.com/v1/verify', [
    'timeout' => 35,
    'headers' => [
      'Content-Type' => 'application/json',
      'X-Api-Key'    => SLIPDEAW_API_KEY,
    ],
    'body' => wp_json_encode([
      'order_id' => (string) $order->get_id(),
      'amount'   => (float) $order->get_total(),
      'image'    => base64_encode(file_get_contents($file['tmp_name'])),
    ]),
  ]);

  if (is_wp_error($res)) {
    // ต่อ SlipDeaw ไม่ได้ / timeout → พักไว้ให้แอดมินตรวจ
    slipdeaw_set_pending($order, 'เชื่อมต่อ SlipDeaw ไม่ได้: ' . $res->get_error_message(), $file);
    slipdeaw_flash($order_id, 'pending', 'รับสลิปแล้ว รอแอดมินตรวจสอบ');
    wp_safe_redirect($back); exit;
  }

  $http = (int) wp_remote_retrieve_response_code($res);
  $body = json_decode(wp_remote_retrieve_body($res), true);
  $body = is_array($body) ? $body : [];
  $msg  = isset($body['message_th']) ? $body['message_th'] : '';

  if ($http === 200 && isset($body['status']) && $body['status'] === 'pass') {
    $slip = isset($body['slip']) ? $body['slip'] : [];
    $order->update_meta_data('_slipdeaw_slip_id', isset($slip['id']) ? $slip['id'] : '');
    $order->update_meta_data('_slipdeaw_ref', isset($slip['ref']) ? $slip['ref'] : '');
    $order->add_order_note(sprintf(
      'SlipDeaw ตรวจสลิปผ่าน ยอด %s บาท ref %s จากธนาคาร %s',
      isset($slip['amount']) ? $slip['amount'] : '-',
      isset($slip['ref']) ? $slip['ref'] : '-',
      isset($slip['sender_bank']) ? $slip['sender_bank'] : '-'
    ));
    $order->payment_complete(isset($slip['ref']) ? $slip['ref'] : ''); // เปลี่ยนเป็น processing/completed + บันทึกวันชำระ
    slipdeaw_flash($order_id, 'pass', 'ชำระเงินสำเร็จ ขอบคุณค่ะ');
  } elseif ($http === 200 || $http === 400) {
    // สลิปไม่ผ่าน (fake / amount_mismatch / wrong_receiver / duplicate / expired / invalid_image)
    // หรือส่งข้อมูลผิด (400) — ให้ลูกค้าอ่าน message_th แล้วส่งใหม่ได้
    $reason = isset($body['reason']) ? $body['reason'] : (isset($body['error']) ? $body['error'] : '-');
    $order->add_order_note('SlipDeaw: สลิปไม่ผ่าน (' . $reason . ') ' . $msg);
    slipdeaw_flash($order_id, 'fail', $msg ?: 'สลิปไม่ผ่านการตรวจสอบ กรุณาส่งสลิปใหม่');
  } else {
    // 503 ระบบตรวจล่ม / 429 โควตาหมด / 401 key มีปัญหา → พักไว้ให้แอดมินตรวจเอง
    $err = isset($body['error']) ? $body['error'] : '-';
    slipdeaw_set_pending($order, 'SlipDeaw ตอบ HTTP ' . $http . ' (' . $err . ') ' . $msg, $file);
    slipdeaw_flash($order_id, 'pending', 'รับสลิปแล้ว รอแอดมินตรวจสอบ');
  }
  $order->save();
  wp_safe_redirect($back); exit;
}

// ตั้งออเดอร์เป็น on-hold พร้อมเก็บรูปสลิปไว้ให้แอดมินเปิดดูจากโน้ตออเดอร์
function slipdeaw_set_pending(WC_Order $order, $why, array $file) {
  require_once ABSPATH . 'wp-admin/includes/file.php';
  $moved = wp_handle_upload($file, ['test_form' => false]);
  $link  = !empty($moved['url']) ? $moved['url'] : '(เก็บรูปไม่สำเร็จ)';
  $order->update_meta_data('_slipdeaw_pending', 1);
  $order->update_status('on-hold', 'รอแอดมินตรวจสลิป: ' . $why . ' รูปสลิป: ' . $link);
}

// เก็บข้อความไว้โชว์หลัง redirect (10 นาที)
function slipdeaw_flash($order_id, $status, $message) {
  set_transient('slipdeaw_msg_' . $order_id, ['status' => $status, 'message' => $message], 10 * MINUTE_IN_SECONDS);
}

สิ่งที่ต้องเช็กเพิ่ม

  • nonce และ order_key โค้ดตรวจทั้งสองอย่างก่อนส่ง เพื่อไม่ให้ใครยิงสลิปใส่ออเดอร์ของคนอื่นด้วยการเดาเลขออเดอร์ อย่าตัดส่วนนี้ออก
  • ขนาดไฟล์ SlipDeaw รับไม่เกิน 4MB โค้ดเช็กไว้แล้ว แต่ต้องดู upload_max_filesize และ post_max_size ใน PHP (แนะนำ 8M ขึ้นไป) และ client_max_body_size ถ้าใช้ nginx ไม่งั้นไฟล์จะถูกตัดก่อนถึง WordPress
  • รูปจากมือถือ สลิปจากแอปธนาคารส่วนใหญ่ 100-800KB ส่งได้เลย ถ้าลูกค้าถ่ายหน้าจอด้วยกล้อง (หลาย MB) ควรใส่ โค้ดย่อรูปฝั่งเบราว์เซอร์ เพิ่ม
  • บัญชีรับเงิน เลขบัญชีที่โชว์ในหน้า thank-you (ตั้งค่า WooCommerce > การชำระเงิน > โอนผ่านธนาคาร) ต้องเป็นบัญชีเดียวกับที่ลงทะเบียนในพอร์ทัล SlipDeaw ไม่งั้นได้ wrong_receiver ทุกใบ
  • สถานะหลังชำระ payment_complete() เปลี่ยนเป็น processing (สินค้าจับต้องได้) หรือ completed (สินค้าดิจิทัล/ดาวน์โหลด) ตามที่ WooCommerce ตัดสินให้ ถ้าต้องการต่างจากนี้ให้ hook woocommerce_payment_complete_order_status
  • ทดสอบ ปิดการใช้เกินโควตาไว้ก่อนตอนทดลอง แล้วสร้างออเดอร์ทดสอบ 1 รายการ โอนเงินจริง 1 บาทเข้าบัญชีร้านแล้วส่งสลิป (หรือตั้ง amount_tolerance ชั่วคราว) เมื่อเห็นออเดอร์เปลี่ยนเป็น processing ค่อยเปิดใช้จริง
  • แสดงผลแบบไม่รีโหลดหน้า ถ้าอยากให้ผลขึ้นในหน้าเดิมโดยไม่ redirect ให้เปลี่ยน handler ไปใช้ wp_ajax_slipdeaw_upload / wp_ajax_nopriv_slipdeaw_upload แล้วตอบ JSON ตรรกะเหมือนกันทุกอย่าง
ไม่อยากทำเอง ทีม 1Day1Deaw ติดตั้งให้เว็บ WordPress ในราคา 1,500 บาท (ครั้งเดียว) ทักไลน์ @117uxbbe พร้อมลิงก์เว็บ

แนวปฏิบัติที่แนะนำ

อย่าเชื่อยอดจาก client

เบราว์เซอร์ส่งมาแค่ order_id กับรูป ให้ server ดึงยอดจากออเดอร์ในฐานข้อมูลแล้วใส่ใน amount เอง ถ้ารับยอดจากฟอร์ม ลูกค้าแก้ยอดเป็น 1 บาทแล้วโอน 1 บาทก็ผ่านได้

ย่อรูปก่อนส่ง

ย่อฝั่งเบราว์เซอร์ให้ด้านยาวไม่เกิน 1,600px แล้วแปลงเป็น JPEG ก่อนส่ง ทั้งเร็วขึ้นและไม่ชน 4MB QR บนสลิปยังอ่านได้ที่ขนาดนี้ (SDK ของเราใช้ค่านี้) อย่าย่อต่ำกว่า 800px และอย่าครอปจน QR หาย

// ย่อรูปในเบราว์เซอร์ก่อนส่ง: ด้านยาวไม่เกิน 1600px, JPEG คุณภาพ 0.85 (สลิปทั่วไปเหลือ 200-500KB)
async function slipToDataUrl(file, maxEdge = 1600) {
  const bmp = await createImageBitmap(file);
  const scale = Math.min(1, maxEdge / Math.max(bmp.width, bmp.height));
  const canvas = document.createElement("canvas");
  canvas.width = Math.round(bmp.width * scale);
  canvas.height = Math.round(bmp.height * scale);
  canvas.getContext("2d").drawImage(bmp, 0, 0, canvas.width, canvas.height);
  return canvas.toDataURL("image/jpeg", 0.85); // ส่ง string นี้ใน field "image" ได้เลย
}

// ใช้กับ <input type="file" id="slip" accept="image/*">
document.getElementById("slip").addEventListener("change", async (e) => {
  const image = await slipToDataUrl(e.target.files[0]);
  const r = await fetch("/api/verify-slip", {           // endpoint ฝั่ง server ของร้าน (ไม่ใช่ api.slipdeaw.com)
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ order_id: ORDER_ID, image }),
  });
  const body = await r.json();
  alert(body.message_th); // pass / fail / pending — โชว์ข้อความให้ลูกค้า
});

เก็บ slip id และ ref ไว้กับออเดอร์

บันทึก slip.id (slp_...), slip.ref, slip.time, slip.sender_bank ลงออเดอร์ ไว้ตรวจย้อนหลัง คืนเงิน หรือค้นใน /v1/slips?order_id= ได้ทันทีเมื่อลูกค้าทัก

retry เมื่อ network ล้ม

ถ้า request ไป SlipDeaw ตาย (timeout, connection reset) ก่อนได้คำตอบ ให้ส่งซ้ำได้อีก 1-2 ครั้งด้วย order_id เดิม ถ้ารอบแรกผ่านไปแล้วจริง ๆ จะได้ pass พร้อม already_verified: true ไม่มีทางเป็น duplicate ตั้ง timeout ประมาณ 30 วินาที (ธนาคารบางแห่งตอบช้า) ถ้ายังไม่ได้ก็ตั้งเป็นรอแอดมินตรวจ

ตั้ง tolerance ถ้ามีค่าธรรมเนียม

โอนต่างธนาคารบางกรณีถูกหักค่าธรรมเนียมปลายทาง หรือลูกค้าปัดเศษ ถ้าร้านรับได้ ให้ตั้ง amount_tolerance ในพอร์ทัล (เช่น 5 บาท) สลิปที่ขาดไม่เกินนี้จะผ่าน ค่าเริ่มต้นคือ 0 คือต้องเต็มจำนวน

ไม่ตรวจซ้ำออเดอร์ที่ชำระแล้ว

เช็คสถานะออเดอร์ก่อนยิง ถ้าชำระแล้วให้ตอบลูกค้าทันทีโดยไม่เรียก API ประหยัดโควตาและกันลูกค้ากดซ้ำ

จำกัดจำนวนครั้งต่อออเดอร์

สลิปที่ไม่ผ่านก็นับโควตา ควรจำกัดการส่งต่อออเดอร์ เช่น ไม่เกิน 5 ครั้ง หรือหน่วง 10 วินาทีระหว่างครั้ง เพื่อกันคนกดรัวหรือบอทเปลืองโควตาร้าน

เก็บ log ทุกครั้ง

บันทึก reason และ message_th ของทุกครั้งที่ตรวจ (ผ่านหรือไม่) ไว้ฝั่งร้าน เวลาลูกค้าทักว่า "โอนแล้วทำไมไม่ผ่าน" แอดมินจะตอบได้ทันทีโดยไม่ต้องเปิดพอร์ทัล

รับแจ้งเตือนทาง LINE

SlipDeaw แจ้งเตือนร้านทางไลน์ได้ทั้งแชทเดี่ยวและกลุ่ม โดยไม่ต้องเขียนโค้ด ตั้งครั้งเดียวในพอร์ทัล

  1. แอดไลน์ @117uxbbe แล้วพิมพ์ id บอทจะตอบ userId ของคุณกลับมา
  2. ถ้าอยากให้แจ้งเข้ากลุ่มทีมร้าน ให้ดึงบอทเข้ากลุ่ม บอทจะตอบ groupId ให้ทันที (หรือพิมพ์ id ในกลุ่มก็ได้)
  3. เข้าพอร์ทัล my.slipdeaw.com ไปที่ตั้งค่า > LINE แล้ววาง userId และ/หรือ groupId (ใส่ได้ทั้งคู่)
  4. เลือก event ที่ต้องการรับแจ้ง
eventแจ้งเมื่อ
slip.failedมีสลิปไม่ผ่าน (บอก order_id, เหตุผล, ยอด) เหมาะกับร้านที่อยากทักลูกค้าทันที
slip.passedมีสลิปผ่าน (ปิดไว้เป็นค่าเริ่มต้น เพราะร้านที่ออเดอร์เยอะจะได้ข้อความถี่)
quota.lowใช้โควตาถึง 80% ของแพ็ก
subscription.expiringใกล้หมดอายุ 7 วัน พร้อมลิงก์ชำระค่าบริการ
subscription.suspendedเลยช่วงผ่อนผันแล้วถูกพัก (API ตอบ 401 shop_suspended)
invoice.paidชำระค่าบริการสำเร็จ พร้อมใบเสร็จ

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

โควตาข้อความแจ้งเตือน LINE

แจ้งเตือนสลิปไม่ผ่าน/สลิปผ่านทาง LINE ใช้โควตาข้อความของแพ็ก ใบแจ้งหนี้และเรื่องชำระเงินไม่นับ ซื้อข้อความเพิ่มได้

  • แต่ละแพ็กแถมข้อความต่อเดือน: เริ่มต้น 150 · มาตรฐาน 300 · โปร 800 (ดู plan.line_quota) ซื้อเพิ่มได้โดยทักไลน์ทีมงาน
  • นับแบบเดียวกับที่ LINE คิด: ส่งหา userId = 1 ข้อความ ส่งเข้ากลุ่ม = จำนวนสมาชิกในกลุ่ม (กลุ่ม 5 คน = 5 ข้อความ) ต่อ 1 event · ข้อความที่บอทตอบในแชทไม่นับ
  • subscription.expiring subscription.suspended invoice.paid ส่งเสมอและไม่นับโควตา
  • เมื่อโควตาเต็ม event อื่นจะถูกข้าม (ประวัติใน /v1/slips และหน้า event ในพอร์ทัลยังบันทึกครบ) และระบบแจ้งร้าน 1 ครั้งต่อรอบ จนกว่าจะถึงรอบถัดไป
  • เช็คยอดใช้ได้จาก line_used / line_limit ใน /v1/quota หรือหน้าโต๊ะทำงานในพอร์ทัล

ราคา

แพ็กราคาโควตาแจ้งเตือน LINEเหมาะกับ
ทดลองฟรี 14 วัน50 สลิปตามแพ็กทดลองลองเชื่อมต่อและทดสอบ flow
เริ่มต้น199 บาท/เดือน150 สลิป/เดือน150 ข้อความ/เดือนร้านเล็ก ออเดอร์วันละไม่กี่ใบ
มาตรฐาน299 บาท/เดือน300 สลิป/เดือน300 ข้อความ/เดือนร้านที่ขายทุกวัน
โปร599 บาท/เดือน800 สลิป/เดือน800 ข้อความ/เดือนร้านที่ยิงแอด ออเดอร์เข้าทั้งวัน
  • ทุกแพ็กได้ฟังก์ชันครบเหมือนกัน ต่างกันแค่โควตา เปลี่ยนแพ็กได้ทุกเดือน
  • เกินโควตา คิดเพิ่ม 1 บาท/สลิป ไม่ตัดระบบ
  • แจ้งเตือนสลิปไม่ผ่าน/สลิปผ่านทาง LINE ใช้โควตาข้อความของแพ็ก ใบแจ้งหนี้และเรื่องชำระเงินไม่นับ ซื้อข้อความเพิ่มได้ (ดูโควตาข้อความ LINE)
  • จ่ายล่วงหน้า 6 เดือน ลด 10% · 12 เดือน ฟรี 2 เดือน
  • เชื่อมต่อเองตามคู่มือนี้ ไม่มีค่าติดตั้ง · ให้ทีมติดตั้งให้บนเว็บที่มีอยู่ (เช่น WordPress) 1,500 บาทครั้งเดียว · สั่งทำเว็บใหม่กับ 1Day1Deaw ฟรีค่าติดตั้ง

ดูรายละเอียดแพ็กเกจเต็มที่ หน้าแรก หรือเลือกแพ็กและชำระได้เองในพอร์ทัล

คำถามที่พบบ่อยของนักพัฒนา

เรียก API จากเบราว์เซอร์ตรง ๆ ได้ไหม

ไม่ได้ และไม่ควร เพราะต้องแนบ API key ซึ่งจะถูกดึงไปใช้ได้ ให้เบราว์เซอร์ส่งรูปมาที่ server ของร้านก่อน แล้ว server ยิงต่อไป SlipDeaw ทุกตัวอย่างในหน้านี้ทำแบบนั้น

มี sandbox หรือสลิปทดสอบไหม

ยังไม่มีสลิปปลอมสำหรับทดสอบ เพราะระบบตรวจกับธนาคารจริง ให้ใช้แพ็กทดลอง (50 สลิป) โอนเงินจริงจำนวนน้อยเข้าบัญชีร้านแล้วส่งสลิปนั้น ส่วนกรณีไม่ผ่านทดสอบง่าย ๆ ได้โดยส่งสลิปเดิมซ้ำกับ order_id ใหม่ (จะได้ duplicate) หรือตั้ง amount สูงกว่าที่โอน (จะได้ amount_mismatch)

รองรับธนาคารไหนบ้าง

ทุกธนาคารไทยและพร้อมเพย์ ขอแค่สลิปมี QR (สลิปจากแอปธนาคารทุกแอปในปัจจุบันมี) สลิปที่ถ่ายหน้าจอมาให้เห็น QR ครบก็ใช้ได้ สลิปกระดาษจากตู้ ATM ที่ไม่มี QR ใช้ไม่ได้

ใช้กับระบบ no-code เช่น Make, n8n, Zapier ได้ไหม

ได้ ใช้โมดูล HTTP request แบบ POST ไปที่ https://api.slipdeaw.com/v1/verify ใส่ header X-Api-Key และ body JSON ตาม reference รูปแปลงเป็น base64 ด้วยโมดูลของแพลตฟอร์มก่อน แล้วแยกทางตาม status ที่ตอบกลับ

ทำไมสลิปทุกใบได้ wrong_receiver

ร้านยังไม่ได้ใส่บัญชีรับเงินในพอร์ทัล หรือใส่เลขบัญชีคนละบัญชีกับที่ลูกค้าโอนเข้า ตรวจที่ตั้งค่า > บัญชีรับเงิน ถ้ารับหลายบัญชีต้องใส่ให้ครบทุกบัญชี

รูปสลิปเก็บไว้นานแค่ไหน

SlipDeaw เก็บรูปไม่เกิน 90 วันแล้วลบอัตโนมัติ เก็บเฉพาะเลขอ้างอิง ยอด เวลา และเลขบัญชีผู้รับแบบปิดบางส่วนไว้กันสลิปซ้ำ ถ้าร้านต้องการเก็บรูปนานกว่านั้น ให้เก็บฝั่งร้านเอง

มี webhook แจ้งผลกลับมาที่เว็บไหม

ไม่จำเป็น เพราะ /v1/verify ตอบผลทันทีใน request เดียว ส่วนการแจ้งเตือนเหตุการณ์ (สลิปไม่ผ่าน โควตาใกล้หมด ใกล้หมดอายุ) ส่งทาง LINE ประวัติทั้งหมดดูได้ในพอร์ทัลและ /v1/slips

ติดต่อทีม

ติดขั้นไหน ส่งลิงก์เว็บ ภาษาที่ใช้ และ order_id ที่ทดสอบมาในไลน์ ทีมช่วยดูให้ (เราดูประวัติ request ของร้านได้จากฝั่งเรา ไม่ต้องส่ง API key มา)