跳到內容

付款通知(webhook)

在 ChatGPT 開啟在 Claude 開啟

付款結果確定時,應援會 POST 一則通知到你的伺服器。請把它當成「提醒你去查」,以回查的結果為準。

通知網址只能在 CRM 設定,API 請求沒有這個欄位:

  • 位置:CRM「總設定」→「開發者」→「應援金流設定」→「交易資料回傳網址位置」。
  • 必須是 https://、使用 443 port,而且可以從網際網路連到。內部網址、私有 IP 會被擋下。
  • 測試環境與正式環境分別設定。
  • 沒有設定網址的期間,通知會直接略過,之後補設也不會補送。
情境 purpose status
信用卡、Apple Pay 付款成功或失敗 charge charged、claimed 或 failed
3D 驗證開始後 10 分鐘沒完成 charge failed(message: 3DS_ABANDONED)
超商代碼、ATM 取號 charge charging(沒有 success)
超商代碼、ATM 繳費完成 charge charged 或 claimed
超商代碼、ATM 逾期未繳 charge failed(message: PAYMENT_EXPIRED)
LINE Pay 等待確認、確認結果 charge charging,之後是最終結果
定期定額每一期(含重扣) charge 同上,action: subscription
綁卡頁完成或失敗 token —
預約定期定額結帳完成或失敗 schedule_subscription —
退款、定期定額取消、重扣排定或用完 refund、subscription_cancelled、subscription_retry 需請應援開啟
API 交易的電子發票開立成功、作廢成功 invoice_issued、invoice_voided 需請應援開啟,見電子發票號碼與狀態
消費者在換卡頁換卡成功 subscription_card_changed —

不會送通知的情況:

  • 消費者沒有付款就離開,交易停在 initiated。
  • 用 token 扣款、用 token 建立定期定額的第一期。結果以 API 回應為準。
  • 付款前的檢查就失敗(例如金額超出付款方式的範圍)。

每一種通知的欄位見付款通知內容。

項目 規則
格式 POST,Content-Type: application/json
逾時 每次 10 秒
成功的條件 回應 HTTP 2xx。不看回應內容
轉址 不跟隨。回 3xx 會被當成失敗
次數 最多 3 次,間隔約 2 秒、4 秒
3 次都失敗 不會再送,也沒有後台重送功能
順序 不保證

在 CRM 產生「付款通知簽章密鑰」後,每則付款通知都會帶 OenPay-Signature header,可以用它確認通知真的是應援送的、內容沒有被改過。還沒產生密鑰時,通知不帶簽章,任何人都可以偽造一則送到你的網址,請一定要回查。

  1. 進入 CRM「總設定」→「開發者」→「應援金流設定」,找到「付款通知簽章密鑰」。
  2. 按「產生簽章密鑰」。密鑰以 pay_whsec_ 開頭,只會顯示這一次,請立刻存到伺服器的環境變數或密鑰管理服務。
  3. 下一則通知起就會帶簽章。測試環境與正式環境的密鑰分開產生。

OenPay-Signature 的格式是 t=<unix 秒>,v1=<hex>:

  1. 取得還沒解析的原始 body。先 JSON.parse 再轉回字串會改變內容,簽章一定對不上。
  2. 從 header 拆出 t 與 v1。
  3. 用完整的 pay_whsec_ 字串當 key,對 {t}.{原始 body} 計算 HMAC-SHA256,輸出十六進位,用固定時間比較函式和 v1 比對。
  4. 建議拒收 t 和現在相差超過 5 分鐘的通知。
  5. 驗證失敗回 4xx,不處理這則通知。

已產生密鑰時,應援不會改送沒有簽章的通知;收到沒有 OenPay-Signature 的通知請直接拒收。

