ให้แอปอื่นออกใบแจ้งหนี้ & ใบเสร็จเข้า ERP
External API ให้ระบบภายนอก (เช่น WideStay, POS, เว็บจอง) สั่งออก ใบกำกับภาษี/ใบแจ้งหนี้ และ ใบเสร็จรับเงิน เข้าบริษัทใน ERP โดยตรง — ลงบัญชีคู่ (double-entry) และรันเลขที่เอกสารให้อัตโนมัติ เหมือนออกจากหน้าเว็บทุกประการ
https://erp.oneable.co.th/api/ext/v1
คีย์ต่อบริษัท (per-company)
แต่ละบริษัทมีคู่คีย์ของตัวเอง เจ้าของบริษัท (OWNER) สร้าง/เพิกถอนได้ที่ ตั้งค่า → การเชื่อมต่อ (Integrations) หรือผ่าน API จัดการคีย์
wsk_live_a1b2c3…ระบุว่าเป็นบริษัทไหน — ใส่ใน header ได้ปลอดภัย
whsec_…โชว์ ครั้งเดียวตอนสร้าง ใช้เซ็น HMAC — เก็บฝั่งคุณเท่านั้น
companyId ผูกกับคีย์เสมอ — payload ไม่ต้องระบุบริษัท และข้ามบริษัทกันไม่ได้
การยืนยันตัวตน (HMAC-SHA256)
ทุก request เซ็นด้วย secret บน raw body + timestamp กัน tamper และ replay ทุก request ต้องมี 3 header นี้:
| Header | ค่า |
|---|---|
| X-Api-Key | keyId สาธารณะของบริษัท (wsk_live_…) |
| X-Timestamp | Unix time (วินาที) ตอนยิง — ต้องอยู่ใน ±300 วินาที |
| X-Signature | hex ของ HMAC_SHA256(secret, "{timestamp}.{rawBody}") |
| Idempotency-Key | แนะนำ เช่น payment intent id — กันสร้างซ้ำ |
สูตรลายเซ็น — string ที่นำมาเซ็นคือ timestamp + "." + rawBody:
// Node.js — เซ็น request const crypto = require('crypto'); const body = JSON.stringify(payload); // raw body ที่จะส่ง (ต้องตรงเป๊ะ) const ts = Math.floor(Date.now() / 1000); const sig = crypto.createHmac('sha256', secret) .update(`${ts}.${body}`).digest('hex'); await fetch('https://erp.oneable.co.th/api/ext/v1/receipts', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': keyId, 'X-Timestamp': String(ts), 'X-Signature': sig, 'Idempotency-Key': paymentIntentId, }, body, // ← ส่ง string เดิมที่เซ็น ห้าม re-stringify });
Idempotency & การยิงซ้ำ
ส่ง Idempotency-Key เดียวกัน (แนะนำใช้ payment intent id) ระบบจะสร้างเอกสารครั้งเดียว การยิงซ้ำคืน ผลเดิม พร้อม "replayed": true (HTTP 200) — ปลอดภัยที่จะ retry เมื่อ network ล้ม
/api/ext/v1/invoicesออกใบกำกับภาษี / ใบแจ้งหนี้
สร้างและ issue ใบกำกับภาษีทันที (ลง GL: เดบิตลูกหนี้ / เครดิตรายได้+ภาษีขาย) ลูกค้าจับคู่จากเลขผู้เสียภาษี ถ้าไม่มีจะสร้างให้ · ต้องมี scope invoice:create
Request body
| field | ชนิด | รายละเอียด |
|---|---|---|
| customer.name req | string | ชื่อลูกค้า/ผู้เช่า |
| customer.taxId | string | เลขผู้เสียภาษี 13 หลัก (ใช้จับคู่กันซ้ำ) |
| customer.address | string | ที่อยู่บนใบกำกับ |
| lines[] req | array | รายการ: description, qty, unitPrice, discount, isService |
| vatMode | string | exclude (ราค ยังไม่รวม VAT · ค่าเริ่มต้น) · include · none |
| whtRate | number | % หัก ณ ที่จ่าย ที่คาดไว้ (ไม่บังคับ) |
| date | ISO date | วันที่เอกสาร (ดีฟอลต์ = วันนี้) |
| externalRef | string | เลขอ้างอิงฝั่งคุณ (เก็บใน refNumber) |
Response 201
{
"id": "cly…",
"taxInvoiceNo": "IV69070001", // เลขใบกำกับภาษี
"subTotal": 1000,
"vatAmount": 70,
"total": 1070,
"status": "ISSUED",
"replayed": false
}
/api/ext/v1/receiptsออกใบเสร็จรับเงิน (ออกใบกำกับ + รับชำระในก้อนเดียว)
เหมาะกับ event "จ่ายเงินสำเร็จ" — สร้างใบกำกับ (ถ้ายังไม่มี) แล้วรับชำระ ออก ใบเสร็จ (RC) พร้อมลง GL รับเงิน ทั้งหมด atomic · ต้องมี scope receipt:create
Request body
รับ field ชุดเดียวกับ /invoices (customer, lines, vatMode…) บวก field รับชำระ:
| field | ชนิด | รายละเอียด |
|---|---|---|
| invoiceId | string | ออกใบเสร็จให้ใบกำกับที่มีอยู่แล้ว (ข้ามการสร้างใหม่) |
| paidAmount | number | ยอดชำระ (ดีฟอลต์ = ยอดเต็มของใบ) |
| method | string | TRANSFER (ดีฟอลต์) · CASH · CHEQUE |
| wht | number | ยอดหัก ณ ที่จ่าย (บาท) |
| reference | string | เลขอ้างอิงการโอน/charge |
Response 201
{
"taxInvoiceNo": "IV69070001", // ใบกำกับภาษี
"receiptNo": "RC69070001", // ใบเสร็จรับเงิน
"invoiceId": "cly…",
"total": 1070,
"paid": 1070,
"replayed": false
}
รหัสข้อผิดพลาด
| HTTP | error | สาเหตุ |
|---|---|---|
| 401 | missing_auth_headers | ขาด X-Api-Key / X-Timestamp / X-Signature |
| 401 | invalid_key | ไม่พบคีย์ หรือถูกปิดใช้งาน |
| 401 | bad_signature | ลายเซ็นไม่ตรง (secret ผิด/บอดี้ถูกแก้) |
| 401 | stale_timestamp | timestamp เกิน ±300 วินาที |
| 403 | insufficient_scope | คีย์ไม่มีสิทธิ์ทำรายการนี้ |
| 400 | missing_customer_name / missing_lines / invalid_amount | ข้อมูลไม่ครบ/ไม่ถูกต้อง |
| 409 | period_closed | งวดบัญชีปิดแล้ว ออกเอกสารวันนั้นไม่ได้ |
ตัวอย่างเต็ม (curl)
# 1) เตรียม body + ลายเซ็น BODY='{"source":"WideStay","customer":{"name":"บจก. ผู้เช่า","taxId":"0105551234567"},"vatMode":"exclude","lines":[{"description":"ค่าบริการทำความสะอาด","qty":1,"unitPrice":1000,"isService":true}]}' TS=$(date +%s) SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}') # 2) ยิง curl -X POST https://erp.oneable.co.th/api/ext/v1/receipts \ -H "Content-Type: application/json" \ -H "X-Api-Key: $KEY_ID" \ -H "X-Timestamp: $TS" \ -H "X-Signature: $SIG" \ -H "Idempotency-Key: pi_abc123" \ -d "$BODY"
การใช้งานให้ถูกภาษี (โปรดอ่าน)
paidAmount = ยอดที่คุณได้รับ ไม่ใช่ยอดที่ผู้จ่ายชำระทั้งหมดvatMode เดียว ใช้ได้ดีเมื่อทุกบรรทัดเป็นภาษีแบบเดียว (ปกติบริการ = 7% ทั้งใบ) หากมีรายการ ยกเว้น VAT จริง ปนกับ VATable ในอนาคต ค่อยขอ VAT รายบรรทัดdocs/ERP-RECEIPT-INTEGRATION.md