開發者文件(Open API)
最後更新:
給商家自己的系統(官網、ERP、會員系統)用:伺服器對伺服器呼叫,建立收款連結(一次付清、訂閱、分期)、查付款與訂閱、退款, 並用 webhook 接收付款事件。款項一樣由 SHOPLINE Payments 直接撥入商家帳戶。
不寫程式?想用說的開收款連結、查交易,用 AI 助理(MCP)就好,不需要工程師: 見操作教學第 25~28 步。
1. 開始之前
- 商家要先在後台填好 SHOPLINE Payments 金鑰(操作教學第 4 步), 否則建連結會回
409 CREDENTIALS_NOT_READY。 - API 金鑰在商家後台「設定 → 開發者 → 建立金鑰」,用途選「自家系統串接」 (操作教學第 25~27 步)。金鑰全文只顯示一次。
- Base URL:
https://pay.site-now.co/api/open/v1。全部 JSON、UTF-8;金額一律是字串(例如"1800.00"),id 是 UUID。 - 錯誤格式固定:
{"error": {"code": "...", "message": "..."}} - 要用沙盒(測試卡、不會真的扣款)先測,請到 LINE 客服申請沙盒金鑰。 上線前建議完整跑一輪:建連結 → 付款 → 收 webhook → 查詢對帳。
2. 認證與權限
Authorization: Bearer slp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx金鑰決定了你操作的是哪一個商家。slp_live_ 開頭是正式環境,slp_test_ 是沙盒。 權限不足回 403 INSUFFICIENT_SCOPE;金鑰本身無效才是 401。限流:每把金鑰每分鐘 300 次。
| scope | 用途 |
|---|---|
| checkout:read / checkout:write | 查詢/建立結帳連結 |
| customer:read / customer:write | 查詢/建立或更新客戶 |
| subscription:read / subscription:write | 查詢訂閱/取消訂閱、排定方案變更 |
| transaction:read / transaction:write | 查詢交易/退款 |
| webhook:write | 管理 webhook 接收端 |
「自家系統串接」預設不含退款(transaction:write)、取消訂閱(subscription:write)、改 webhook(webhook:write), 需要的話建立金鑰時用途選「自訂」再勾。
3. 冪等(避免重複開單)
所有 POST 請帶 Idempotency-Key(例如 UUID 或你的訂單編號)。網路逾時後重送很常見,沒帶就可能開出兩張單。
| 情況 | 結果 |
|---|---|
| 同一把 key、同樣內容 | 回放第一次的結果,附 Idempotent-Replay: true |
| 同一把 key、不同內容 | 409 IDEMPOTENCY_KEY_REUSED |
| 同一把 key、前一次還在處理 | 409 REQUEST_IN_PROGRESS,稍後用同一把 key 重試 |
| 前一次失敗(4xx/5xx) | 不佔用 key,可以用同一把重試 |
4. 快速開始
BASE=https://pay.site-now.co/api/open/v1
KEY=slp_live_你的金鑰
# 1) 確認金鑰是哪個商家、收款設定好了沒
curl -s $BASE/merchant/ -H "Authorization: Bearer $KEY"
# 2) 建一條一次付清的收款連結
curl -s -X POST $BASE/checkout-sessions/ \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-20261005-0001" \
-d '{
"mode": "one_time",
"title": "訂單 #20261005-0001",
"amount": "1280",
"customer": {"email": "buyer@example.com", "name": "王小明"},
"external_ref": "20261005-0001",
"return_url": "https://shop.example.com/orders/20261005-0001/done",
"cancel_url": "https://shop.example.com/cart"
}'
# → {"id": "…", "pay_url": "https://pay.site-now.co/checkout/…", "status": "pending", …}
# 3) 把 pay_url 給客人。付款完成後我們送 webhook;也可以主動查:
curl -s $BASE/checkout-sessions/<id>/ -H "Authorization: Bearer $KEY"external_ref 是整條串接的關鍵:放你系統裡不會變的主鍵(例如訂單編號), 之後的查詢與 webhook 事件都會原樣帶回,你才對得回自己的資料。只在建立時寫入,之後不能改。
定期扣款(訂閱):
curl -s -X POST $BASE/checkout-sessions/ \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"mode": "subscription",
"title": "專業版(月繳)",
"amount": "1800",
"billing_cycle": "monthly",
"customer": {"email": "owner@example.com"},
"external_ref": "ws:7f3c:pro:monthly"
}'5. 端點
路徑都接在 https://pay.site-now.co/api/open/v1 後面。
商家基本資料與 has_shopline_credentials。導客人去付款前先查:收款還沒設好時建連結會回 409。
以 email 建立或更新客戶(201 新建/200 既有)。建結帳連結時會自動建立,通常不必先呼叫。
建立結帳連結,回 pay_url。欄位見下一節。
status:pending/completed/failed。完成後帶 subscription_id 或 order_id。
關閉尚未付款的連結。
可帶 ?external_ref= ?status=。單筆:GET /subscriptions/{id}/。
{"at_period_end": true}(預設)本期到期才停;false 立即終止。
改方案,下一期才生效:{"amount": "3600", "title": "旗艦版(月繳)"};{"amount": null} 取消排定。卡片不用重綁,不做期中補差。
對帳用。?external_ref= ?since=<ISO 8601,記得 URL 編碼> ?status= ?kind=charge|refund。
{"amount": "500"},省略為全額。已退款、超額、不支援線上退款的付款方式回 409。
{"url": "...", "event_types": [...]},回應的 secret 只出現這一次;event_types 留空=全部事件。也可以在後台設定。
列出接收端(不含 secret)。刪除:DELETE /webhook-endpoints/{id}/。
查投遞紀錄,例如 ?status=failed。
6. 建立結帳連結
POST /checkout-sessions/ 的欄位:
| 欄位 | 必填 | 說明 |
|---|---|---|
| mode | 必填 | subscription(定期扣款)/one_time(一次付清)/installment(信用卡分期) |
| title | 必填 | 顯示在付款頁 |
| amount | 必填 | 字串,大於 0。subscription 為每期金額 |
| currency | 預設 TWD | |
| billing_cycle | subscription 必填 | daily/weekly/monthly/yearly |
| interval_count | 預設 1(monthly + 3 = 每季) | |
| billing_day | 1–28 固定扣款日;不給就以開通日為準 | |
| installment_counts | installment 必填 | 開放的期數,例如 [3, 6, 12] |
| cart_items | 品項明細(subscription 不支援),見下方 | |
| customer | {"email", "name", "phone"} | |
| external_ref | 你系統裡的主鍵,之後所有查詢與事件都原樣帶回 | |
| metadata | 自由欄位,原樣帶回 | |
| return_url | 付款成功後導回你網站的哪一頁(https) | |
| cancel_url | 客人放棄或付款失敗時的返回頁(https) |
品項明細 cart_items
給了的話付款頁會列出明細,付款後也會成為訂單品項。明細小計加總必須等於 amount, 運費、折扣請一併列成品項(折扣用負數)。上限 100 筆。品項指的是你自己系統的商品,不會扣本站的庫存。
"cart_items": [
{"name": "經典帆布包", "quantity": 2, "unit_price": "540"},
{"name": "運費", "quantity": 1, "unit_price": "200"},
{"name": "折價券 WELCOME100", "quantity": 1, "unit_price": "-100"}
]7. 付款後導回你的網站
給了 return_url/cancel_url:付款成功自動導回 return_url;付款失敗時結果頁多一個回 cancel_url 的按鈕; 還沒付就想離開,結帳頁上方的「回商店」會連到 cancel_url。我們會在網址後面加上:
| 參數 | 內容 |
|---|---|
| status | paid/failed/cancelled |
| ref | 本次結帳的參考編號(等同 webhook 的 data.request_id) |
| external_ref | 你建立連結時給的值 |
status=paid 不是付款成功的證明——網址任何人都能自己打。 用它找到訂單就好,出貨、開通一律以 webhook 或 GET /checkout-sessions/{id}/ 為準。
8. Webhook 付款事件
在後台「設定 → 開發者 → Webhook 接收端」或用 API 註冊你的網址,付款、續期、退款時我們會 POST 給你。
| type | 何時 |
|---|---|
| subscription.activated | 首次付款成功、訂閱開通 |
| subscription.charged | 續期扣款成功 |
| subscription.payment_failed | 續期扣款失敗(每次重試都會送) |
| subscription.plan_changed | 排定的方案變更已套用 |
| subscription.cancelled | 訂閱終止(data.reason:requested/payment_failed/immediate) |
| order.paid | 一次付清或分期訂單付款成功 |
| refund.succeeded | 退款成功 |
{
"id": "9f1c…", // 這次投遞的唯一 id,請據此去重
"type": "subscription.charged",
"created_at": "2026-10-05T03:00:00Z",
"external_ref": "ws:7f3c:pro:monthly",
"metadata": {},
"data": { "subscription_id": "…", "amount": "1800.00", "status": "active",
"current_period_end": "2026-11-05", "card_last4": "4242" }
}- 處理成功回
2xx;認得但處理失敗回5xx,我們會重試;對不到 external_ref 或重複的事件也回2xx,不然會一直重試。 - 失敗後依 1 分 → 5 分 → 25 分 → 2 小時 → 12 小時重試,五次後停止;可以在後台手動重送。
- 同一個事件可能送到不只一次,請用
id去重。 - 系統停機後沒有「重放全部」的端點:用
GET /transactions/?since=與GET /subscriptions/回填。
9. 驗證簽章(請務必實作)
X-SLP-Event-Id: 9f1c…
X-SLP-Event-Type: subscription.charged
X-SLP-Signature: t=1759633200,v1=8d9f…v1 = HMAC_SHA256(secret, "{t}." + 原始 body bytes),secret 是註冊接收端時拿到的密鑰。 一定要用原始 bytes 驗,不要先 parse 再序列化(空白與欄位順序會變)。
Python
import hashlib, hmac, time
def verify(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
try:
parts = dict(p.split('=', 1) for p in header.split(','))
ts, received = int(parts['t']), parts['v1']
except (ValueError, KeyError):
return False
if abs(time.time() - ts) > tolerance: # 防重放
return False
expected = hmac.new(secret.encode(), f'{ts}.'.encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received)
# Django:用 request.body(原始 bytes),不要用 request.dataNode.js
const crypto = require('crypto');
function verify(secret, header, rawBody, tolerance = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const ts = Number(parts.t);
if (!ts || Math.abs(Date.now() / 1000 - ts) > tolerance) return false;
const expected = crypto.createHmac('sha256', secret)
.update(Buffer.concat([Buffer.from(`${ts}.`), rawBody]))
.digest('hex');
return expected.length === parts.v1?.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
// Express:app.use(express.raw({ type: 'application/json' })) 才拿得到原始 bytes10. 錯誤碼
| HTTP | code | 說明 |
|---|---|---|
| 400 | INVALID | 欄位驗證失敗,message 為各欄位的錯誤 |
| 401 | NOT_AUTHENTICATED | 金鑰無效、已撤銷、已過期,或商家已停用 |
| 403 | INSUFFICIENT_SCOPE | 金鑰沒有這個操作的權限 |
| 404 | NOT_FOUND | 不存在,或屬於其他商家 |
| 409 | CREDENTIALS_NOT_READY | 商家還沒完成 SHOPLINE Payments 收款設定 |
| 409 | IDEMPOTENCY_KEY_REUSED/REQUEST_IN_PROGRESS | 見「冪等」 |
| 409 | CONFLICT | 狀態不允許,例如重複退款、取消已取消的訂閱 |
| 429 | — | 超過限流(每把金鑰每分鐘 300 次) |
| 502 | GATEWAY_ERROR | 金流端回報失敗 |
11. 常見問題
- 建連結回 409 CREDENTIALS_NOT_READY
- 商家還沒在後台填 SHOPLINE Payments 金鑰。先呼叫 GET /merchant/ 看 has_shopline_credentials,是 false 就引導商家去設定。
- 收到事件但 external_ref 對不到
- 建連結時沒帶,或帶的值跟你系統的鍵對不上。請放不會變的內部 id,不要放 email 或名稱。
- 續期扣款失敗多久會停
- 預設重試 3 次(商家可調),用盡後訂閱轉為 cancelled 並送 subscription.cancelled(reason=payment_failed)。收到第一次 payment_failed 可以先通知使用者換卡,但不要立刻停權。
- 改方案為什麼不是馬上生效
- 本期已經收款,期中補差要處理退款、發票與跨期對帳,所以 schedule-change 一律下一期生效。
- 金鑰外流怎麼辦
- 到後台「設定 → 開發者」撤銷那把金鑰(立刻失效),再建一把新的換上。
串接遇到問題,請到 LINE 客服,附上請求時間、端點與回應的 error.code(不要附金鑰全文)。