POST https://<your-api-domain>/slip/upload

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 ที่รออยู่ (ยอดเงิน, บัญชีปลายทาง) แล้วตั้งสถานะรอรีวิว หรือปฏิเสธ

When to use slip upload
ใช้เมื่อไหร่

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

FieldTypeRequiredNotes ฟิลด์ประเภทบังคับหมายเหตุ
merchant_idstring Yes Your merchant identifier. merchant_idstring Yes รหัส merchant
tokenstring Yes Your API token. tokenstring Yes API token
time number | string Yes Unix epoch (seconds). See Authentication. time number | string Yes Unix epoch (วินาที) — ดู Authentication
platform_order_idstring Yes Payment order ID (mode marker must be P). Slip upload is only valid for payment orders, not withdraw or settlement. platform_order_idstring Yes Payment order ID (ตัว mode ต้องเป็น P) — ใช้ได้กับ payment order เท่านั้น
image_base64string 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_base64string 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

JSON
{
  "code":    200,
  "message": "Success",
  "data":    {
    "msg":              "slip upload successful.",
    "verification_msg": "amount ok, destination ok, transfer in time."
  },
  "success": true
}

Response fields

Response fields

FieldTypeDescription ฟิลด์ประเภทคำอธิบาย
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 เป็นข้อความ — ดูตัวอย่างด้านล่าง
A 200 success doesn't mean the slip was accepted as proof of payment
200 success ไม่ได้แปลว่าสลิปถูกยอมรับเป็นหลักฐานการจ่าย

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 ที่พบบ่อย:

MessageMeaning ข้อความความหมาย
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

JSON
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 open status. Uploading a slip for an order that has already moved to success or failed returns "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/upload on an order whose order_datetime is 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 success or leaves it for further investigation. The validation result reflected in verification_msg is informational — final settlement decisions go through human review.
  • เมื่ออัปโหลดสำเร็จ — รูปสลิปถูกเก็บที่ S3 และผูกกับ order — order จะถูก flag ว่ารอ admin รีวิว (ไม่ว่าผลตรวจ QR จะผ่านหรือไม่) — operations จะรีวิวและตัดสินว่าจะ mark success หรือเก็บไว้ตรวจสอบเพิ่ม — verification_msg เป็น informational — การตัดสินใจสุดท้ายผ่านการรีวิวของคน