開發者文件(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 後面。

GET/merchant/

商家基本資料與 has_shopline_credentials。導客人去付款前先查:收款還沒設好時建連結會回 409。

POST/customers/customer:write

以 email 建立或更新客戶(201 新建/200 既有)。建結帳連結時會自動建立,通常不必先呼叫。

POST/checkout-sessions/checkout:write

建立結帳連結,回 pay_url。欄位見下一節。

GET/checkout-sessions/{id}/checkout:read

status:pending/completed/failed。完成後帶 subscription_id 或 order_id。

POST/checkout-sessions/{id}/cancel/checkout:write

關閉尚未付款的連結。

GET/subscriptions/subscription:read

可帶 ?external_ref= ?status=。單筆:GET /subscriptions/{id}/。

POST/subscriptions/{id}/cancel/subscription:write

{"at_period_end": true}(預設)本期到期才停;false 立即終止。

POST/subscriptions/{id}/schedule-change/subscription:write

改方案,下一期才生效:{"amount": "3600", "title": "旗艦版(月繳)"};{"amount": null} 取消排定。卡片不用重綁,不做期中補差。

GET/transactions/transaction:read

對帳用。?external_ref= ?since=<ISO 8601,記得 URL 編碼> ?status= ?kind=charge|refund。

POST/transactions/{id}/refund/transaction:write

{"amount": "500"},省略為全額。已退款、超額、不支援線上退款的付款方式回 409。

POST/webhook-endpoints/webhook:write

{"url": "...", "event_types": [...]},回應的 secret 只出現這一次;event_types 留空=全部事件。也可以在後台設定。

GET/webhook-endpoints/webhook:write

列出接收端(不含 secret)。刪除:DELETE /webhook-endpoints/{id}/。

GET/webhook-deliveries/webhook:write

查投遞紀錄,例如 ?status=failed。

6. 建立結帳連結

POST /checkout-sessions/ 的欄位:

欄位必填說明
mode必填subscription(定期扣款)/one_time(一次付清)/installment(信用卡分期)
title必填顯示在付款頁
amount必填字串,大於 0。subscription 為每期金額
currency預設 TWD
billing_cyclesubscription 必填daily/weekly/monthly/yearly
interval_count預設 1(monthly + 3 = 每季)
billing_day1–28 固定扣款日;不給就以開通日為準
installment_countsinstallment 必填開放的期數,例如 [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。我們會在網址後面加上:

參數內容
statuspaid/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.data

Node.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' })) 才拿得到原始 bytes

10. 錯誤碼

HTTPcode說明
400INVALID欄位驗證失敗,message 為各欄位的錯誤
401NOT_AUTHENTICATED金鑰無效、已撤銷、已過期,或商家已停用
403INSUFFICIENT_SCOPE金鑰沒有這個操作的權限
404NOT_FOUND不存在,或屬於其他商家
409CREDENTIALS_NOT_READY商家還沒完成 SHOPLINE Payments 收款設定
409IDEMPOTENCY_KEY_REUSED/REQUEST_IN_PROGRESS見「冪等」
409CONFLICT狀態不允許,例如重複退款、取消已取消的訂閱
429—超過限流(每把金鑰每分鐘 300 次)
502GATEWAY_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(不要附金鑰全文)。