Submit a payment slip image (base64-encoded) for an existing payment order. The system extracts the QR data, validates against the order's expected amount and destination, then marks the order as awaiting review or rejects it.
ส่งภาพสลิป (base64) สำหรับ payment order ที่ยังไม่จบ
จากนั้นระบบจะอ่าน QR ในสลิป ตรวจสอบกับ order ที่รออยู่ (ยอดเงินและบัญชีปลายทาง) แล้วตั้งสถานะรอรีวิว หรือปฏิเสธ
Use this endpoint when a payment is suspected to have been made but not detected automatically (e.g. customer transferred a different amount, used a different account). Most successful payments don't require slip upload — the system detects them automatically and fires the callback.
ใช้กับกรณีที่สงสัยว่าลูกค้าโอนแล้วแต่ระบบยังไม่จับ (เช่น โอนยอดผิด หรือใช้บัญชีอื่น)
ทั้งนี้ payment ที่สำเร็จส่วนใหญ่ไม่ต้องอัปโหลดสลิป ระบบจะจับเงินและยิง callback ให้เอง
Request
Request
Body fields
Body fields
| Field | Type | Required | Notes | ฟิลด์ | ประเภท | บังคับ | หมายเหตุ |
|---|---|---|---|---|---|---|---|
| merchant_id | string | Yes | Your merchant identifier. | merchant_id | string | Yes | รหัส merchant |
| token | string | Yes | Your API token. | token | string | Yes | API token |
| time | number | string | Yes | Unix epoch (seconds). See Authentication. | time | number | string | Yes | Unix epoch (วินาที) — ดู Authentication |
| platform_order_id | string | Yes | Payment order ID (mode marker must be P). Slip upload is only valid for payment orders, not withdraw or settlement. |
platform_order_id | string | Yes | Payment order ID (ตัว mode ต้องเป็น P) — ใช้ได้กับ payment order เท่านั้น |
| image_base64 | string | Yes | Base64-encoded image bytes (PNG or JPG). Max ~2 MB after decoding. Don't include the data:image/...;base64, prefix — just the encoded bytes. |
image_base64 | string | Yes | Base64 ของไฟล์ภาพ (PNG หรือ JPG) สูงสุด ~2 MB หลัง decode อย่าใส่ prefix data:image/...;base64, ส่งแค่ encoded bytes |
Code samples
ตัวอย่างโค้ด
# Encode the slip image to base64 first
B64=$(base64 -i slip.jpg | tr -d '\n')
BODY=$(jq -nc \
--arg mid "AA12345678" \
--arg tok "YOUR_TOKEN" \
--argjson t $(date +%s) \
--arg pid "ABCP20260508abc123XYZ456" \
--arg img "$B64" \
'{merchant_id:$mid, token:$tok, time:$t, platform_order_id:$pid, image_base64:$img}')
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "YOUR_SECRET" | awk '{print $2}')
curl -X POST https://<your-api-domain>/slip/upload \
-H "Content-Type: application/json" \
-H "X-SIGNATURE: $SIG" \
-d "$BODY"
import crypto from 'node:crypto';
import fs from 'node:fs';
const SECRET = 'YOUR_SECRET';
const imageB64 = fs.readFileSync('./slip.jpg').toString('base64');
const body = JSON.stringify({
merchant_id: 'AA12345678',
token: 'YOUR_TOKEN',
time: Math.floor(Date.now() / 1000),
platform_order_id: 'ABCP20260508abc123XYZ456',
image_base64: imageB64,
});
const sig = crypto.createHmac('sha256', SECRET).update(body).digest('hex');
const r = await fetch('https://<your-api-domain>/slip/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-SIGNATURE': sig },
body
});
console.log(await r.json());
<?php
$secret = 'YOUR_SECRET';
$imageB64 = base64_encode(file_get_contents('slip.jpg'));
$body = json_encode([
'merchant_id' => 'AA12345678',
'token' => 'YOUR_TOKEN',
'time' => time(),
'platform_order_id' => 'ABCP20260508abc123XYZ456',
'image_base64' => $imageB64,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$sig = hash_hmac('sha256', $body, $secret);
$ch = curl_init('https://<your-api-domain>/slip/upload');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-SIGNATURE: $sig",
],
]);
echo curl_exec($ch);
Response
Response
Success — HTTP 200
สำเร็จ — HTTP 200
{
"code": 200,
"message": "Success",
"data": {
"msg": "slip upload successful.",
"verification_msg": "amount ok, destination ok, transfer in time."
},
"success": true
}
Response fields
Response fields
| Field | Type | Description | ฟิลด์ | ประเภท | คำอธิบาย |
|---|---|---|---|---|---|
| data.msg | string | Always "slip upload successful." on success — confirms the upload was accepted by the system. |
data.msg | string | เป็น "slip upload successful." เสมอเมื่อสำเร็จ — ยืนยันว่าระบบรับสลิปแล้ว |
| data.verification_msg | string | Human-readable result of the QR/transaction validation against the order. See sample messages below. | data.verification_msg | string | ผลการตรวจสอบ QR/รายการเทียบกับ order เป็นข้อความ — ดูตัวอย่างด้านล่าง |
HTTP 200 only means the upload itself was processed without an upstream error. Read verification_msg to see whether the validation actually passed (e.g. "amount ok, destination ok, transfer in time.") or failed (e.g. "destination error, amount mismatch, transfer expired."). Order status only advances when the validation passes.
HTTP 200 แค่หมายความว่าระบบรับการอัปโหลดได้โดยไม่ error
ต้องอ่าน verification_msg เพื่อดูว่าตรวจสอบผ่านจริงหรือเปล่า (เช่น "amount ok, destination ok, transfer in time.") หรือไม่ผ่าน (เช่น "destination error, amount mismatch, transfer expired.")
ทั้งนี้สถานะของ order จะเปลี่ยนเมื่อ validation ผ่านเท่านั้น
Sample verification_msg values
ตัวอย่างค่าของ verification_msg
The string is composed from comma-separated check results. Common combinations:
ข้อความถูกประกอบจากผลตรวจหลายอย่าง คั่นด้วย comma — combinations ที่พบบ่อย:
| Message | Meaning | ข้อความ | ความหมาย |
|---|---|---|---|
slip received, verification pending. |
Pending state, not a failure. The slip was accepted and verification will complete later (out-of-band). Exact-match parsers should treat this value as pending, not pass or fail. Seen on the enduser payment-page upload path, where verify verdicts are never returned to the enduser — only this pending receipt, with the real outcome arriving afterwards. | slip received, verification pending. |
สถานะ pending ไม่ใช่ failure ระบบรับสลิปแล้ว แต่การตรวจสอบจะเสร็จภายหลัง (out-of-band) parser ที่ exact-match ควรถือว่าค่านี้เป็น pending ไม่ใช่ผ่านหรือไม่ผ่าน พบบนเส้นทางอัปโหลดผ่านหน้า payment ของ enduser ซึ่งจะไม่คืนผลตรวจให้ enduser เห็น (เห็นแค่ใบรับ pending นี้ ส่วนผลจริงมาทีหลัง) |
amount ok, destination ok, transfer in time. |
QR-type slip — all three checks passed; the order will be marked accordingly. | amount ok, destination ok, transfer in time. |
สลิปแบบ QR — ผ่านทั้ง 3 เงื่อนไข — order จะถูก mark ให้ |
amount ok, destination ok (TRANSFER), transfer in time. |
TRANSFER-type slip — all three checks passed. | amount ok, destination ok (TRANSFER), transfer in time. |
สลิปแบบ TRANSFER — ผ่านทั้ง 3 เงื่อนไข |
ref ok, transfer in time. |
QR slip — payment reference matched (preferred path; supersedes amount/destination checks). | ref ok, transfer in time. |
สลิป QR — payment reference ตรง (เส้นทางที่ดีที่สุด — ไม่ต้องเช็ค amount/destination) |
destination error, amount mismatch, transfer expired. |
All three checks failed. | destination error, amount mismatch, transfer expired. |
ผิดทั้ง 3 เงื่อนไข |
… , transfer before create. |
Suffix added when the slip's transaction time is earlier than the order's creation time. Indicates a recycled / wrong slip. | … , transfer before create. |
Suffix ถ้าเวลา transaction ในสลิปเก่ากว่าเวลาสร้าง order — แสดงว่าใช้สลิปเก่าหรือสลิปผิด |
… , ref error |
Suffix on QR slips when the QR reference doesn't match the order. | … , ref error |
Suffix สำหรับสลิป QR เมื่อ ref ใน QR ไม่ตรงกับ order |
qrcode error |
QR could not be parsed from the slip image. | qrcode error |
อ่าน QR จากภาพสลิปไม่ได้ |
qrcode error - missing transaction date/time |
QR was parsed but lacks the transaction timestamp fields. | qrcode error - missing transaction date/time |
อ่าน QR ได้ แต่ขาดข้อมูลเวลา transaction |
can't read qr data. |
No QR could be detected in the image at all. | can't read qr data. |
ไม่พบ QR ในภาพเลย |
Errors
Errors
/slip/upload uses the same error format as every other endpoint:
HTTP 4xx with {"code", "error", "success": false, "message"}.
See Error Codes for the full reference.
/slip/upload ใช้รูปแบบ error เหมือน endpoint อื่นทุกตัว — HTTP 4xx พร้อม
{"code", "error", "success": false, "message"} —
ดู Error Codes เพิ่มเติม
| HTTP | Error code | When it happens | Error code | เกิดเมื่อ |
|---|---|---|---|---|
| 422 | invalid-inputs |
Missing field (platform_order_id, image_base64), invalid platform_order_id format, image larger than 2,000,000 bytes, or image is not PNG/JPG. The message field tells you which. |
invalid-inputs |
Field ขาด (platform_order_id, image_base64), รูปแบบ platform_order_id ผิด, รูปใหญ่เกิน 2,000,000 bytes, หรือไม่ใช่ PNG/JPG — ดูใน message ว่าเป็นอันไหน |
| 404 | not-found |
Payment order not found, or it's owned by a different merchant. message: "payment order not found". |
not-found |
ไม่พบ payment order หรือเป็นของ merchant อื่น — message: "payment order not found" |
| 403 | permission-denied |
Order is not in open status (already success/failed) — message: "order status is not open". Or the order is older than 30 days — message: "order is older than 30 days". |
permission-denied |
Order ไม่ใช่สถานะ open (เป็น success/failed ไปแล้ว) — message: "order status is not open" หรือ order เก่ากว่า 30 วัน — message: "order is older than 30 days" |
| 429 | too-many-requests |
Rate limit. Either you uploaded another slip for the same order within the past 5 minutes ("please wait 5 minutes before next upload"), or the order is too fresh — must wait the configured minimum delay after order creation ("please wait N seconds before next upload"). |
too-many-requests |
Rate limit — อัปโหลดสลิปสำหรับ order เดียวกันใน 5 นาทีที่ผ่านมา ("please wait 5 minutes before next upload") หรือ order ยังใหม่เกินไป ต้องรอตาม minimum delay ที่ตั้งไว้ ("please wait N seconds before next upload") |
Auth errors (signature-error, authentication-failed, etc.) are handled by the auth middleware. See Authentication errors.
Auth error (signature-error, authentication-failed ฯลฯ) จัดการโดย auth middleware — ดู Authentication errors
Example error response
ตัวอย่าง error response
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"code": 422,
"error": "invalid-inputs",
"success": false,
"message": "image too large (max 2,000,000 bytes)"
}
Behavior & notes
พฤติกรรม & หมายเหตุ
- Order must be in
openstatus. Uploading a slip for an order that has already moved tosuccessorfailedreturns"order status must be 'pending'". - Order ต้องสถานะ
open— ถ้า order ขยับเป็นsuccessหรือfailedไปแล้ว จะส่งกลับ error"order status must be 'pending'" - The order itself must be no older than 30 days. If you call
/slip/uploadon an order whoseorder_datetimeis more than 30 days ago, the response is"slip upload expired.". - Order ต้องสร้างมาไม่เกิน 30 วัน — ถ้าเรียกบน order ที่
order_datetimeเกิน 30 วัน จะได้ error"slip upload expired." - Minimum wait time after order creation. Some accounts have a minimum delay (
slip_upload_wait_time, in seconds) between order creation and the first slip upload. Calling too early returns"please wait N seconds and try uploading again.". Ask your payment gateway admin for the value. - ต้องรอ N วินาทีหลังสร้าง order
อาจมีการตั้ง delay ขั้นต่ำสำหรับบัญชีคุณ (slip_upload_wait_time, หน่วยวินาที) ระหว่างสร้าง order กับการอัปโหลดสลิปครั้งแรก
ถ้าเรียกเร็วเกินจะได้"please wait N seconds and try uploading again."
ทั้งนี้ติดต่อ Payment gateway admin เพื่อรับค่า - Rate limit: 1 upload per order per 5 minutes. Re-uploading within the same 5-minute window returns
"please wait 5 minutes before upload again.". - Rate limit: 1 ครั้งต่อ order ใน 5 นาที — อัปโหลดซ้ำในช่วง 5 นาทีจะได้
"please wait 5 minutes before upload again." - Image format. PNG and JPG only, verified via magic bytes. Maximum 2,000,000 bytes after base64 decoding.
- รูปแบบไฟล์ — PNG หรือ JPG เท่านั้น (ตรวจจาก magic bytes) — สูงสุด 2,000,000 bytes หลัง decode base64
- What happens on a successful upload. The slip image is stored on S3, attached to the order, and the order is flagged as awaiting admin review (regardless of whether QR validation passed). Operations then reviews and either marks the order
successor leaves it for further investigation. The validation result reflected inverification_msgis informational — final settlement decisions go through human review. - เมื่ออัปโหลดสำเร็จ
รูปสลิปถูกเก็บที่ S3 และผูกกับ order
order จะถูก flag ว่ารอ admin รีวิว (ไม่ว่าผลตรวจ QR จะผ่านหรือไม่)
operations จะรีวิวและตัดสินว่าจะ marksuccessหรือเก็บไว้ตรวจสอบเพิ่มverification_msgเป็น informational
ทั้งนี้การตัดสินใจสุดท้ายผ่านการรีวิวของคน
This endpoint is the merchant-facing API and keeps its original gates: ~2 MB body cap, a minimum wait after order creation before the first upload, the 5-minute per-order re-upload limit, and the 30-day order-age limit described above.
Endpoint นี้คือ API ฝั่ง merchant และยังคงใช้ gate เดิม — body cap ~2 MB, ต้องรอขั้นต่ำหลังสร้าง order ก่อนอัปโหลดครั้งแรก, จำกัดอัปโหลดซ้ำ 5 นาทีต่อ order, และ order เก่าได้ไม่เกิน 30 วัน ตามที่อธิบายไว้ด้านบน
A separate enduser payment-page upload path behaves differently: the pre-upload wait-gate was removed (a slip can be uploaded immediately after the order is created), it accepts a larger request body (~8 MB), and it is governed by a per-order cooldown, a max-uploads-per-order cap, and a max-age limit, plus a per-IP rate limit of 10 requests per minute. On that path the enduser never sees verify verdicts — only a "slip received, verification pending." receipt, with the outcome arriving later. Do not assume the payment-page limits apply here (or vice versa).
อีกเส้นทางหนึ่งคือ การอัปโหลดผ่านหน้า payment ของ enduser ซึ่งทำงานต่างกัน
ตัด wait-gate ก่อนอัปโหลดออกแล้ว จึงอัปโหลดสลิปได้ทันทีหลังสร้าง order
รับ request body ใหญ่ขึ้น (~8 MB)
ถูกควบคุมด้วย cooldown ต่อ order เพดานจำนวนครั้งต่อ order และขีดจำกัดอายุ order พร้อม rate limit ต่อ IP ที่ 10 ครั้งต่อนาที
บนเส้นทางนั้น enduser จะไม่เห็นผลตรวจ เห็นแค่ใบรับ "slip received, verification pending." ส่วนผลจริงมาทีหลัง
อย่าเอา limit ของหน้า payment มาใช้กับ endpoint นี้ (หรือกลับกัน)