// slipbolt.js — Node.js 18+ · ไม่ต้องลงแพ็กเกจเพิ่ม
const BASE = 'https://api.slip-bolt.com/api/v1' ;
export class SlipBoltError extends Error {
constructor ( status , body ) {
super ( `${ status } ${ body . code ?? body . error ?? ''}: ${ body . message ?? 'request failed'}` );
this .status = status; // HTTP status เช่น 402
this .code = body.code; // เช่น QUOTA_EXCEEDED
this .requestId = body.requestId; // แนบมาตอนติดต่อทีมงาน
}
}
const sleep = ( ms ) => new Promise (( resolve ) => setTimeout (resolve, ms));
/** ตรวจสลิป 1 ใบ — คืนก้อน `data` · โยน SlipBoltError เมื่อ API ตอบ error */
export async function verifySlip ( image , { apiKey = process.env. SLIPBOLT_API_KEY , retries = 3 } = {}) {
for ( let attempt = 0 ; ; attempt ++ ) {
let res;
try {
res = await fetch ( `${ BASE }/verify` , {
method: 'POST' ,
headers: { Authorization: `Bearer ${ apiKey }` , 'Content-Type' : 'application/octet-stream' },
body: image,
signal: AbortSignal. timeout ( 30_000 ),
});
} catch (err) {
if (attempt >= retries) throw err; // เน็ตหลุด / timeout ครบจำนวนครั้งแล้ว
await sleep ( 500 * 2 ** attempt);
continue ;
}
if ((res.status === 429 || res.status >= 500 ) && attempt < retries) {
await sleep (( Number (res.headers. get ( 'retry-after' )) || 2 ** attempt) * 1000 );
continue ;
}
const body = await res. json (). catch (() => ({}));
if ( ! res.ok) throw new SlipBoltError (res.status, body);
return body.data; // ผลอยู่ใน data เสมอ
}
}
# slipbolt.py — pip install requests
import os
import time
import requests
BASE = "https://api.slip-bolt.com/api/v1"
class SlipBoltError ( Exception ):
def __init__ (self, status: int , body: dict ):
super (). __init__ (
f " { status } { body.get( 'code' ) or body.get( 'error' ) or '' } : { body.get( 'message' ) or 'request failed' } "
)
self .status = status # HTTP status เช่น 402
self .code = body.get( "code" ) # เช่น QUOTA_EXCEEDED
self .request_id = body.get( "requestId" ) # แนบมาตอนติดต่อทีมงาน
def verify_slip (image: bytes , api_key: str = "" , retries: int = 3 ) -> dict :
"""ตรวจสลิป 1 ใบ — คืนก้อน data · โยน SlipBoltError เมื่อ API ตอบ error"""
api_key = api_key or os.environ[ "SLIPBOLT_API_KEY" ]
for attempt in range (retries + 1 ):
last = attempt == retries
try :
res = requests.post(
f " {BASE} /verify" ,
headers = { "Authorization" : f "Bearer { api_key } " , "Content-Type" : "application/octet-stream" },
data = image,
timeout = 30 ,
)
except requests.RequestException:
if last:
raise # เน็ตหลุด / timeout ครบจำนวนครั้งแล้ว
time.sleep( 0.5 * 2 ** attempt)
continue
if (res.status_code == 429 or res.status_code >= 500 ) and not last:
time.sleep( float (res.headers.get( "Retry-After" ) or 2 ** attempt))
continue
try :
body = res.json()
except ValueError :
body = {}
if not res.ok:
raise SlipBoltError(res.status_code, body)
return body[ "data" ] # ผลอยู่ใน data เสมอ
<? php
// slipbolt.php — PHP 8.1+ · ใช้ ext-curl ไม่ต้องใช้ Composer
class SlipBoltError extends RuntimeException
{
public function __construct ( public int $status, public array $body)
{
parent:: __construct (
" $status " . ($body[ 'code' ] ?? $body[ 'error' ] ?? '' ) . ': ' . ($body[ 'message' ] ?? 'request failed' )
);
}
}
/** ตรวจสลิป 1 ใบ — คืนก้อน data · โยน SlipBoltError เมื่อ API ตอบ error */
function slipbolt_verify ( string $image, int $retries = 3 ) : array
{
for ($attempt = 0 ; ; $attempt ++ ) {
$retryAfter = 0 ;
$ch = curl_init ( 'https://api.slip-bolt.com/api/v1/verify' );
curl_setopt_array ($ch, [
CURLOPT_POST => true ,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv ( 'SLIPBOLT_API_KEY' ),
'Content-Type: application/octet-stream' ,
],
CURLOPT_POSTFIELDS => $image,
CURLOPT_RETURNTRANSFER => true ,
CURLOPT_TIMEOUT => 30 ,
CURLOPT_HEADERFUNCTION => function ($ch, string $line) use ( & $retryAfter) {
if ( stripos ($line, 'retry-after:' ) === 0 ) {
$retryAfter = ( int ) trim ( substr ($line, 12 ));
}
return strlen ($line);
},
]);
$raw = curl_exec ($ch);
$status = curl_getinfo ($ch, CURLINFO_HTTP_CODE );
// เน็ตหลุด / timeout / 429 / 5xx → รอแล้วลองใหม่
if (($raw === false || $status === 429 || $status >= 500 ) && $attempt < $retries) {
sleep ($retryAfter ?: 2 ** $attempt);
continue ;
}
if ($raw === false ) {
throw new RuntimeException ( 'เชื่อมต่อไม่ได้: ' . curl_error ($ch));
}
$body = json_decode ($raw, true ) ?? [];
if ($status !== 200 ) {
throw new SlipBoltError ($status, $body);
}
return $body[ 'data' ]; // ผลอยู่ใน data เสมอ
}
}
โจทย์ที่เจอบ่อยที่สุด: ลูกค้าอัปโหลดสลิปในหน้าชำระเงิน → เซิร์ฟเวอร์คุณตรวจ → ปิดออเดอร์อัตโนมัติ
Node.js (Express) Python (Flask) PHP
คัดลอก // server.js — npm install express
import express from 'express' ;
import { verifySlip } from './slipbolt.js' ;
const app = express ();
// ของจริง = ตารางในฐานข้อมูล · usedRefs = คอลัมน์ transRef ที่ติด UNIQUE
const orders = new Map ([[ '1001' , { total: 250 , paid: false }]]);
const usedRefs = new Set ();
// ข้อความที่บอกลูกค้าเมื่อสลิปไม่ผ่าน
const REJECT = {
DUPLICATE: 'สลิปนี้ถูกใช้ไปแล้ว' ,
FRAUD: 'สลิปไม่ถูกต้อง กรุณาติดต่อร้าน' ,
RECEIVER_MISMATCH: 'สลิปนี้ไม่ได้โอนเข้าบัญชีของร้าน' ,
INVALID: 'อ่านสลิปไม่ได้ ลองอัปโหลดรูปที่เห็น QR ชัด ๆ' ,
PROVIDER_ERROR: 'ระบบตรวจสลิปขัดข้องชั่วคราว ลองส่งใหม่อีกครั้ง' ,
};
app. post ( '/orders/:id/slip' , express. raw ({ type: 'image/*' , limit: '5mb' }), async ( req , res ) => {
const order = orders. get (req.params.id);
if ( ! order) return res. status ( 404 ). json ({ ok: false , message: 'ไม่พบออเดอร์' });
if (order.paid) return res. json ({ ok: true , message: 'ออเดอร์นี้ชำระแล้ว' });
if ( ! Buffer. isBuffer (req.body) || req.body. length === 0 ) {
return res. status ( 400 ). json ({ ok: false , message: 'กรุณาแนบรูปสลิป' });
}
let slip;
try {
slip = await verifySlip (req.body);
} catch (err) {
console. error ( 'SlipBolt:' , err); // 402 = โควตาร้านหมด · 401/403 = คีย์หรือแพ็ก → แจ้งเตือนทีมคุณ
return res. status ( 503 ). json ({ ok: false , message: 'ตรวจสลิปอัตโนมัติไม่ได้ชั่วคราว ลองใหม่หรือติดต่อร้าน' });
}
// 1) สลิปจริงและเข้าบัญชีร้าน 2) ไม่ใช่สลิปที่เคยตรวจแล้ว
if ( ! slip.verified || slip.duplicate) {
return res. status ( 422 ). json ({ ok: false , message: REJECT [slip.status] ?? 'สลิปไม่ผ่านการตรวจ' });
}
// 3) ยอดตรงกับออเดอร์ — API ไม่รู้ว่าออเดอร์นี้ต้องจ่ายเท่าไร
if (Math. abs (slip.amount - order.total) > 0.001 ) {
return res. status ( 422 ). json ({
ok: false ,
message: `ยอดโอน ${ slip . amount } บาท ไม่ตรงกับยอดออเดอร์ ${ order . total } บาท` ,
});
}
// 4) สลิปใบเดียวปิดได้ออเดอร์เดียว
if (usedRefs. has (slip.transRef)) {
return res. status ( 422 ). json ({ ok: false , message: 'สลิปนี้ถูกใช้กับออเดอร์อื่นแล้ว' });
}
usedRefs. add (slip.transRef);
order.paid = true ;
order.transRef = slip.transRef;
res. json ({ ok: true , message: 'ชำระเงินเรียบร้อย ขอบคุณค่ะ' , transRef: slip.transRef });
});
app. listen ( 3000 );
# app.py — pip install flask requests
from flask import Flask, jsonify, request
from slipbolt import verify_slip
app = Flask( __name__ )
# ของจริง = ตารางในฐานข้อมูล · used_refs = คอลัมน์ transRef ที่ติด UNIQUE
orders = { "1001" : { "total" : 250 , "paid" : False }}
used_refs: set[ str ] = set ()
# ข้อความที่บอกลูกค้าเมื่อสลิปไม่ผ่าน
REJECT = {
"DUPLICATE" : "สลิปนี้ถูกใช้ไปแล้ว" ,
"FRAUD" : "สลิปไม่ถูกต้อง กรุณาติดต่อร้าน" ,
"RECEIVER_MISMATCH" : "สลิปนี้ไม่ได้โอนเข้าบัญชีของร้าน" ,
"INVALID" : "อ่านสลิปไม่ได้ ลองอัปโหลดรูปที่เห็น QR ชัด ๆ" ,
"PROVIDER_ERROR" : "ระบบตรวจสลิปขัดข้องชั่วคราว ลองส่งใหม่อีกครั้ง" ,
}
def reply (status: int , ok: bool , message: str , ** extra):
return jsonify( ok = ok, message = message, ** extra), status
@app.post ( "/orders/<order_id>/slip" )
def pay_with_slip (order_id: str ):
order = orders.get(order_id)
if order is None :
return reply( 404 , False , "ไม่พบออเดอร์" )
if order[ "paid" ]:
return reply( 200 , True , "ออเดอร์นี้ชำระแล้ว" )
image = request.get_data()
if not image:
return reply( 400 , False , "กรุณาแนบรูปสลิป" )
try :
slip = verify_slip(image)
except Exception as err: # 402 = โควตาร้านหมด · 401/403 = คีย์หรือแพ็ก → แจ้งเตือนทีมคุณ
app.logger.error( "SlipBolt: %s " , err)
return reply( 503 , False , "ตรวจสลิปอัตโนมัติไม่ได้ชั่วคราว ลองใหม่หรือติดต่อร้าน" )
# 1) สลิปจริงและเข้าบัญชีร้าน 2) ไม่ใช่สลิปที่เคยตรวจแล้ว
if not slip[ "verified" ] or slip[ "duplicate" ]:
return reply( 422 , False , REJECT .get(slip[ "status" ], "สลิปไม่ผ่านการตรวจ" ))
# 3) ยอดตรงกับออเดอร์ — API ไม่รู้ว่าออเดอร์นี้ต้องจ่ายเท่าไร
if abs (slip[ "amount" ] - order[ "total" ]) > 0.001 :
return reply( 422 , False , f "ยอดโอน { slip[ 'amount' ] } บาท ไม่ตรงกับยอดออเดอร์ { order[ 'total' ] } บาท" )
# 4) สลิปใบเดียวปิดได้ออเดอร์เดียว
if slip[ "transRef" ] in used_refs:
return reply( 422 , False , "สลิปนี้ถูกใช้กับออเดอร์อื่นแล้ว" )
used_refs.add(slip[ "transRef" ])
order.update( paid = True , transRef = slip[ "transRef" ])
return reply( 200 , True , "ชำระเงินเรียบร้อย ขอบคุณค่ะ" , transRef = slip[ "transRef" ])
if __name__ == "__main__" :
app.run( port = 3000 )
<? php
// pay.php?order=1001 — ตาราง: orders(id, total, trans_ref UNIQUE NULL)
require __DIR__ . '/slipbolt.php' ;
function reply ( int $status, bool $ok, string $message) : never
{
http_response_code ($status);
header ( 'Content-Type: application/json; charset=utf-8' );
echo json_encode ([ 'ok' => $ok, 'message' => $message], JSON_UNESCAPED_UNICODE );
exit ;
}
// ข้อความที่บอกลูกค้าเมื่อสลิปไม่ผ่าน
const REJECT = [
'DUPLICATE' => 'สลิปนี้ถูกใช้ไปแล้ว' ,
'FRAUD' => 'สลิปไม่ถูกต้อง กรุณาติดต่อร้าน' ,
'RECEIVER_MISMATCH' => 'สลิปนี้ไม่ได้โอนเข้าบัญชีของร้าน' ,
'INVALID' => 'อ่านสลิปไม่ได้ ลองอัปโหลดรูปที่เห็น QR ชัด ๆ' ,
'PROVIDER_ERROR' => 'ระบบตรวจสลิปขัดข้องชั่วคราว ลองส่งใหม่อีกครั้ง' ,
];
$pdo = new PDO ( getenv ( 'DB_DSN' ), getenv ( 'DB_USER' ) ?: null , getenv ( 'DB_PASS' ) ?: null , [
PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION ,
]);
$find = $pdo -> prepare ( ' SELECT id, total, trans_ref FROM orders WHERE id = ?' );
$find -> execute ([$_GET[ 'order' ] ?? '' ]);
$order = $find -> fetch ( PDO :: FETCH_ASSOC );
if ( ! $order) reply ( 404 , false , 'ไม่พบออเดอร์' );
if ($order[ 'trans_ref' ] !== null ) reply ( 200 , true , 'ออเดอร์นี้ชำระแล้ว' );
$image = file_get_contents ( 'php://input' ); // รูปสลิปที่เบราว์เซอร์ส่งมาเป็น body
if ($image === '' || $image === false ) reply ( 400 , false , 'กรุณาแนบรูปสลิป' );
try {
$slip = slipbolt_verify ($image);
} catch ( Throwable $err) { // 402 = โควตาร้านหมด · 401/403 = คีย์หรือแพ็ก → แจ้งเตือนทีมคุณ
error_log ( 'SlipBolt: ' . $err -> getMessage ());
reply ( 503 , false , 'ตรวจสลิปอัตโนมัติไม่ได้ชั่วคราว ลองใหม่หรือติดต่อร้าน' );
}
// 1) สลิปจริงและเข้าบัญชีร้าน 2) ไม่ใช่สลิปที่เคยตรวจแล้ว
if ( ! $slip[ 'verified' ] || $slip[ 'duplicate' ]) {
reply ( 422 , false , REJECT [$slip[ 'status' ]] ?? 'สลิปไม่ผ่านการตรวจ' );
}
// 3) ยอดตรงกับออเดอร์ — API ไม่รู้ว่าออเดอร์นี้ต้องจ่ายเท่าไร
if ( abs ($slip[ 'amount' ] - ( float ) $order[ 'total' ]) > 0.001 ) {
reply ( 422 , false , "ยอดโอน { $slip ['amount']} บาท ไม่ตรงกับยอดออเดอร์ { $order ['total']} บาท" );
}
// 4) สลิปใบเดียวปิดได้ออเดอร์เดียว — ให้ UNIQUE ของฐานข้อมูลเป็นคนตัดสิน (กันสองคำขอชนกันได้ด้วย)
try {
$pay = $pdo -> prepare ( ' UPDATE orders SET trans_ref = ? WHERE id = ? AND trans_ref IS NULL ' );
$pay -> execute ([$slip[ 'transRef' ], $order[ 'id' ]]);
} catch ( PDOException $err) {
reply ( 422 , false , 'สลิปนี้ถูกใช้กับออเดอร์อื่นแล้ว' );
}
reply ( 200 , true , 'ชำระเงินเรียบร้อย ขอบคุณค่ะ' );
cURL Node.js Python PHP Go Java C#
คัดลอก API = "https://api.slip-bolt.com/api/v1/verify"
# 1) raw bytes
curl -X POST " $API " -H "Authorization: Bearer $SLIPBOLT_API_KEY " \
-H "Content-Type: application/octet-stream" --data-binary "@slip.jpg"
# 2) multipart/form-data — ฟิลด์ไฟล์ชื่อ file
curl -X POST " $API " -H "Authorization: Bearer $SLIPBOLT_API_KEY " \
-F "[email protected] "
# 3) JSON base64
base64 < slip.jpg | tr -d '\n' | sed 's/.*/{"image":"&"}/' |
curl -X POST " $API " -H "Authorization: Bearer $SLIPBOLT_API_KEY " \
-H "Content-Type: application/json" --data-binary @-
# 4) JSON url — ลิงก์ https ของรูปที่เปิดได้จากอินเทอร์เน็ต
curl -X POST " $API " -H "Authorization: Bearer $SLIPBOLT_API_KEY " \
-H "Content-Type: application/json" \
-d '{"url":"https://cdn.example.com/slips/1001.jpg"}'
# 5) JSON qr — ข้อความที่สแกนได้จาก QR บนสลิป
curl -X POST " $API " -H "Authorization: Bearer $SLIPBOLT_API_KEY " \
-H "Content-Type: application/json" \
-d '{"qr":"0046000600000101030140225202610075020019535816MIR5102TH9104A1B2"}'
import { readFile } from 'node:fs/promises' ;
const API = 'https://api.slip-bolt.com/api/v1/verify' ;
const auth = { Authorization: `Bearer ${ process . env . SLIPBOLT_API_KEY }` };
const bytes = await readFile ( 'slip.jpg' );
// multipart/form-data — ฟิลด์ไฟล์ชื่อ file
const form = new FormData ();
form. append ( 'file' , new Blob ([bytes]), 'slip.jpg' );
const viaForm = await fetch ( API , { method: 'POST' , headers: auth, body: form });
// JSON base64 — รับทั้ง base64 ล้วนและ data:image/...;base64,...
const viaJson = await fetch ( API , {
method: 'POST' ,
headers: { ... auth, 'Content-Type' : 'application/json' },
body: JSON . stringify ({ image: bytes. toString ( 'base64' ) }),
});
for ( const res of [viaForm, viaJson]) {
const body = await res. json ();
console. log (res.status, body.data?.status ?? body.message);
}
import base64
import os
import requests
API = "https://api.slip-bolt.com/api/v1/verify"
auth = { "Authorization" : f "Bearer { os.environ[ 'SLIPBOLT_API_KEY' ] } " }
with open ( "slip.jpg" , "rb" ) as f:
data = f.read()
# multipart/form-data — ฟิลด์ไฟล์ชื่อ file
via_form = requests.post( API , headers = auth, files = { "file" : ( "slip.jpg" , data)}, timeout = 30 )
# JSON base64 — รับทั้ง base64 ล้วนและ data:image/...;base64,...
via_json = requests.post( API , headers = auth, json = { "image" : base64.b64encode(data).decode()}, timeout = 30 )
for res in (via_form, via_json):
body = res.json()
print (res.status_code, body[ "data" ][ "status" ] if res.ok else body[ "message" ])
<? php
function slipbolt_post ( array $headers, $payload) : array
{
$ch = curl_init ( 'https://api.slip-bolt.com/api/v1/verify' );
curl_setopt_array ($ch, [
CURLOPT_POST => true ,
CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . getenv ( 'SLIPBOLT_API_KEY' ), ... $headers],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true ,
CURLOPT_TIMEOUT => 30 ,
]);
$body = json_decode ( curl_exec ($ch), true ) ?? [];
return [ curl_getinfo ($ch, CURLINFO_HTTP_CODE ), $body];
}
// multipart/form-data — ฟิลด์ไฟล์ชื่อ file
$viaForm = slipbolt_post ([], [ 'file' => new CURLFile ( 'slip.jpg' )]);
// JSON base64 — รับทั้ง base64 ล้วนและ data:image/...;base64,...
$viaJson = slipbolt_post (
[ 'Content-Type: application/json' ],
json_encode ([ 'image' => base64_encode ( file_get_contents ( 'slip.jpg' ))])
);
foreach ([$viaForm, $viaJson] as [$status, $body]) {
echo $status, ' ' , $body[ 'data' ][ 'status' ] ?? $body[ 'message' ], " \n " ;
}
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"time"
)
func main() {
api := "https://api.slip-bolt.com/api/v1/verify"
key := os.Getenv("SLIPBOLT_API_KEY")
data, err := os.ReadFile("slip.jpg")
if err != nil {
panic(err)
}
client := &http.Client{Timeout: 30 * time.Second}
// multipart/form-data — ฟิลด์ไฟล์ชื่อ file
var form bytes.Buffer
w := multipart.NewWriter(&form)
part, err := w.CreateFormFile("file", "slip.jpg")
if err != nil {
panic(err)
}
if _, err = part.Write(data); err != nil {
panic(err)
}
if err = w.Close(); err != nil {
panic(err)
}
viaForm := post(client, api, key, w.FormDataContentType(), form.Bytes())
// JSON base64 — รับทั้ง base64 ล้วนและ data:image/...;base64,...
payload, _ := json.Marshal(map[string]string{"image": base64.StdEncoding.EncodeToString(data)})
viaJSON := post(client, api, key, "application/json", payload)
for _, res := range []*http.Response{viaForm, viaJSON} {
show(res)
}
}
func post(client *http.Client, api, key, contentType string, body []byte) *http.Response {
req, err := http.NewRequest(http.MethodPost, api, bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", contentType)
res, err := client.Do(req)
if err != nil {
panic(err)
}
return res
}
func show(res *http.Response) {
defer res.Body.Close()
raw, _ := io.ReadAll(res.Body)
var body struct {
Data struct{ Status string } `json:"data"`
Message string `json:"message"`
}
_ = json.Unmarshal(raw, &body)
status := body.Data.Status
if status == "" {
status = body.Message
}
fmt.Println(res.StatusCode, status)
}
import java.net.URI;
import java.net.http. * ;
import java.nio.charset.StandardCharsets;
import java.nio.file. * ;
import java.time.Duration;
import java.util.Base64;
class VerifyFormats {
public static void main ( String [] args ) throws Exception {
String api = "https://api.slip-bolt.com/api/v1/verify" ;
String key = System. getenv ( "SLIPBOLT_API_KEY" );
byte [] data = Files. readAllBytes (Path. of ( "slip.jpg" ));
HttpClient client = HttpClient. newBuilder (). connectTimeout (Duration. ofSeconds ( 30 )). build ();
// multipart/form-data — ฟิลด์ไฟล์ชื่อ file
show ( send (client, api, key, "multipart/form-data; boundary=slipbolt" , multipart (data)));
// JSON base64 — รับทั้ง base64 ล้วนและ data:image/...;base64,...
String json = "{ \" image \" : \" " + Base64. getEncoder (). encodeToString (data) + " \" }" ;
show ( send (client, api, key, "application/json" , json. getBytes (StandardCharsets.UTF_8)));
}
static HttpResponse< String > send (HttpClient client , String api , String key , String type , byte [] body ) throws Exception {
HttpRequest req = HttpRequest. newBuilder (URI. create (api))
. timeout (Duration. ofSeconds ( 30 ))
. header ( "Authorization" , "Bearer " + key)
. header ( "Content-Type" , type)
. POST (HttpRequest.BodyPublishers. ofByteArray (body))
. build ();
return client. send (req, HttpResponse.BodyHandlers. ofString ());
}
static byte [] multipart ( byte [] data ) throws Exception {
String head = "--slipbolt \r\n Content-Disposition: form-data; name= \" file \" ; filename= \" slip.jpg \"\r\n Content-Type: image/jpeg \r\n\r\n " ;
String tail = " \r\n --slipbolt-- \r\n " ;
byte [] h = head. getBytes (StandardCharsets.UTF_8);
byte [] t = tail. getBytes (StandardCharsets.UTF_8);
byte [] out = new byte [h.length + data.length + t.length];
System. arraycopy (h, 0 , out, 0 , h.length);
System. arraycopy (data, 0 , out, h.length, data.length);
System. arraycopy (t, 0 , out, h.length + data.length, t.length);
return out;
}
static void show (HttpResponse< String > res ) {
String body = res. body ();
String status = jsonString (body, "status" );
if (status. isEmpty ()) status = jsonString (body, "message" );
System.out. println (res. statusCode () + " " + status);
}
static String jsonString (String json , String key ) {
int i = json. indexOf ( " \" " + key + " \" " );
if (i < 0 ) return "" ;
int q1 = json. indexOf ( '"' , json. indexOf ( ':' , i) + 1 );
int q2 = json. indexOf ( '"' , q1 + 1 );
return q1 < 0 || q2 < 0 ? "" : json. substring (q1 + 1 , q2);
}
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var api = "https://api.slip-bolt.com/api/v1/verify";
var key = Environment.GetEnvironmentVariable("SLIPBOLT_API_KEY")!;
var data = await File.ReadAllBytesAsync("slip.jpg");
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
// multipart/form-data — ฟิลด์ไฟล์ชื่อ file
using var form = new MultipartFormDataContent();
var file = new ByteArrayContent(data);
file.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
form.Add(file, "file", "slip.jpg");
using var viaForm = await http.SendAsync(authed(new HttpRequestMessage(HttpMethod.Post, api) { Content = form }));
// JSON base64 — รับทั้ง base64 ล้วนและ data:image/...;base64,...
var json = new StringContent(
JsonSerializer.Serialize(new { image = Convert.ToBase64String(data) }),
Encoding.UTF8,
"application/json");
using var viaJson = await http.SendAsync(authed(new HttpRequestMessage(HttpMethod.Post, api) { Content = json }));
foreach (var res in new[] { viaForm, viaJson })
{
using var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
string? status = null;
if (body.RootElement.TryGetProperty("data", out var d) && d.TryGetProperty("status", out var s)) status = s.GetString();
else if (body.RootElement.TryGetProperty("message", out var m)) status = m.GetString();
Console.WriteLine($"{(int)res.StatusCode} {status}");
}
HttpRequestMessage authed(HttpRequestMessage req)
{
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
return req;
}
Node.js Python PHP Go Java C#
คัดลอก const API = 'https://api.slip-bolt.com/api/v1/verify' ;
async function verifyJson ( payload ) {
const res = await fetch ( API , {
method: 'POST' ,
headers: {
Authorization: `Bearer ${ process . env . SLIPBOLT_API_KEY }` ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify (payload),
signal: AbortSignal. timeout ( 30_000 ),
});
const body = await res. json ();
if ( ! res.ok) throw new Error ( `${ res . status } ${ body . code ?? body . error }: ${ body . message }` );
return body.data;
}
const byUrl = await verifyJson ({ url: process.env. SLIP_URL });
const byQr = await verifyJson ({ qr: process.env. SLIP_QR });
console. log (byUrl.status, byQr.status);
import os
import requests
API = "https://api.slip-bolt.com/api/v1/verify"
def verify_json (payload: dict ) -> dict :
res = requests.post(
API ,
headers = { "Authorization" : f "Bearer { os.environ[ 'SLIPBOLT_API_KEY' ] } " },
json = payload,
timeout = 30 ,
)
body = res.json()
if not res.ok:
raise RuntimeError ( f " { res.status_code } { body.get( 'code' ) or body.get( 'error' ) } : { body.get( 'message' ) } " )
return body[ "data" ]
by_url = verify_json({ "url" : os.environ[ "SLIP_URL" ]})
by_qr = verify_json({ "qr" : os.environ[ "SLIP_QR" ]})
print (by_url[ "status" ], by_qr[ "status" ])
<? php
function slipbolt_verify_json ( array $payload) : array
{
$ch = curl_init ( 'https://api.slip-bolt.com/api/v1/verify' );
curl_setopt_array ($ch, [
CURLOPT_POST => true ,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv ( 'SLIPBOLT_API_KEY' ),
'Content-Type: application/json' ,
],
CURLOPT_POSTFIELDS => json_encode ($payload),
CURLOPT_RETURNTRANSFER => true ,
CURLOPT_TIMEOUT => 30 ,
]);
$body = json_decode ( curl_exec ($ch), true ) ?? [];
$status = curl_getinfo ($ch, CURLINFO_HTTP_CODE );
if ($status !== 200 ) {
throw new RuntimeException ( " $status " . ($body[ 'code' ] ?? $body[ 'error' ] ?? '' ) . ': ' . ($body[ 'message' ] ?? '' ));
}
return $body[ 'data' ];
}
$byUrl = slipbolt_verify_json ([ 'url' => getenv ( 'SLIP_URL' )]);
$byQr = slipbolt_verify_json ([ 'qr' => getenv ( 'SLIP_QR' )]);
echo $byUrl[ 'status' ], ' ' , $byQr[ 'status' ], " \n " ;
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
byURL := verifyJSON(map[string]string{"url": os.Getenv("SLIP_URL")})
byQR := verifyJSON(map[string]string{"qr": os.Getenv("SLIP_QR")})
fmt.Println(byURL["status"], byQR["status"])
}
func verifyJSON(payload map[string]string) map[string]any {
body, _ := json.Marshal(payload)
req, err := http.NewRequest(http.MethodPost, "https://api.slip-bolt.com/api/v1/verify", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("SLIPBOLT_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
raw, _ := io.ReadAll(res.Body)
var parsed struct {
Data map[string]any `json:"data"`
Code string `json:"code"`
Error string `json:"error"`
Message string `json:"message"`
}
_ = json.Unmarshal(raw, &parsed)
if res.StatusCode != 200 {
code := parsed.Code
if code == "" {
code = parsed.Error
}
panic(fmt.Sprintf("%d %s: %s", res.StatusCode, code, parsed.Message))
}
return parsed.Data
}
import java.net.URI;
import java.net.http. * ;
import java.time.Duration;
class VerifyJson {
public static void main ( String [] args ) throws Exception {
String byUrl = verify ( "{ \" url \" : \" " + System. getenv ( "SLIP_URL" ) + " \" }" );
String byQr = verify ( "{ \" qr \" : \" " + System. getenv ( "SLIP_QR" ) + " \" }" );
System.out. println (byUrl + " " + byQr);
}
static String verify (String json ) throws Exception {
HttpRequest req = HttpRequest. newBuilder (URI. create ( "https://api.slip-bolt.com/api/v1/verify" ))
. timeout (Duration. ofSeconds ( 30 ))
. header ( "Authorization" , "Bearer " + System. getenv ( "SLIPBOLT_API_KEY" ))
. header ( "Content-Type" , "application/json" )
. POST (HttpRequest.BodyPublishers. ofString (json))
. build ();
HttpClient client = HttpClient. newBuilder (). connectTimeout (Duration. ofSeconds ( 30 )). build ();
HttpResponse< String > res = client. send (req, HttpResponse.BodyHandlers. ofString ());
if (res. statusCode () != 200 ) {
String code = field (res. body (), "code" );
if (code. isEmpty ()) code = field (res. body (), "error" );
throw new RuntimeException (res. statusCode () + " " + code + ": " + field (res. body (), "message" ));
}
return field (res. body (), "status" );
}
static String field (String json , String key ) {
int i = json. indexOf ( " \" " + key + " \" " );
if (i < 0 ) return "" ;
int q1 = json. indexOf ( '"' , json. indexOf ( ':' , i) + 1 );
int q2 = json. indexOf ( '"' , q1 + 1 );
return q1 < 0 || q2 < 0 ? "" : json. substring (q1 + 1 , q2);
}
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var byUrl = await VerifyJson(new { url = Environment.GetEnvironmentVariable("SLIP_URL") });
var byQr = await VerifyJson(new { qr = Environment.GetEnvironmentVariable("SLIP_QR") });
Console.WriteLine($"{byUrl} {byQr}");
static async Task<string> VerifyJson(object payload)
{
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.slip-bolt.com/api/v1/verify");
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SLIPBOLT_API_KEY"));
req.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
using var res = await http.SendAsync(req);
var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
{
string code = body.RootElement.TryGetProperty("code", out var c) ? c.GetString() ?? "" : "";
if (code == "" && body.RootElement.TryGetProperty("error", out var e)) code = e.GetString() ?? "";
string message = body.RootElement.TryGetProperty("message", out var m) ? m.GetString() ?? "" : "";
throw new Exception($"{(int)res.StatusCode} {code}: {message}");
}
return body.RootElement.GetProperty("data").GetProperty("status").GetString()!;
}
เร็วที่สุด ไม่ต้องเปิดอะไรออกอินเทอร์เน็ต · สคริปต์นี้เซ็นแบบเดียวกับที่ระบบจริงเซ็นทุกประการ
ใช้ secret เดียวกับที่ตั้งไว้ใน handler ของคุณ (ตัวอย่าง handler )
Terminal คัดลอก
SECRET = "whsec_ใส่ของคุณ"
URL = "http://localhost:3000/webhooks/slipbolt"
TS = $( date +%s )
BODY = '{"event":"slip.verified","id":"evt_local_' $TS '","created":' $TS ',"data":{"transactionId":"txn_local_1","transRef":"TEST' $TS '","amount":250,"status":"SUCCESS","bankCode":"004","bankName":"ธนาคารกสิกรไทย","sender":"นาย ทดสอบ ระบบ","receiver":"ร้านของคุณ","verifiedAt":"2026-06-17T03:21:06.412Z","latencyMs":480}}'
SIG = $( printf '%s.%s' " $TS " " $BODY " | openssl dgst -sha256 -hmac " $SECRET " | sed 's/^.*= //' )
curl -i -X POST " $URL " \
-H "Content-Type: application/json" \
-H "X-Slipbolt-Event: slip.verified" \
-H "X-Slipbolt-Signature: t= $TS ,v1= $SIG " \
--data-binary " $BODY "
ใช้เมื่ออยากเห็น event จากสลิปจริง — เปิดอุโมงค์ด้วยเครื่องมือที่ถนัด (cloudflared, ngrok ฯลฯ) เช่น