External API v1
ERP บัญชี · สำหรับนักพัฒนา
REST · JSON · HMAC

ให้แอปอื่นออกใบแจ้งหนี้ & ใบเสร็จเข้า ERP

External API ให้ระบบภายนอก (เช่น WideStay, POS, เว็บจอง) สั่งออก ใบกำกับภาษี/ใบแจ้งหนี้ และ ใบเสร็จรับเงิน เข้าบริษัทใน ERP โดยตรง — ลงบัญชีคู่ (double-entry) และรันเลขที่เอกสารให้อัตโนมัติ เหมือนออกจากหน้าเว็บทุกประการ

Base URL https://erp.oneable.co.th/api/ext/v1

คีย์ต่อบริษัท (per-company)

แต่ละบริษัทมีคู่คีย์ของตัวเอง เจ้าของบริษัท (OWNER) สร้าง/เพิกถอนได้ที่ ตั้งค่า → การเชื่อมต่อ (Integrations) หรือผ่าน API จัดการคีย์

X-Api-Key public
wsk_live_a1b2c3…

ระบุว่าเป็นบริษัทไหน — ใส่ใน header ได้ปลอดภัย

Signing Secret secret
whsec_…

โชว์ ครั้งเดียวตอนสร้าง ใช้เซ็น HMAC — เก็บฝั่งคุณเท่านั้น

companyId ผูกกับคีย์เสมอ — payload ไม่ต้องระบุบริษัท และข้ามบริษัทกันไม่ได้

การยืนยันตัวตน (HMAC-SHA256)

ทุก request เซ็นด้วย secret บน raw body + timestamp กัน tamper และ replay ทุก request ต้องมี 3 header นี้:

Headerค่า
X-Api-KeykeyId สาธารณะของบริษัท (wsk_live_…)
X-TimestampUnix time (วินาที) ตอนยิง — ต้องอยู่ใน ±300 วินาที
X-Signaturehex ของ 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 ล้ม

POST/api/ext/v1/invoices

ออกใบกำกับภาษี / ใบแจ้งหนี้

สร้างและ issue ใบกำกับภาษีทันที (ลง GL: เดบิตลูกหนี้ / เครดิตรายได้+ภาษีขาย) ลูกค้าจับคู่จากเลขผู้เสียภาษี ถ้าไม่มีจะสร้างให้ · ต้องมี scope invoice:create

Request body

fieldชนิดรายละเอียด
customer.name reqstringชื่อลูกค้า/ผู้เช่า
customer.taxIdstringเลขผู้เสียภาษี 13 หลัก (ใช้จับคู่กันซ้ำ)
customer.addressstringที่อยู่บนใบกำกับ
lines[] reqarrayรายการ: description, qty, unitPrice, discount, isService
vatModestringexclude (ราค ยังไม่รวม VAT · ค่าเริ่มต้น) · include · none
whtRatenumber% หัก ณ ที่จ่าย ที่คาดไว้ (ไม่บังคับ)
dateISO dateวันที่เอกสาร (ดีฟอลต์ = วันนี้)
externalRefstringเลขอ้างอิงฝั่งคุณ (เก็บใน refNumber)

Response 201

{
  "id": "cly…",
  "taxInvoiceNo": "IV69070001",   // เลขใบกำกับภาษี
  "subTotal": 1000,
  "vatAmount": 70,
  "total": 1070,
  "status": "ISSUED",
  "replayed": false
}
POST/api/ext/v1/receipts

ออกใบเสร็จรับเงิน (ออกใบกำกับ + รับชำระในก้อนเดียว)

เหมาะกับ event "จ่ายเงินสำเร็จ" — สร้างใบกำกับ (ถ้ายังไม่มี) แล้วรับชำระ ออก ใบเสร็จ (RC) พร้อมลง GL รับเงิน ทั้งหมด atomic · ต้องมี scope receipt:create

Request body

รับ field ชุดเดียวกับ /invoices (customer, lines, vatMode…) บวก field รับชำระ:

fieldชนิดรายละเอียด
invoiceIdstringออกใบเสร็จให้ใบกำกับที่มีอยู่แล้ว (ข้ามการสร้างใหม่)
paidAmountnumberยอดชำระ (ดีฟอลต์ = ยอดเต็มของใบ)
methodstringTRANSFER (ดีฟอลต์) · CASH · CHEQUE
whtnumberยอดหัก ณ ที่จ่าย (บาท)
referencestringเลขอ้างอิงการโอน/charge

Response 201

{
  "taxInvoiceNo": "IV69070001",   // ใบกำกับภาษี
  "receiptNo": "RC69070001",      // ใบเสร็จรับเงิน
  "invoiceId": "cly…",
  "total": 1070,
  "paid": 1070,
  "replayed": false
}

รหัสข้อผิดพลาด

HTTPerrorสาเหตุ
401missing_auth_headersขาด X-Api-Key / X-Timestamp / X-Signature
401invalid_keyไม่พบคีย์ หรือถูกปิดใช้งาน
401bad_signatureลายเซ็นไม่ตรง (secret ผิด/บอดี้ถูกแก้)
401stale_timestamptimestamp เกิน ±300 วินาที
403insufficient_scopeคีย์ไม่มีสิทธิ์ทำรายการนี้
400missing_customer_name / missing_lines / invalid_amountข้อมูลไม่ครบ/ไม่ถูกต้อง
409period_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 = ยอดที่คุณได้รับ ไม่ใช่ยอดที่ผู้จ่ายชำระทั้งหมด
อย่าใส่รายการที่เก็บแทนคนอื่น — เงินที่ไม่ได้ผ่านมือคุณ (เช่น ค่าเช่าที่ธนาคารโอนตรงให้เจ้าของทรัพย์) ห้ามใส่เป็นบรรทัดในใบกำกับของคุณ — ไม่ใช่รายได้คุณ ผู้รับเงินจริงต้องออกเอกสารเอง
VAT เดียวต่อใบ — v1 คิด VAT ทั้งใบตาม vatMode เดียว ใช้ได้ดีเมื่อทุกบรรทัดเป็นภาษีแบบเดียว (ปกติบริการ = 7% ทั้งใบ) หากมีรายการ ยกเว้น VAT จริง ปนกับ VATable ในอนาคต ค่อยขอ VAT รายบรรทัด
รายละเอียดเชิงเทคนิค + สัญญา webhook ฉบับเต็ม: docs/ERP-RECEIPT-INTEGRATION.md