終端機視窗
# 在本機模擬一個帶簽章的通知,測試你的驗簽程式(測試資料,不是實際的通知)
BODY='{"purpose":"charge","action":"onetime","merchantId":"ming","orderId":"A20260928001","id":"2HhndgEquCbDzC5OyVxWSGZmd2l","transactionId":"2HhndgEquCbDzC5OyVxWSGZmd2l","transactionHid":"P20260928AB12CD34","status":"charged","success":true,"paymentMethod":"card","amount":1200,"currency":"twd","message":""}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$OEN_PAYMENT_WEBHOOK_SECRET" | sed 's/^.* //')
EVENT_ID="evt_$(printf '%s' '["charge","ming","2HhndgEquCbDzC5OyVxWSGZmd2l","charged",true]' | openssl dgst -sha256 | sed 's/^.* //' | cut -c1-32)"
curl -X POST "http://localhost:3000/webhooks/oen" \
-H "Content-Type: application/json" \
-H "OenPay-Event-Id: $EVENT_ID" \
-H "OenPay-Delivery-Attempt: 1" \
-H "OenPay-Signature: t=$T,v1=$SIG" \
-d "$BODY"

通知可能重複,也可能順序顛倒。驗證簽章通過代表內容是真的,但不一定是最新狀態。收到付款通知(purpose 是 charge)時,出貨前請回查:

  1. 收到通知,先回 HTTP 200(有驗證簽章時,驗證通過並保存後再回)。
  2. 用 body 的 id(27 字元)呼叫 GET /transactions/{id}。
  3. 確認查到的交易屬於你的訂單:比對 orderId 與 amount。
  4. 依查到的 status 更新訂單。
  • 剛付款完成的極短時間內,查到的可能還是舊狀態。查到 charging 或 initiated 時,隔幾秒再查一次。
  • 退款與發票通知的 id 是事件編號(例如 refund#…、invoice#…),不是交易編號;要回查原交易,請用 body 的 chargeId。其他通知依 purpose 處理,欄位見付款通知物件。
  • token 通知沒有 API 可以回查。請確認 id 是你剛建立的綁卡頁,並核對 customId。
  • 程式範例見快速開始。
  • 超商代碼、ATM、LINE Pay 會先收到 charging,之後才收到結果,兩則的 id 相同。
  • 通知可能重複送達,也可能順序顛倒。
  • 每則通知都帶 OenPay-Event-Id(evt_ 加 32 個十六進位字元)與 OenPay-Delivery-Attempt(第幾次投遞,1 到 3)。同一則通知重送時 OenPay-Event-Id 不變;同一筆交易先送 charging、再送 charged 是兩個不同的事件。
  • 這兩個 header 不在簽章範圍內,不能直接拿來防重複。請用已驗證的 body 依下式重算,和 header 不同就拒收:evt_ + sha256(JSON.stringify([purpose, merchantId, id, status, success])) 的前 32 個十六進位字元,缺少的欄位帶 null。上方的範例程式已經示範。
  • 「標記為已付款」這類動作仍要做到重複執行也安全,並以回查到的狀態決定要做什麼。
  • 應援每次只等 10 秒。請先回 200,再把回查、出貨、寄信等工作放到背景處理。
  • 不要回 3xx,也不要讓網址需要登入。
  • 防火牆有擋外部連線時,請向應援索取付款通知的來源 IP。

請在你的系統排一個定期工作,處理「超過一段時間還沒有結果」的訂單:

  1. 找出建立超過 15 分鐘、還沒有最終結果的訂單。
  2. 用 GET /order/{orderId}/transactions 查這個訂單的所有交易。
  3. 有 charged 或 claimed:補標為已付款。
  4. 有 charging:繼續等待(超商代碼最多 48 小時、ATM 3 天)。
  5. 全部是 initiated 或 failed:視為未付款。

每天也可以用查詢交易列表做一次完整對帳,見查詢與對帳。

  • 測試環境會實際送出付款通知,送到測試環境 CRM 設定的網址。
  • 沒有測試事件 API。要測試接收端,可以建立一筆測試交易並用測試卡號付款(成功用 4242 4242 4242 4242、失敗用 4012 8888 1888 8333),或用 cURL 自己送一則模擬的通知到你的網址;有產生簽章密鑰時,請用同一把密鑰自己算出 OenPay-Signature,見上方驗證簽章的 cURL 範例。
  • 超商代碼、ATM 的 charged 與逾期通知,可以在測試環境 CRM 的交易明細按「模擬付款成功」或「模擬付款失敗/過期」觸發,見環境與測試。