SDK สำหรับ Python
easyslip-sdk ช่วยให้แอป Python ของคุณตรวจสลิปโอนเงินของธนาคารไทยและสลิป TrueMoney Wallet กับธนาคารได้ เช็กได้ว่าเงินเข้าบัญชีของคุณ ยอดเงินถูกต้อง และไม่ใช่สลิปซ้ำ รวมถึงรับผลการตรวจเบื้องหลังผ่านเว็บฮุคที่มีลายเซ็นยืนยัน มีทั้ง Client แบบ synchronous และ AsyncClient ที่มีเมธอดชุดเดียวกัน ผลลัพธ์เป็น model ที่มี type ซึ่ง mypy ตรวจได้ในโหมด strict และมี dependency ตัวเดียวคือ HTTPX
หน้านี้เป็นคู่มือฉบับเต็ม ส่วน README บน GitHub เป็นฉบับสรุป และดูซอร์สโค้ดได้ที่ EASYSLIP-CO-LTD/sdk-python
สิ่งที่ต้องมี
- Python 3.11, 3.12, 3.13, 3.14 หรือ 3.15
- API key ของ EasySlip เข้าสู่ระบบที่ EasySlip developer portal แล้วคัดลอก API key ของแอปพลิเคชันมาใช้ ดูเรื่อง key และสาขาได้ที่การยืนยันตัวตน
การติดตั้ง
python -m pip install easyslip-sdkถ้าใช้ uv ให้รัน uv add easyslip-sdk ถ้าใช้ Poetry ให้รัน poetry add easyslip-sdk ในโค้ดให้ import ในชื่อ easyslip
เริ่มต้นใช้งาน
โปรแกรมนี้ตรวจสลิปที่ลูกค้าส่งมาจ่ายออเดอร์ยอด 1,500 บาท โดยเช็กว่าเงินเข้าบัญชีรับเงินที่ลงทะเบียนไว้ ยอดเงินถูกต้อง และสลิปไม่ซ้ำ จากนั้นพิมพ์รายละเอียดการโอนออกมา
บันทึกโค้ดเป็นไฟล์ verify.py แล้ววางรูปสลิปไว้ข้างกันในชื่อ slip.jpg
from easyslip import Client
from easyslip.errors import SlipNotFoundError
from easyslip.models import MatchedAccount, VerifyOptions
order_total = 1500.0 # บาท
with Client() as client: # อ่าน API key จาก EASYSLIP_API_KEY
try:
result = client.verify.bank_by_image(
"slip.jpg",
VerifyOptions(
match_account=True, # เงินเข้าบัญชีรับเงินของเราไหม
match_amount=order_total, # ยอดเงินถูกไหม
check_duplicate=True, # เป็นสลิปซ้ำไหม
),
)
except SlipNotFoundError:
print("ไม่พบรายการโอนนี้ที่ธนาคาร สลิปอาจเป็นของปลอมหรือถูกแก้ไข")
else:
slip = result.raw_slip
if result.is_duplicate is not False:
print("สลิปนี้เคยใช้แล้ว อย่ายืนยันออเดอร์ซ้ำ")
elif result.is_amount_matched is not True:
print(f"สลิปนี้โอน {result.amount_in_slip} บาท แต่ออเดอร์ยอด {order_total} บาท")
elif not isinstance(result.matched_account, MatchedAccount):
print("สลิปนี้ไม่ได้โอนเข้าบัญชีรับเงินของเรา")
else:
print("ยืนยันการชำระเงินแล้ว")
print(f"ยอดเงิน: {slip.amount.amount} บาท")
print(f"วันที่: {slip.date}")
print(f"ผู้โอน: {slip.sender.account.name.th}")
print(f"ผู้รับ: {slip.receiver.account.name.th}")
print(f"เลขอ้างอิง: {slip.trans_ref}")รันพร้อม API key ของคุณ
EASYSLIP_API_KEY="your-api-key" python verify.pyถ้าสลิปถูกต้อง จะได้ผลแบบนี้
ยืนยันการชำระเงินแล้ว
ยอดเงิน: 1500.0 บาท
วันที่: 2024-01-15T14:30:00+07:00
ผู้โอน: นาย ผู้โอน ทดสอบ
ผู้รับ: บริษัท ตัวอย่าง จำกัด
เลขอ้างอิง: 68370160657749I376388B35match_account เทียบบัญชีผู้รับกับบัญชีรับเงินที่คุณลงทะเบียนไว้ จึงต้องเพิ่มบัญชีรับเงินก่อน ทำได้ใน developer portal หรือใช้ bank_accounts.create การตรวจสลิปแต่ละครั้งใช้โควต้า 1 ครั้ง
โปรแกรมนี้รับยอดเฉพาะเมื่อแต่ละเงื่อนไขได้ค่าที่หมายถึงผ่านจริง ถ้า API ไม่ได้ส่งผลของเงื่อนไขใดมา ค่านั้นจะเป็น UNSET และโปรแกรมถือว่าไม่ผ่านเช่นกัน
ยอดของออเดอร์ต้องดึงจากฐานข้อมูลของคุณเอง ห้ามรับค่าจากลูกค้า ยืนยันออเดอร์เฉพาะเมื่อผ่านครบทั้งสามเงื่อนไข และเก็บ raw_slip.trans_ref ในคอลัมน์ที่ตั้ง unique constraint ไว้ ภายใน transaction เดียวกับที่บันทึกว่าออเดอร์จ่ายแล้ว เพื่อไม่ให้คำขอสองรายการที่ใช้สลิปใบเดียวกันยืนยันออเดอร์ได้ทั้งคู่
อ่านต่อ
- ตรวจสลิปจากข้อมูลรูปแบบอื่น เช่น ข้อความในคิวอาร์หรือ URL ของรูป
- ดูฟิลด์ทั้งหมดของผลการตรวจ
- จัดการ error และดูว่า SDK ลองใหม่ให้ในกรณีไหนบ้าง
- รับผลการตรวจเบื้องหลังทางเว็บฮุค
สร้าง client
สร้าง client ครั้งเดียวตอนแอปเริ่มทำงาน แล้วใช้ซ้ำกับทุกคำขอ ใช้เป็น context manager หรือเรียก client.close() เมื่อเลิกใช้ เพื่อปิด connection ให้เรียบร้อย ตัวเลือกที่ไม่ได้ใส่จะอ่านจากตัวแปร environment ถ้าไม่มีก็ใช้ค่าเริ่มต้น
| ตัวเลือก | ตัวแปร environment | ค่าเริ่มต้น | ใช้ทำอะไร |
|---|---|---|---|
api_key | EASYSLIP_API_KEY | ไม่มี | API key ของคุณ ถ้าไม่มี key เลย Client() จะ raise InputError |
| ไม่มีตัวเลือก | EASYSLIP_API_KEY_FILE | ไม่มี | path ของไฟล์ที่เก็บ API key เช่น Docker secret หรือ Kubernetes secret ใช้เมื่อไม่มีทั้ง api_key และ EASYSLIP_API_KEY |
timeout | ไม่มีตัวแปร | 30 | เวลาสูงสุดของการส่งคำขอแต่ละครั้ง หน่วยวินาที |
max_retries | ไม่มีตัวแปร | 2 | จำนวนครั้งที่ลองใหม่เมื่อคำขอล้มเหลว ตั้งได้ 0 ถึง 20 ใส่ 0 เพื่อปิด |
http | ไม่มีตัวแปร | httpx.Client ตัวใหม่ | HTTPX client ที่ SDK ใช้ส่งคำขอ ส่วน AsyncClient รับ httpx.AsyncClient |
v2_base_url | EASYSLIP_V2_BASE_URL | https://api.easyslip.com/v2 | ที่อยู่ของ API v2 |
v1_base_url | EASYSLIP_V1_BASE_URL | https://api.easyslip.com/v1 | ที่อยู่ของ API v1 |
from easyslip import Client
with Client(timeout=15, max_retries=1) as client: # ให้เวลาแต่ละครั้ง 15 วินาที
print(client.get_info().application.name)easyslip.EasySlip เป็นอีกชื่อหนึ่งของ Client
ใช้ HTTP client ของคุณเอง
ส่ง httpx.Client ของคุณเข้าไปได้ เช่น เพื่อส่งคำขอผ่าน proxy ใส่ event hook ไว้เก็บ log หรือบันทึกคำขอในเทสต์ SDK จะไม่ปิด client ที่คุณส่งเข้ามา คุณต้องปิดเอง
import httpx
from easyslip import Client
def log_response(response: httpx.Response) -> None:
print(response.request.method, response.request.url, response.status_code)
with httpx.Client(trust_env=False, follow_redirects=False, event_hooks={"response": [log_response]}) as http:
with Client(timeout=15, max_retries=1, http=http) as client:
print(client.get_info().application.name)SDK ใช้ timeout กับการส่งแต่ละครั้ง และไม่ follow redirect เลย แม้จะใช้ client ของคุณเอง
ตรวจสลิปธนาคาร
เลือกเมธอดตามข้อมูลที่ลูกค้าส่งมา ทั้งสี่เมธอดเรียก POST /verify/bank คืนผลการตรวจแบบเดียวกัน และรับตัวเลือกชุดเดียวกัน
| ข้อมูลที่มี | เมธอด | เอกสาร API |
|---|---|---|
| ไฟล์รูป ไฟล์ที่อัปโหลดมา หรือ stream | verify.bank_by_image(image) | อัปโหลดรูป |
| ข้อความในคิวอาร์โค้ดของสลิปที่อ่านด้วยตัวสแกน | verify.bank_by_payload(payload) | Payload |
| URL แบบ https ของรูปสลิปที่เปิดได้จากภายนอก | verify.bank_by_url(url) | URL |
รูปที่เข้ารหัสเป็น base64 จะมี data: นำหน้าหรือไม่ก็ได้ | verify.bank_by_base64(base64) | Base64 |
from easyslip import Client
with Client() as client:
result = client.verify.bank_by_url("https://example.com/slips/1001.jpg")
print(result.raw_slip.trans_ref, result.amount_in_slip)payload คือข้อความที่เข้ารหัสอยู่ในคิวอาร์โค้ดของสลิป ไม่ใช่เลขอ้างอิงที่พิมพ์บนสลิป ถ้าแอปของคุณสแกนคิวอาร์เองได้ bank_by_payload จะเร็วที่สุดเพราะไม่ต้องอัปโหลดอะไร
รูปแบบรูปที่รับได้
bank_by_image และ truewallet_by_image รับข้อมูลได้ดังนี้
- path ของไฟล์ในรูปแบบ
strหรือpathlib.Path - รูปที่เป็น
bytes - file object แบบ binary เช่น จาก
open(path, "rb")
รูปต้องเป็น JPEG, PNG, GIF หรือ WebP ขนาดไม่เกิน 4 MB SDK ตรวจ bytes ของไฟล์ก่อนอัปโหลด และจะ raise InputError ถ้าไฟล์ว่าง ใหญ่เกิน 4 MB หรือไม่ใช่รูป
string คือ path ของไฟล์
str ที่ส่งเข้า bank_by_image จะถูกอ่านเป็น path ของไฟล์บนเซิร์ฟเวอร์เสมอ ไฟล์ที่ลูกค้าอัปโหลดต้องส่งเป็น bytes ห้ามส่งเป็น string ที่ลูกค้ากำหนดค่าได้
route ของ Flask นี้รับสลิปที่อัปโหลดมาทางฟอร์มในช่องชื่อ slip
from flask import Flask, Response, jsonify, request
from easyslip import Client
from easyslip.errors import InvalidImageError
from easyslip.models import VerifyOptions
app = Flask(__name__)
client = Client()
@app.post("/slips")
def upload_slip() -> Response | tuple[str, int]:
upload = request.files.get("slip")
if upload is None:
return 'กรุณาแนบรูปสลิปในช่อง "slip"', 400
try:
result = client.verify.bank_by_image(upload.read(), VerifyOptions(check_duplicate=True))
except InvalidImageError:
return "กรุณาอัปโหลดรูปสลิปที่ชัดเจน", 422
return jsonify(trans_ref=result.raw_slip.trans_ref, amount=result.amount_in_slip)ถ้าใช้ Django ให้ส่ง request.FILES["slip"].read() ถ้าใช้ FastAPI ให้ใช้ AsyncClient แล้วส่ง await upload.read()
เช็กยอดเงิน บัญชีรับเงิน และสลิปซ้ำ
เปิดการตรวจที่ต้องการด้วยฟิลด์ของ VerifyOptions ตัวเลือกแต่ละตัวจะเพิ่มฟิลด์ในผลการตรวจ ถ้าไม่ใส่ก็จะไม่มีฟิลด์นั้น เมื่อเงื่อนไขไม่ผ่าน API ยังคืนข้อมูลสลิปมาให้ตามปกติ โค้ดของคุณเป็นคนตัดสินว่าจะรับยอดหรือไม่
| ตัวเลือก | ตรวจอะไร | ฟิลด์ในผลการตรวจ | เมื่อไม่ผ่าน |
|---|---|---|---|
match_amount=1500.0 | ยอดเงินในสลิปตรงกับยอดที่คาดไว้ หน่วยเป็นบาท | amount_in_order, is_amount_matched | is_amount_matched เป็น False |
match_account=True | ผู้รับเงินเป็นบัญชีรับเงินที่ลงทะเบียนไว้ | matched_account | matched_account เป็น None |
check_duplicate=True | สลิปนี้เคยถูกตรวจมาแล้ว | is_duplicate | is_duplicate เป็น True |
remark="Order 1001" | ไม่ได้ตรวจอะไร แค่บันทึกโน้ตยาว 1 ถึง 255 ตัวอักษรไว้กับการตรวจครั้งนี้ | remark | ไม่มีกรณีไม่ผ่าน |
from easyslip import Client
from easyslip.models import MatchedAccount, VerifyOptions
with Client() as client:
result = client.verify.bank_by_image(
"slip.jpg",
VerifyOptions(remark="Order 1001", match_account=True, match_amount=1500.0, check_duplicate=True),
)
accepted = (
result.is_amount_matched is True
and isinstance(result.matched_account, MatchedAccount)
and result.is_duplicate is False
)
print("รับยอด" if accepted else "ไม่รับยอด", result.remark)ถ้าสาขาเดียวกันเคยตรวจสลิปนี้แล้ว API จะคืนผลเดิมที่บันทึกไว้และไม่ใช้โควต้า ถ้าเป็นสาขาอื่นที่เคยตรวจ API จะตรวจกับธนาคารใหม่และใช้โควต้า ทั้งสองกรณี is_duplicate เป็น True
match_account เทียบเฉพาะบัญชีที่ผูกกับสาขาของ API key ถ้าสลิปที่ควรผ่านกลับได้ matched_account เป็น None ให้ตรวจว่าบัญชีรับเงินนั้นผูกกับสาขานี้แล้ว
UNSET กับ None
ฟิลด์ที่ API ไม่ได้ส่งมาจะมีค่าเป็น easyslip.UNSET ส่วนฟิลด์ที่ API ส่งมาเป็น JSON null จะเป็น None เช่น matched_account จะเป็น UNSET เมื่อไม่ได้ใส่ match_account=True และเป็น None เมื่อใส่แล้วแต่สลิปไม่ได้โอนเข้าบัญชีไหนของคุณเลย ให้เทียบด้วย is True, is False หรือ isinstance แบบตัวอย่างในหน้านี้ เพื่อไม่ให้ค่า UNSET ถูกนับว่าผ่าน
สลิป TrueMoney Wallet
สลิป TrueMoney Wallet ใช้งานแบบเดียวกันและรับตัวเลือกชุดเดียวกัน เมธอดกลุ่มนี้เรียก POST /verify/truewallet และข้อมูลสลิปในผลการตรวจสั้นกว่า
from easyslip import Client
from easyslip.models import VerifyOptions
with Client() as client:
result = client.verify.truewallet_by_image(
"wallet-receipt.png", VerifyOptions(match_amount=500.0, check_duplicate=True)
)
slip = result.raw_slip
print(slip.transaction_id, slip.amount, slip.receiver.name)ใช้ verify.truewallet_by_url เมื่อมี URL ของรูป และ verify.truewallet_by_base64 เมื่อมี base64 สลิป TrueMoney Wallet ไม่มีเมธอดแบบ payload
ข้อมูลบัญชีและโควต้าคงเหลือ
get_info เรียก GET /info แล้วคืนข้อมูลแอปพลิเคชัน สาขา และโควต้า ถ้าแพ็กเกจไม่จำกัดโควต้า quota.remaining จะเป็น None
from easyslip import Client
with Client() as client:
info = client.get_info()
quota = info.application.quota
print(f"{info.application.name}, สาขา {info.branch.name}")
print(f"ใช้ไป {quota.used} คงเหลือ {quota.remaining if quota.remaining is not None else 'ไม่จำกัด'}")บัญชีรับเงิน
match_account เทียบผู้รับเงินในสลิปกับบัญชีกลุ่มนี้ บัญชีรับเงินเป็นของบริการ (service) และแต่ละสาขาจะเทียบเฉพาะบัญชีที่ผูกกับสาขานั้น bank_accounts.create เพิ่มบัญชีแล้วผูกกับสาขาของ API key ให้ทันที ดูรายละเอียดแต่ละ endpoint ได้ที่บัญชีธนาคาร
from easyslip import Client
from easyslip.models import CreateBankAccountParams, ListBankAccountsParams, UpdateBankAccountParams
with Client() as client:
account = client.bank_accounts.create(
CreateBankAccountParams(
bank_code="004", # ดูรหัสธนาคารได้จาก client.banks.list()
bank_number="1234567890",
name_th="บริษัท ตัวอย่าง จำกัด",
name_en="EXAMPLE CO., LTD.",
type="JURISTIC",
)
)
client.bank_accounts.update(account.id, UpdateBankAccountParams(name_en="EXAMPLE COMPANY LIMITED"))
# บัญชีที่ผูกกับสาขานี้ทีละหน้า
page = client.bank_accounts.list(ListBankAccountsParams(page=1, limit=20))
print(f"ผูกกับสาขานี้ {page.total} บัญชี")
# ทุกบัญชีในบริการ SDK ดึงทุกหน้าให้เอง
for item in client.bank_accounts.iterate_all():
print(item.id, item.bank_number, item.name_en)| เมธอด | ใช้ทำอะไร |
|---|---|
bank_accounts.create(params) | เพิ่มบัญชีเข้าบริการและผูกกับสาขานี้ |
bank_accounts.get(id) | ดึงข้อมูลบัญชีเดียว |
bank_accounts.update(id, params) | แก้เฉพาะฟิลด์ที่ส่งมา ส่ง extra_verify=None เพื่อล้างค่านั้น |
bank_accounts.link(id) | ผูกบัญชีของบริการกับสาขานี้ ผูกซ้ำก็ไม่เป็นไร |
bank_accounts.unlink(id) | ยกเลิกการผูกกับสาขานี้ บัญชียังอยู่ในบริการ |
bank_accounts.list(params) | ดึงบัญชีที่ผูกกับสาขานี้หนึ่งหน้า page เริ่มที่ 1 และ limit อยู่ระหว่าง 1 ถึง 100 (ค่าเริ่มต้น 20) |
bank_accounts.list_all(params) | ดึงบัญชีทั้งหมดของบริการหนึ่งหน้า แบ่งหน้าแบบเดียวกัน |
bank_accounts.iterate(params) | ไล่ดูทุกบัญชีที่ผูกกับสาขานี้ ดึงหน้าละ 100 บัญชี |
bank_accounts.iterate_all(params) | ไล่ดูทุกบัญชีของบริการ ดึงหน้าละ 100 บัญชี |
type เป็น "NATURAL" สำหรับบัญชีบุคคลธรรมดา หรือ "JURISTIC" สำหรับบัญชีนิติบุคคล type ของช่องนี้คือ AccountType จาก easyslip ซึ่งรับได้แค่สองค่านี้ mypy จึงไม่รับค่าอื่น เมธอดดึงรายการทั้งสี่ตัวรับ ListBankAccountsParams ที่กรองด้วย type และ bank_code ได้ ถ้าเพิ่มบัญชีที่มีอยู่แล้วจะได้ ConflictError
รหัสธนาคาร
banks.list เรียก GET /banks แล้วคืนรายชื่อธนาคารทั้งหมดที่ API รู้จัก พร้อม code ที่ใช้เป็น bank_code รายการเดียวกันนี้ดูได้ที่รหัสธนาคาร
from easyslip import Client
with Client() as client:
for bank in client.banks.list().items:
print(bank.code, bank.short_code, bank.name_th)ตรวจสลิปจำนวนมากเบื้องหลัง
งานเบื้องหลัง (job) จะตรวจสลิปที่ฝั่ง EasySlip แล้วส่งผลไปที่เว็บฮุคของคุณ เหมาะเมื่อมีสลิปจำนวนมากหรือไม่อยากรอธนาคารตอบ ต้องส่ง callback_url แบบ https ทุกครั้ง เว้นแต่สาขาตั้ง Default Webhook URL ไว้แล้ว ตรวจเบื้องหลังได้เฉพาะสลิปธนาคาร ดูรายละเอียด endpoint ได้ที่ Async Verify
ตัวอย่างนี้อ่านข้อความคิวอาร์ของสลิปหลายใบจาก payloads.txt บรรทัดละใบ แล้วส่งตรวจเป็นชุดเดียว
from pathlib import Path
from easyslip import Client
from easyslip.models import BatchOptions, BatchSlip
payloads = [line for line in Path("payloads.txt").read_text().splitlines() if line]
with Client() as client:
batch = client.jobs.submit_bank_batch(
[BatchSlip(payload=payload, check_duplicate=True) for payload in payloads],
BatchOptions(callback_url="https://example.com/webhooks/easyslip"),
)
# เว็บฮุคจะได้รับผลทุกใบ หรือจะรอผลของแต่ละงานตรงนี้ก็ได้
for submitted in batch.jobs:
job = client.jobs.wait_for_job(submitted.job_id)
print(job.job_id, job.status, job.result)| เมธอด | ใช้ทำอะไร |
|---|---|
jobs.submit_bank(payload, options) | ส่งสลิปหนึ่งใบ AsyncVerifyOptions รับตัวเลือกเหมือน VerifyOptions และเพิ่ม callback_url |
jobs.submit_bank_batch(slips, options) | ส่งสลิป 1 ถึง 100 ใบในคำขอเดียว BatchSlip แต่ละใบตั้งตัวเลือกของตัวเองได้ และ BatchOptions เก็บ callback_url |
jobs.submit_bank_many(slips, options) | ส่งสลิปกี่ใบก็ได้ โดยแบ่งเป็นชุดละ 100 ใบแล้วส่งทีละชุด และคืนรายการของชุดที่ส่ง |
jobs.get(job_id) | ดึงสถานะงาน และผลการตรวจเมื่องานเสร็จ |
jobs.wait_for_job(job_id, interval=2, timeout=600) | เช็กงานทุก interval วินาที จนงานเป็น done หรือ failed รอนานสุด timeout วินาที (600 วินาทีคือ 10 นาที) |
สลิปแต่ละใบในงานเบื้องหลังใช้โควต้าตอนที่ถูกตรวจ เหมือนการตรวจโดยตรง การเช็กงานด้วย jobs.get หรือ wait_for_job ไม่ใช้โควต้าและไม่ส่งสลิปไปตรวจซ้ำ API เก็บงานไว้ประมาณ 7 วัน ถ้าเว็บฮุคพลาดผลไป ก็ยังอ่านผลได้ด้วย jobs.get
งานจะผ่านสถานะ queued และ retrying ไปจนเป็น done หรือ failed สถานะ done แปลว่าธนาคารตอบแล้ว โดย result.status เป็น success พร้อมผลการตรวจใน result.data หรือเป็น not_found เมื่อลองครบแล้วธนาคารก็ยังไม่มีรายการโอนที่ตรงกัน ส่วน failed แปลว่าตรวจไม่สำเร็จ และ error.code บอกสาเหตุ เช่น QUOTA_EXCEEDED
ถ้า submit_bank_many หยุดกลางทาง จะ raise BatchSubmitError ชุดที่อยู่ใน error.submitted ถูกรับไปแล้วและยังทำงานต่อ ให้ส่งใหม่เฉพาะสลิปตั้งแต่ชุด error.failed_chunk เป็นต้นไป
สร้างคิวอาร์รับเงิน
v1.generate_qr สร้างคิวอาร์ PromptPay และคิวอาร์ชำระบิลให้ลูกค้าสแกนจ่ายได้ การสร้างคิวอาร์ไม่ใช้โควต้า amount มีหน่วยเป็นบาท ใส่ได้ตั้งแต่ 0.01 ถึง 9,999,999,999.99 ทศนิยมไม่เกิน 2 ตำแหน่ง ถ้าไม่ใส่ ผู้จ่ายจะกรอกยอดเอง
from pathlib import Path
from easyslip import Client
from easyslip.models import QrRequest
with Client() as client:
# PromptPay เข้าเบอร์โทร ยอด 250 บาท
prompt_pay = client.v1.generate_qr(QrRequest(type="PROMPTPAY", msisdn="0812345678", amount=250.0))
Path("promptpay.png").write_bytes(prompt_pay.image_bytes())
# ชำระบิลเข้า Biller ID ของคุณ ใช้เลขใบแจ้งหนี้เป็น Ref1
bill = client.v1.generate_qr(
QrRequest(type="PROMPTPAY", biller_id="010555512345601", ref1="INV1001", amount=1500.0)
)
Path("bill.png").write_bytes(bill.image_bytes())type | ฟิลด์ที่ต้องใส่ | ฟิลด์ที่ใส่หรือไม่ก็ได้ |
|---|---|---|
"PROMPTPAY" | อย่างใดอย่างหนึ่ง: msisdn (เบอร์โทร), nat_id (เลขบัตรประชาชนหรือเลขผู้เสียภาษี), e_wallet_id หรือ biller_id ถ้าใช้ biller_id ต้องมี ref1 ด้วย | amount และใส่ ref2 ได้เมื่อใช้ biller_id |
"KSHOP" | ref1 คือรหัสร้าน ขึ้นต้นด้วย KB ตามด้วยตัวเลข 12 หลัก | amount |
"MAE_MANEE" | ref1 คือรหัสร้าน 15 หลัก | amount |
"TUNGNGERN" | ref1 คือรหัสร้านค้า และ merchant_name คือชื่อร้านภาษาอังกฤษ | amount |
type "BILL_PAYMENT" แบบเดิมยังใช้ได้ ถ้าใช้ biller_id เดียวกันจะได้คิวอาร์เดียวกับ "PROMPTPAY" ส่วน type QrType จาก easyslip รวมทั้งห้า type ที่ API รับ ผลลัพธ์มี payload ซึ่งเป็นข้อความในคิวอาร์ด้วย ถ้าต้องการวาดคิวอาร์เอง
API v1 แบบเดิม
ใช้ client.v1 เฉพาะระบบที่เชื่อมกับ API v1 อยู่แล้ว โค้ดใหม่ควรใช้เมธอด v2 ด้านบน เมธอดของ v1 คืนข้อมูลสลิปโดยตรง และเมื่อใส่ check_duplicate=True แล้วเจอสลิปซ้ำจะ raise DuplicateSlipError
from easyslip import Client
from easyslip.errors import DuplicateSlipError
from easyslip.models import V1VerifyOptions
with Client() as client:
try:
slip = client.v1.verify_bank_by_image("slip.jpg", V1VerifyOptions(check_duplicate=True))
print(slip.trans_ref, slip.amount.amount)
except DuplicateSlipError as error:
print("สลิปนี้เคยใช้แล้ว วันที่โอน:", error.slip.date if error.slip else None)API v1 ยังมี verify_bank_by_payload, verify_bank_by_url, verify_bank_by_base64, verify_truemoney_by_image และ get_me ดูสิ่งที่เปลี่ยนใน v2 ได้ที่เปรียบเทียบเวอร์ชัน
โค้ดแบบ async
AsyncClient มี resource และเมธอดเหมือน Client ทุกอย่าง แค่ต้อง await ทุกครั้งที่เรียก ใช้กับแอป asyncio เช่น FastAPI และปิดด้วย async with หรือ await client.close() อย่ายิงคำขอพร้อมกันมากเกินไป เพราะ API จำกัดจำนวนคำขอต่อช่วงเวลา ตัวอย่างนี้ตรวจสลิปพร้อมกันได้ไม่เกินสามใบ
import asyncio
from easyslip import AsyncClient
from easyslip.models import BankSlipResult, VerifyOptions
async def main() -> None:
files = ["slip-1001.jpg", "slip-1002.jpg", "slip-1003.jpg"]
limit = asyncio.Semaphore(3)
async with AsyncClient() as client:
async def verify(path: str) -> BankSlipResult:
async with limit:
return await client.verify.bank_by_image(path, VerifyOptions(check_duplicate=True))
outcomes = await asyncio.gather(*(verify(path) for path in files), return_exceptions=True)
for path, outcome in zip(files, outcomes):
if isinstance(outcome, BaseException):
print(path, "ไม่สำเร็จ:", outcome)
else:
print(path, outcome.raw_slip.trans_ref)
asyncio.run(main())ถ้ามีสลิปมากกว่าไม่กี่ใบ ให้ส่งเป็นงานเบื้องหลังแทน
ผลการตรวจ
การตรวจแบบ v2 คืนผลของเงื่อนไขที่คุณขอไว้ที่ชั้นบนสุด และข้อมูลสลิปตามที่ธนาคารส่งมาไว้ใน raw_slip ดูโครงสร้างเต็มได้ที่เอกสาร API
สลิปธนาคาร
verify.bank_by_* คืน BankSlipResult
| ฟิลด์ | ชนิด | ความหมาย | ตัวอย่าง |
|---|---|---|---|
amount_in_slip | float | ยอดเงินที่โอน หน่วยเป็นบาท | 1500.0 |
is_amount_matched | bool | ยอดเงินตรงกับ match_amount หรือไม่ มีเฉพาะเมื่อใส่ match_amount ถ้าไม่ใส่จะเป็น UNSET | True |
amount_in_order | float | ค่า match_amount ที่ส่งไป มีเฉพาะเมื่อใส่ match_amount | 1500.0 |
matched_account | MatchedAccount หรือ None | บัญชีรับเงินของคุณที่สลิปโอนเข้า หรือ None มีเฉพาะเมื่อใส่ match_account | bank_number เป็น "123-4-56789-0" |
is_duplicate | bool | สลิปนี้เคยถูกตรวจแล้วหรือไม่ มีเฉพาะเมื่อใส่ check_duplicate | False |
remark | str | ค่า remark ที่ส่งไป มีเฉพาะเมื่อส่ง | "Order 1001" |
raw_slip.trans_ref | str | เลขอ้างอิงรายการของธนาคาร แต่ละรายการโอนไม่ซ้ำกัน เก็บไว้เพื่อกันสลิปซ้ำได้ | "68370160657749I376388B35" |
raw_slip.date | str | เวลาที่โอน รูปแบบ ISO 8601 พร้อมเขตเวลาไทย | "2024-01-15T14:30:00+07:00" |
raw_slip.amount.amount | float | ยอดเงิน หน่วยเป็นบาท | 1500.0 |
raw_slip.fee | float | ค่าธรรมเนียม เมื่อธนาคารแจ้งมา | 0.0 |
raw_slip.sender.bank | SlipBank | ธนาคารผู้โอน: id (รหัสธนาคาร), name และ short | "004", "กสิกรไทย", "KBANK" |
raw_slip.sender.account.name | AccountName | ชื่อผู้โอนภาษาไทย (th) และอังกฤษ (en) เมื่อธนาคารแสดง | "นาย ผู้โอน ทดสอบ", "MR. SENDER TEST" |
raw_slip.sender.account.bank | AccountNumber | เลขบัญชีผู้โอน (account) และ type ธนาคารปิดบางหลักไว้ | "BANKAC", "123-4-xxxxx-5" |
raw_slip.receiver.bank | SlipReceiverBank | ธนาคารผู้รับ รูปแบบเดียวกับผู้โอน | "014", "ไทยพาณิชย์", "SCB" |
raw_slip.receiver.account.name | SlipReceiverAccountName | ชื่อผู้รับ | "บริษัท ตัวอย่าง จำกัด" |
raw_slip.receiver.account.bank | AccountNumber | เลขบัญชีผู้รับ ปิดบางหลักไว้ | "BANKAC", "xxx-x-x5678-x" |
raw_slip.receiver.account.proxy | AccountNumber | PromptPay ที่ใช้รับเงิน เมื่อโอนผ่าน PromptPay | "MSISDN", "08xxxxxxxx89" |
raw_slip.ref1, ref2, ref3 | str | เลขอ้างอิงของการชำระบิล เป็นค่าว่างสำหรับการโอนทั่วไป | "" |
ฟิลด์ที่ธนาคารไม่ได้ส่งมาทุกครั้ง เช่น fee ธนาคารของแต่ละฝ่าย ชื่อ และเลขบัญชี จะเป็น UNSET เมื่อไม่มีข้อมูล ให้เช็กด้วย isinstance ก่อนอ่าน attribute ข้างใน
สลิป TrueMoney Wallet
verify.truewallet_by_* คืน TrueWalletResult ฟิลด์ชั้นบนสุดเหมือนสลิปธนาคาร ส่วน raw_slip มีข้อมูลดังนี้
| ฟิลด์ | ชนิด | ความหมาย | ตัวอย่าง |
|---|---|---|---|
raw_slip.transaction_id | str | เลขรายการของ TrueMoney | "50012345678901" |
raw_slip.date | str | เวลาที่ชำระ รูปแบบ ISO 8601 | "2026-10-26T14:30:00+07:00" |
raw_slip.amount | float | ยอดเงิน หน่วยเป็นบาท | 500.0 |
raw_slip.sender.name | str | ชื่อผู้โอน | "นาย ผู้โอน ทดสอบ" |
raw_slip.receiver.name | str | ชื่อผู้รับ | "นาย รับเงิน ทดสอบ" |
raw_slip.receiver.phone | str | เบอร์โทรผู้รับ ปิดบางหลักไว้ | "08x-xxx-4567" |
งานเบื้องหลังและ event ของเว็บฮุค
jobs.get และ wait_for_job คืน Job ส่วนเว็บฮุคได้รับ WebhookEvent
| ฟิลด์ | Job | WebhookEvent | ความหมาย |
|---|---|---|---|
job_id | มี | มี | ID ของงานที่ submit_bank หรือ submit_bank_batch คืนมา |
batch_id | มี | มี | ID ของชุด เมื่อสลิปถูกส่งมาเป็นชุด |
status | queued, processing, retrying, done หรือ failed | success, not_found หรือ failed | งานอยู่ขั้นไหน หรือจบด้วยผลอะไร |
result.status | มี | ไม่มี | success หรือ not_found เมื่องานเป็น done แล้ว |
result.data, data | มี | มี | BankSlipResult แบบเดียวกับการเรียกตรง เมื่อตรวจสลิปได้ |
error.code, error.message | มี | มี | สาเหตุที่ตรวจไม่สำเร็จ เมื่อสถานะเป็น failed |
timestamp | ไม่มี | มี | เวลาที่ได้ผล |
ข้อมูลประกอบของ response
ผลการตรวจทุกตัวมี attribute meta ที่เก็บ HTTP status, request_id, ตัวนับ rate_limit และ headers ของ response แนบ meta.request_id มาด้วยเมื่อติดต่อซัพพอร์ตเรื่องการเรียกครั้งนั้น
การจัดการ error
exception ทุกตัวที่ SDK raise เป็น easyslip.EasySlipError คลาสทั้งหมดอยู่ใน easyslip.errors ส่วน exception ที่มาจาก API เป็น ApiError ซึ่งมี status, code (เช่น SLIP_NOT_FOUND), message, request_id และ retry_after บางคลาสเป็นคลาสย่อยของอีกคลาส ได้แก่ QuotaExceededError เป็น PermissionDeniedError ส่วน InvalidImageError, InvalidUrlError และ DuplicateSlipError เป็น InvalidRequestError ส่วน SlipNotFoundError และ SlipPendingError เป็น NotFoundError ส่วน ServiceUnavailableError เป็น ServerError และ RequestTimeoutError เป็น NetworkError นอกจากนี้ InputError ยังเป็น ValueError ด้วย ดูความหมายของ code ทุกตัวที่ API คืนได้ที่รหัส Error
ถ้า SDK รู้จัก code ของ error จะเลือกคลาสจาก code ก่อน ถ้าไม่รู้จักจะเลือกจาก HTTP status ในตาราง และ 4xx อื่นจะได้ ApiError
| คลาส exception | HTTP status | เกิดเมื่อ | ควรทำอย่างไร |
|---|---|---|---|
AuthenticationError | 401 | ไม่มี API key, key ผิด หรือ key ถูกยกเลิกแล้ว | ตรวจ key ที่ส่งเข้าไปหรือ EASYSLIP_API_KEY ถ้าจำเป็นให้สร้าง key ใหม่ใน developer portal |
PermissionDeniedError | 403 | key ถูกต้องแต่ไม่มีสิทธิ์ทำคำขอนี้ เช่น สาขาถูกปิด แพ็กเกจหมดอายุ หรือ IP ของเซิร์ฟเวอร์ไม่อยู่ในรายการที่อนุญาต | ดู error.code แล้วแก้การตั้งค่านั้นใน developer portal |
QuotaExceededError | 403 | โควต้าหมด คำขอนี้ยังไม่ถูกหักโควต้า | เติมโควต้าหรือเปลี่ยนแพ็กเกจ แล้วส่งคำขอใหม่ |
InvalidRequestError | 400 | API ไม่รับค่าบางอย่างในคำขอ เช่น ข้อความคิวอาร์หรือค่าตัวเลือก | แก้ค่าที่ error.message บอก ส่งคำขอเดิมซ้ำจะได้ error เดิม |
InvalidImageError | 400 | อ่านคิวอาร์ของสลิปในรูปไม่ได้ ชนิดไฟล์ไม่รองรับ หรือไฟล์ใหญ่เกินไป | ขอให้ลูกค้าส่งรูป JPEG, PNG, GIF หรือ WebP ที่ชัดและไม่เกิน 4 MB |
InvalidUrlError | 400 | URL ของรูปหรือ callback_url ชี้ไปที่ที่อยู่ภายใน ไม่ได้ใช้ http หรือ https หรือเปิดไม่ได้ | ใช้ URL แบบ https ที่เปิดได้จากภายนอก |
DuplicateSlipError | 400 | เฉพาะ API v1 ที่ใส่ check_duplicate=True: สลิปนี้เคยถูกตรวจแล้ว error.slip คือผลการตรวจครั้งแรก | ถือว่าได้รับยอดนี้ไปแล้ว อย่ายืนยันซ้ำ |
NotFoundError | 404 | ไม่พบบัญชีธนาคาร งาน หรือรายการที่ขอ | ตรวจ ID ที่ส่งเข้าไป |
SlipNotFoundError | 404 | ธนาคารไม่มีรายการโอนที่ตรงกับสลิปนี้ | อย่ารับยอด และขอสลิปตัวจริงจากลูกค้า |
SlipPendingError | 404 | ธนาคารยังไม่เผยแพร่รายการโอนนี้ มักเกิดกับสลิปธนาคารกรุงเทพที่เพิ่งโอน | รอประมาณหนึ่งนาทีแล้วตรวจใหม่ |
ConflictError | 409 | บัญชีรับเงินที่กำลังเพิ่มมีอยู่แล้ว | ใช้บัญชีเดิม หาได้ด้วย bank_accounts.iterate_all() |
RateLimitError | 429 | ส่งคำขอถี่เกินไป error.retry_after บอกว่าต้องรอกี่วินาที | ลดจำนวนคำขอที่ส่งพร้อมกัน SDK ลองใหม่ให้แล้วก่อน raise exception นี้ |
ServerError | 500, 502, 504; 5xx อื่นยกเว้น 503 | เกิดปัญหาที่ฝั่ง EasySlip | ลองใหม่ภายหลัง ถ้ายังเกิดซ้ำให้ติดต่อซัพพอร์ตพร้อม error.request_id |
ServiceUnavailableError | 503 | บริการไม่พร้อมใช้งานชั่วคราว เช่น ช่วงต่ออายุโควต้า | ลองใหม่ในอีกไม่กี่นาที |
ApiError | 4xx อื่น ๆ | API ตอบ error ด้วย status ที่ไม่มีคลาสเฉพาะ | เก็บ log status, code และ request_id ถ้ายังเกิดอยู่ให้ติดต่อซัพพอร์ต |
NetworkError | เครือข่าย | ไม่ได้รับคำตอบเพราะ DNS การเชื่อมต่อ หรือ TLS ล้มเหลว | ตรวจเครือข่ายหรือ proxy แล้วลองใหม่ |
RequestTimeoutError | เครือข่าย | ไม่ได้รับคำตอบภายใน timeout | ลองใหม่หรือเพิ่ม timeout การตรวจสลิปครั้งนั้นอาจถูกนับโควต้าไปแล้ว |
InputError | ก่อนส่งคำขอ | SDK เจอปัญหาก่อนส่ง เช่น ไม่มี API key ข้อความคิวอาร์ว่าง รูปเกิน 4 MB หรือไฟล์ไม่ใช่รูป error.code บอกสาเหตุ | แก้ค่าที่ส่งเข้าไป |
WebhookSignatureError | ก่อนส่งคำขอ | เว็บฮุคไม่มีลายเซ็น หรือลายเซ็นไม่ตรง | ตอบเว็บฮุคนั้นด้วย status 400 |
JobWaitTimeoutError | ก่อนส่งคำขอ | wait_for_job รอจนครบ timeout แล้ว error.last_job คือสถานะล่าสุดที่เห็น | รอนานขึ้น หรือรอผลทางเว็บฮุคแทน |
BatchSubmitError | ก่อนส่งคำขอ | submit_bank_many หยุดกลางทาง error.submitted คือชุดที่ส่งไปแล้ว error.failed_chunk คือลำดับของชุดที่ล้มเหลว นับจาก 0 และ error.cause คือ error ของชุดนั้น | ส่งชุดที่ล้มเหลวและชุดถัดไปใหม่ ชุดที่ส่งไปแล้วยังทำงานต่อ |
ตัวอย่างนี้ตรวจสลิปที่ลูกค้าอัปโหลดตอนชำระเงิน แล้วแปลง error แต่ละแบบเป็นข้อความที่แสดงให้ลูกค้าเห็น
import logging
from easyslip import Client
from easyslip.errors import (
ApiError,
InvalidImageError,
QuotaExceededError,
RateLimitError,
SlipNotFoundError,
SlipPendingError,
)
from easyslip.models import MatchedAccount, VerifyOptions
logger = logging.getLogger(__name__)
def check_payment(client: Client, upload: bytes, order_total: float) -> str:
try:
result = client.verify.bank_by_image(
upload, VerifyOptions(match_amount=order_total, match_account=True, check_duplicate=True)
)
except SlipNotFoundError:
return "ไม่พบรายการโอนนี้ที่ธนาคาร"
except SlipPendingError:
return "ธนาคารกำลังดำเนินการรายการนี้ ลองใหม่ในอีกหนึ่งนาที"
except InvalidImageError:
return "กรุณาอัปโหลดรูปถ่ายหรือภาพหน้าจอสลิปที่ชัดเจน"
except (QuotaExceededError, RateLimitError) as error:
logger.error("ใช้ EasySlip ถึงขีดจำกัดแล้ว: %s %s", error.code, error.request_id)
return "ตอนนี้ยังตรวจสลิปไม่ได้ กรุณาลองใหม่อีกครั้ง"
except ApiError as error:
logger.error("EasySlip ตอบ error: %s %s %s", error.status, error.code, error.request_id)
return "ตรวจสลิปไม่สำเร็จ กรุณาลองใหม่"
if result.is_duplicate is not False:
return "สลิปนี้ถูกใช้ไปแล้ว"
if result.is_amount_matched is not True:
return f"สลิปนี้โอน {result.amount_in_slip} บาท"
if not isinstance(result.matched_account, MatchedAccount):
return "สลิปนี้ไม่ได้โอนเข้าบัญชีของเรา"
return "ได้รับการชำระเงินแล้ว"
with Client() as client, open("slip.jpg", "rb") as file:
print(check_payment(client, file.read(), 1500.0))SDK ลองใหม่ให้เมื่อไร
SDK ลองส่งคำขอที่ล้มเหลวใหม่ได้สูงสุด max_retries ครั้ง (ค่าเริ่มต้น 2 ครั้ง) ก่อนลองใหม่ครั้งแรกจะรอประมาณครึ่งวินาที แล้วเพิ่มเวลารอเป็นสองเท่าทุกครั้ง ไม่เกิน 8 วินาที ถ้า API ส่ง Retry-After มา SDK จะรอตามนั้นแทน แต่ถ้าต้องรอนานกว่า 60 วินาทีจะ raise exception ทันที
| คำขอ | ลองใหม่เมื่อ | ไม่ลองใหม่เมื่อ |
|---|---|---|
การอ่านข้อมูลและการผูกบัญชี: get_info, banks.list, bank_accounts.list, list_all, get และ link, jobs.get, v1.get_me, v1.generate_qr | เครือข่ายล้มเหลว หมดเวลา หรือ API ตอบ 429, 500, 502, 503 หรือ 504 | API ตอบ 400, 401, 403, 404 หรือ 409 |
| การตรวจสลิปและการส่งงานเบื้องหลัง ซึ่งใช้โควต้า | API ตอบ 429 หรือเชื่อมต่อไม่สำเร็จก่อนคำขอจะออกจากเซิร์ฟเวอร์ | คำขอส่งออกไปแล้ว แล้วหมดเวลาหรือได้คำตอบ 5xx |
การแก้ไขบัญชีรับเงิน: create, update และ unlink | API ตอบ 429 หรือเชื่อมต่อไม่สำเร็จก่อนคำขอจะออกจากเซิร์ฟเวอร์ | คำขอส่งออกไปแล้ว แล้วหมดเวลาหรือได้คำตอบ 5xx |
การตรวจสลิปที่ส่งถึง API แล้วอาจถูกนับโควต้าไปแล้ว แม้จะไม่ได้รับคำตอบกลับมา การลองใหม่อาจทำให้ถูกหักโควต้าซ้ำ SDK จึงให้คุณเป็นคนตัดสินใจเอง ถ้าจะส่งใหม่ ให้ใส่ check_duplicate=True ไว้ด้วย ถ้าครั้งแรกถูกนับโควต้าไปแล้ว ครั้งที่สองจะได้ผลเดิมที่บันทึกไว้พร้อม is_duplicate เป็น True และไม่ใช้โควต้า
เว็บฮุค
เมื่องานเบื้องหลังเสร็จ EasySlip จะส่งคำขอ POST ไปที่ callback_url ของคุณ ถ้าสาขาตั้ง Webhook Secret ไว้ คำขอจะมี header X-EasySlip-Signature ให้ตรวจลายเซ็นกับ body ดิบก่อนแปลงหรือเชื่อข้อมูลใด ๆ ในนั้น ดูรายละเอียดของคำขอทั้งหมดได้ที่ Webhook Callback
import os
from flask import Flask, Response, request
from easyslip.errors import InputError, WebhookSignatureError
from easyslip.webhooks import SIGNATURE_HEADER, construct_event
app = Flask(__name__)
webhook_secret = os.environ["EASYSLIP_WEBHOOK_SECRET"]
@app.post("/webhooks/easyslip")
def receive_webhook() -> Response:
raw_body = request.get_data(cache=False) # body ตามที่ได้รับมาทุก byte
try:
event = construct_event(raw_body, request.headers.get(SIGNATURE_HEADER), webhook_secret)
except (WebhookSignatureError, InputError):
return Response("invalid webhook", status=400)
if event.status == "success" and event.data is not None:
app.logger.info("ตรวจผ่าน %s %s %s", event.job_id, event.data.raw_slip.trans_ref, event.data.amount_in_slip)
else:
app.logger.info("ตรวจไม่ผ่าน %s %s", event.job_id, event.status)
return Response(status=204)import logging
import os
from fastapi import FastAPI, Request, Response
from easyslip.errors import InputError, WebhookSignatureError
from easyslip.webhooks import SIGNATURE_HEADER, construct_event
app = FastAPI()
logger = logging.getLogger(__name__)
webhook_secret = os.environ["EASYSLIP_WEBHOOK_SECRET"]
@app.post("/webhooks/easyslip")
async def receive_webhook(request: Request) -> Response:
raw_body = await request.body() # body ตามที่ได้รับมาทุก byte
try:
event = construct_event(raw_body, request.headers.get(SIGNATURE_HEADER), webhook_secret)
except (WebhookSignatureError, InputError):
return Response("invalid webhook", status_code=400)
if event.status == "success" and event.data is not None:
logger.info("ตรวจผ่าน %s %s %s", event.job_id, event.data.raw_slip.trans_ref, event.data.amount_in_slip)
else:
logger.info("ตรวจไม่ผ่าน %s %s", event.job_id, event.status)
return Response(status_code=204)ทั้งสองแบบตอบ 400 เมื่อไม่มีลายเซ็นหรือลายเซ็นไม่ตรง และเมื่อ body ที่ลงลายเซ็นมาไม่ใช่ event ของเว็บฮุค ถ้าใช้ Django ให้ส่ง request.body และ request.headers.get(SIGNATURE_HEADER) ใน view ที่ใส่ @csrf_exempt ไว้ อย่าแปลง JSON แล้วแปลงกลับเป็นข้อความก่อนตรวจ เพราะ bytes จะเปลี่ยน แล้วลายเซ็นจะไม่ตรง
ฟังก์ชันใน easyslip.webhooks | ใช้ทำอะไร |
|---|---|
construct_event(raw_body, signature, secret) | ตรวจลายเซ็นแล้วคืน WebhookEvent ที่แปลงแล้ว raise WebhookSignatureError เมื่อไม่มีลายเซ็นหรือลายเซ็นไม่ตรง และ raise InputError เมื่อ body ไม่ใช่ event ของเว็บฮุคในรูป JSON |
verify_signature(raw_body, signature, secret) | คืน True เมื่อลายเซ็นตรง และ False เมื่อไม่ตรง |
parse_event(raw_body) | แปลง event โดยไม่ตรวจลายเซ็น และ raise InputError เมื่อ body ไม่ใช่ event ใช้หลังจากตรวจลายเซ็นเองแล้วเท่านั้น |
secret คือ Webhook Secret ของสาขา ต้องส่งทุกครั้งที่เรียก ตั้งค่าได้ใน developer portal ถ้าไม่ได้ตั้งไว้ EasySlip จะส่ง callback มาโดยไม่มีลายเซ็น และ construct_event จะไม่รับ จึงควรตั้งให้เรียบร้อยก่อนเปิดใช้งานจริง
กติกาการส่งเว็บฮุค
- ตอบกลับด้วย status 2xx ภายใน 5 วินาที บันทึก event ไว้ก่อนแล้วค่อยทำงานที่ใช้เวลานานทีหลัง เช่น ใน task queue
- ถ้าตอบด้วย status อื่นที่ไม่ใช่ 2xx หรือตอบไม่ทัน EasySlip จะส่ง event เดิมอีกครั้งหลังผ่านไปประมาณ 10, 30 และ 120 วินาที
- EasySlip อาจส่ง event เดิมมาซ้ำได้ ให้ใช้
job_idข้าม event ที่จัดการไปแล้ว - ถ้าส่งไม่สำเร็จทุกครั้ง
jobs.get(job_id)ยังคืนผลได้ประมาณ 7 วัน
ความปลอดภัยและข้อมูลส่วนบุคคล
- เก็บ API key ไว้ฝั่งเซิร์ฟเวอร์เท่านั้น อย่าใส่ในหน้าเว็บ แอปมือถือ หรือ repository สาธารณะ เพราะใครได้ key ไปก็ใช้โควต้าของคุณได้
- อ่าน key จาก
EASYSLIP_API_KEYหรือไฟล์ secret แทนการเขียนลงในโค้ด - SDK ส่ง key ไปใน header
Authorizationเท่านั้น และลบ key ออกจากข้อความของ exception และ header ที่ส่งคืนให้คุณ จึงเก็บ log ของ error ได้อย่างปลอดภัย ส่วนerror.sensitive_raw_body()คืน body ของ error ฉบับเดิม อย่าเก็บค่านี้ลง log - สลิปมีชื่อ เลขบัญชี และยอดเงิน ซึ่งเป็นข้อมูลส่วนบุคคล เก็บเฉพาะที่จำเป็น ดูแลเหมือนข้อมูลลูกค้าอื่น ๆ และปฏิบัติตาม พ.ร.บ. คุ้มครองข้อมูลส่วนบุคคล (PDPA)
- ตรวจลายเซ็นของเว็บฮุคทุกครั้งก่อนนำข้อมูลไปใช้
เวอร์ชันและนโยบายการรองรับ
- การออกเวอร์ชันใช้ semantic versioning เวอร์ชัน minor และ patch จะไม่ทำให้โค้ดเดิมพัง การเปลี่ยนแปลงที่กระทบโค้ดเดิมจะออกเป็นเวอร์ชัน major ใหม่ พร้อมวิธีอัปเกรดใน changelog
- SDK รองรับ Python เวอร์ชันที่โปรเจกต์ Python ยังดูแลอยู่ เมื่อ Python เวอร์ชันใดหมดอายุการดูแล SDK เวอร์ชันถัดไปอาจเลิกรองรับเวอร์ชันนั้น และจะแจ้งไว้ใน changelog
- changelog และหน้า releases รวบรวมทุกเวอร์ชันและสิ่งที่เปลี่ยน
อ่านเพิ่มเติม
- โฟลเดอร์
examplesมีโปรแกรมที่รันได้จริงครบทุกเมธอด - ภาพรวม API v2 และรหัส Error อธิบายทุก endpoint ฟิลด์ และรหัสข้อผิดพลาด
- คำถามเรื่องบัญชี โควต้า หรือการเรียกเก็บเงิน ติดต่อ EasySlip ได้ทาง LINE @ai-easy โทร 02-114-8806 หรือหน้าติดต่อเรา โดยแนบ
request_idของการเรียกครั้งนั้นมาด้วย - แจ้งบั๊กหรือขอฟีเจอร์ได้ที่ issue ห้ามโพสต์ API key หรือสลิปจริง ส่วนปัญหาด้านความปลอดภัยให้แจ้งแบบส่วนตัวตามที่ SECURITY.md อธิบายไว้