สำหรับนักพัฒนา
เชื่อมต่อ SlipDeaw กับเว็บของคุณเอง
คู่มือนี้สำหรับร้านที่มีเว็บอยู่แล้ว ไม่ว่าจะเป็น WordPress/WooCommerce, PHP, Node.js, Python, Next.js หรือระบบ no-code ที่ยิง HTTP ได้ ใช้แค่ API เดียวคือ POST /v1/verify ลูกค้าอัปโหลดสลิปบนเว็บร้าน ระบบตรวจกับธนาคารแล้วตอบผลใน 2-3 วินาที ออเดอร์เปลี่ยนเป็นชำระแล้วโดยแอดมินไม่ต้องนั่งเช็ก
สารบัญ
เริ่มต้น 3 ขั้น
-
ขั้นที่ 1
สมัครที่ my.slipdeaw.com
สมัครด้วยอีเมลที่ my.slipdeaw.com ได้ทดลองฟรี 14 วัน ตรวจได้ 50 สลิป ไม่ต้องใส่บัตรเครดิต ครบแล้วค่อยเลือกแพ็กที่ต้องการ
-
ขั้นที่ 2
ใส่บัญชีรับเงินของร้าน
ไปที่ตั้งค่า แล้วใส่เลขบัญชีธนาคารหรือพร้อมเพย์ที่ลูกค้าโอนเข้า (ใส่ได้หลายบัญชี) ขั้นนี้สำคัญที่สุด ถ้ายังไม่ใส่ สลิปทุกใบจะไม่ผ่านด้วยเหตุผล
wrong_receiverเพราะระบบเทียบบัญชีปลายทางในสลิปกับบัญชีที่ร้านลงทะเบียนไว้ -
ขั้นที่ 3
รับ API key แล้วเก็บฝั่ง server
ระบบแสดง key รูปแบบ
sd_live_...ครั้งเดียวตอนสร้าง ให้เก็บใน env หรือไฟล์ config ฝั่ง server เท่านั้น ห้ามใส่ใน JavaScript หน้าเว็บ แอปมือถือ หรือตัวแปรที่ขึ้นต้นด้วยNEXT_PUBLIC_ถ้า key หลุด ให้เข้าพอร์ทัลกดปิด (revoke) แล้วสร้างใหม่ได้ทันที
"status": "pass" แปลว่าพร้อมเชื่อมต่อกับเว็บแล้ว
แนวคิดที่ต้องรู้ก่อนเขียนโค้ด
กฎผ่าน 5 ข้อ
สลิปจะได้ "status": "pass" ก็ต่อเมื่อผ่านครบทุกข้อ ข้อใดไม่ผ่านจะได้ "fail" พร้อม reason บอกว่าตกข้อไหน
- สลิปจริง ระบบอ่าน QR บนสลิปแล้วตรวจกับธนาคารว่ามีรายการโอนนี้จริง (ไม่ผ่าน =
fake) - ยอดถึง ยอดในสลิปต้องไม่น้อยกว่ายอดออเดอร์ลบ tolerance ที่ร้านตั้ง (ค่าเริ่มต้น 0 = ต้องตรงหรือมากกว่า) โอนเกินถือว่าผ่าน (ไม่ผ่าน =
amount_mismatch) - เข้าบัญชีร้าน บัญชีปลายทางในสลิปต้องตรงกับบัญชีที่ร้านลงทะเบียน (ไม่ผ่าน =
wrong_receiver) - ไม่ซ้ำ เลขอ้างอิงของสลิปต้องไม่เคยผ่านมาก่อนในร้านนี้ (ไม่ผ่าน =
duplicateพร้อมduplicate_of.order_id) - ไม่เก่า เวลาโอนต้องไม่เกิน
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, หรือต่อไม่ติด) ห้ามบอกลูกค้าว่าสลิปไม่ผ่าน ให้ทำแบบนี้แทน
- รับสลิปไว้ เก็บรูปฝั่งร้าน (SlipDeaw ไม่ได้เก็บให้ในกรณีนี้)
- ตั้งออเดอร์เป็น "รอแอดมินตรวจ" และแจ้งแอดมินร้าน
- บอกลูกค้าว่า "รับสลิปแล้ว รอแอดมินตรวจสอบ" (ใช้ข้อความจาก
message_thได้) - แอดมินตรวจด้วยตา หรือส่งซ้ำผ่าน 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_id | string | ต้องมี | รหัสออเดอร์ฝั่งร้าน (ไม่เกิน 100 ตัวอักษร) ใช้กันซ้ำและใช้ retry ต้องคงที่ต่อออเดอร์ |
amount | number | ต้องมี | ยอดที่ต้องชำระเป็นบาท ทศนิยม 2 ตำแหน่ง ดึงจากออเดอร์ฝั่ง server ห้ามรับจากเบราว์เซอร์ |
image | string | อย่างใดอย่างหนึ่ง | รูปสลิปเป็น base64 หรือ data URL (data:image/jpeg;base64,...) ขนาดรูปไม่เกิน 4MB รองรับ JPG/PNG จากแอปธนาคาร |
qr_payload | string | อย่างใดอย่างหนึ่ง | ข้อความใน QR บนสลิป (ถ้าเว็บอ่าน QR เองได้) ไม่ต้องส่งรูป เบาและเร็วกว่า ยาวไม่เกิน 2,000 ตัวอักษร ถ้าส่งมาพร้อม image ระบบใช้ qr_payload |
Response 200 — ตรวจแล้ว (ผ่านหรือไม่ผ่าน)
| field | ชนิด | คำอธิบาย |
|---|---|---|
status | "pass" | "fail" | ผลรวม ใช้ตัดสินว่าจะเปลี่ยนออเดอร์เป็นชำระแล้วหรือไม่ |
reason | string | ok fake amount_mismatch wrong_receiver duplicate expired invalid_image ดูตารางด้านล่าง |
message_th | string | ข้อความภาษาไทยที่โชว์ลูกค้าได้ทันที ใส่ยอดที่ต้องโอนเพิ่ม/ออเดอร์ที่ซ้ำให้แล้ว |
slip | object | null | ข้อมูลจากสลิป: id (รหัสสลิปใน SlipDeaw ขึ้นต้น slp_), amount, time, ref (เลขอ้างอิงธนาคาร), sender_bank, sender_name, receiver_bank, receiver_masked (เลขบัญชีปลายทางแบบปิดบางส่วน) เป็น null เมื่ออ่านสลิปไม่ได้ |
duplicate_of | object | มีเมื่อ reason = duplicate: { order_id, time } ของออเดอร์ที่สลิปนี้เคยใช้ (order_id เป็น null ถ้าตรวจแบบ manual โดยไม่ระบุออเดอร์) |
already_verified | boolean | มีเป็น true เมื่อส่งสลิปเดิมกับออเดอร์เดิมซ้ำ (retry) ผลเป็น pass เหมือนครั้งแรก |
quota | object | { 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 ส่งมาให้จริง (โชว์ได้เลย) ส่วนคอลัมน์ "สิ่งที่ลูกค้าควรทำ" เป็นข้อความเสริมที่แนะนำให้แสดงใต้ผล เพื่อให้ลูกค้ารู้ว่าต้องทำอะไรต่อ
| reason | status | นับโควตา | message_th จาก API | สิ่งที่ลูกค้าควรทำ (แนะนำ) |
|---|---|---|---|---|
ok | pass | นับ | ตรวจสลิปผ่าน ยืนยันการชำระเงินเรียบร้อย (retry: สลิปนี้ยืนยันออเดอร์นี้ไว้แล้ว) | ไม่ต้องทำอะไรเพิ่ม ร้านจะดำเนินการออเดอร์ให้ต่อไป |
fake | fail | นับ | ไม่พบรายการโอนนี้ที่ธนาคาร กรุณาตรวจสอบว่าเป็นสลิปจริงและส่งใหม่อีกครั้ง | ตรวจสอบว่าโอนสำเร็จแล้ว และอัปโหลดสลิปต้นฉบับจากแอปธนาคารโดยตรง |
amount_mismatch | fail | นับ | ยอดโอนไม่ตรงกับยอดสั่งซื้อ กรุณาโอนเพิ่ม 10 บาทและส่งสลิปใหม่ (ตัวเลขใส่ให้อัตโนมัติ) | โอนยอดส่วนที่ขาดเข้าบัญชีเดิม แล้วอัปโหลดสลิปใบใหม่ หรือติดต่อร้าน |
wrong_receiver | fail | นับ | บัญชีปลายทางในสลิปไม่ใช่บัญชีของร้าน กรุณาตรวจสอบเลขบัญชีที่โอน (ถ้าร้านยังไม่ตั้งบัญชี: ร้านยังไม่ได้ตั้งค่าบัญชีรับเงินในระบบตรวจสลิป กรุณาติดต่อร้านค้า) | ตรวจสอบเลขบัญชีของร้านแล้วโอนใหม่ หากโอนผิดบัญชีให้ติดต่อธนาคารต้นทาง |
duplicate | fail | นับ | สลิปนี้เคยใช้แล้วกับออเดอร์ A1001 กรุณาส่งสลิปของรายการโอนใหม่ | ใช้สลิปของการโอนครั้งใหม่ หากคิดว่าเป็นความผิดพลาดให้ติดต่อร้าน |
expired | fail | นับ | สลิปเก่าเกิน 24 ชั่วโมง กรุณาโอนใหม่และส่งสลิปล่าสุด | โอนใหม่และส่งสลิปทันทีหลังโอน หรือติดต่อร้านเพื่อตรวจสอบ |
invalid_image | fail | ไม่นับ | อ่านสลิปไม่ได้ กรุณาส่งรูปสลิปที่ชัดและเห็น QR ครบ | อัปโหลดรูปสลิปต้นฉบับจากแอปธนาคาร ให้เห็น QR ชัด ไม่ครอป ไม่ใส่ฟิลเตอร์ |
กรณีที่ตรวจไม่ได้ (401 / 429 / 503 / เน็ตหลุด) แนะนำให้โชว์ข้อความกลาง ๆ ว่า "รับสลิปแล้ว รอแอดมินตรวจสอบ" ไม่ควรโชว์ข้อความเชิงเทคนิค เช่น API key ผิด ให้ลูกค้าเห็น
HTTP error
เมื่อไม่ใช่ 200 body จะเป็น { "error": "<code>", "message_th": "..." } ข้อความใน message_th ของกลุ่มนี้เขียนสำหรับแอดมินร้าน ไม่ใช่ลูกค้า
| HTTP | error | ความหมาย | เว็บควรทำอย่างไร |
|---|---|---|---|
| 400 | bad_request | body ไม่ครบหรือผิดรูปแบบ: ไม่มี order_id/amount, ไม่มีทั้ง image และ qr_payload, image ไม่ใช่ base64, รูปใหญ่เกิน 4MB, amount ติดลบ | บั๊กฝั่งร้าน แก้โค้ด (ถ้ารูปใหญ่เกิน ให้ย่อรูปก่อนส่ง) |
| 401 | invalid_key | key ผิด ถูกปิด หรือไม่ได้ส่ง header | ตั้งออเดอร์เป็นรอแอดมินตรวจ แจ้งแอดมินร้านตรวจ key ในพอร์ทัล |
| 401 | shop_suspended | ร้านหมดอายุเลยช่วงผ่อนผัน หรือถูกพัก | ตั้งออเดอร์เป็นรอแอดมินตรวจ แจ้งแอดมินร้านต่ออายุที่พอร์ทัล |
| 429 | quota_exceeded | โควตาเดือนนี้หมดและร้านปิดการใช้เกินโควตา (มี quota แนบมา) | ตั้งออเดอร์เป็นรอแอดมินตรวจ แจ้งแอดมินร้านเปิดใช้เกินโควตาหรือเปลี่ยนแพ็ก |
| 503 | provider_down | ระบบธนาคาร/ผู้ให้บริการตรวจสลิปขัดข้อง ไม่นับโควตา | ตั้งออเดอร์เป็นรอแอดมินตรวจ เก็บรูปไว้ ตรวจซ้ำภายหลังได้ |
| 422 | validation | ค่าที่ส่งไป /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 ที่ได้
- ลูกค้าเลือกชำระแบบโอนผ่านธนาคาร กดสั่งซื้อ WooCommerce พาไปหน้า thank-you (ออเดอร์สถานะ on-hold)
- ใต้เลขบัญชีของร้าน มีช่องอัปโหลดสลิปเพิ่มขึ้นมา (แสดงในหน้า "ดูออเดอร์" ในบัญชีลูกค้าด้วย)
- ส่งไป
/v1/verifyผ่านwp_remote_postยอดใช้$order->get_total() - ผ่าน →
$order->payment_complete()ออเดอร์เปลี่ยนเป็น processing/completed อัตโนมัติ พร้อมโน้ตยอด/ref/ธนาคาร - ไม่ผ่าน → แสดง
message_thให้ลูกค้าแก้แล้วส่งใหม่ (ออเดอร์ยัง on-hold) - 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 ตัดสินให้ ถ้าต้องการต่างจากนี้ให้ hookwoocommerce_payment_complete_order_status - ทดสอบ ปิดการใช้เกินโควตาไว้ก่อนตอนทดลอง แล้วสร้างออเดอร์ทดสอบ 1 รายการ โอนเงินจริง 1 บาทเข้าบัญชีร้านแล้วส่งสลิป (หรือตั้ง amount_tolerance ชั่วคราว) เมื่อเห็นออเดอร์เปลี่ยนเป็น processing ค่อยเปิดใช้จริง
- แสดงผลแบบไม่รีโหลดหน้า ถ้าอยากให้ผลขึ้นในหน้าเดิมโดยไม่ redirect ให้เปลี่ยน handler ไปใช้
wp_ajax_slipdeaw_upload/wp_ajax_nopriv_slipdeaw_uploadแล้วตอบ JSON ตรรกะเหมือนกันทุกอย่าง
แนวปฏิบัติที่แนะนำ
อย่าเชื่อยอดจาก 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 แจ้งเตือนร้านทางไลน์ได้ทั้งแชทเดี่ยวและกลุ่ม โดยไม่ต้องเขียนโค้ด ตั้งครั้งเดียวในพอร์ทัล
- แอดไลน์ @117uxbbe แล้วพิมพ์
idบอทจะตอบ userId ของคุณกลับมา - ถ้าอยากให้แจ้งเข้ากลุ่มทีมร้าน ให้ดึงบอทเข้ากลุ่ม บอทจะตอบ groupId ให้ทันที (หรือพิมพ์
idในกลุ่มก็ได้) - เข้าพอร์ทัล my.slipdeaw.com ไปที่ตั้งค่า > LINE แล้ววาง userId และ/หรือ groupId (ใส่ได้ทั้งคู่)
- เลือก 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.expiringsubscription.suspendedinvoice.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 มา)