修正 PayUni V3 定期定額訂閱:綁卡取不到 CreditHash、續扣必然失敗
PayUni V3 定期定額的續扣自上線起從未成功過。正式站於 2026-09-01 全數扣款失敗,逐行查證後為三層獨立缺陷。
根因
1. UseTokenType 送了 2(記憶卡號)
依 PayUni 文件,記憶卡號僅供下次結帳自動帶出卡號,不會壓可幕後扣款的 CreditHash。訂閱續扣需要的是「約定信用卡」,必須送 3(強制約定,消費者無法取消)。
2. 前端 TOKEN_TYPE 常數語意與官方顛倒
constants.module.js 定義為 REMEMBER_CARD: 1、SUBSCRIPTION_CARD: 2,使 getTradeResult() 送 1 而後端 token_get 送 2。兩者不一致時 PayUni 直接不執行綁定,症狀是「授權成功但回傳沒有 CreditHash」,極難從日誌看出。
3. 續扣打了 UNi Embed 的 /iframe/merchant_trade
該端點必須帶前端 SDK_TOKEN(10 分鐘有效),背景排程無從取得,PayUni 必然回 IFTRADE02004「未有交易 Token」——這條路徑 100% 不可能成功。
此外 save_payment_token() 在沒有 CreditHash 時,會以卡號組合(Card6No****Card4No)當替身存入 WC_Payment_Tokens,把綁卡失敗偽裝成成功,直到扣款日才爆——這是災情擴大到全站的直接原因。
修正內容
constants.module.js的TOKEN_TYPE對齊官方定義(1=約定信用卡/2=記憶卡號/3=強制約定信用卡)get_sdk_token()接受EUseTokenType;Bootstrap依購物車是否含訂閱商品決定,並經USE_TOKEN_TYPElocalize 給前端。SDK_TOKEN 在頁面載入時就已綁定某個UseTokenType,故前端一律以後端值為準,不自行推導process_zero_amount_token()與process_positive_amount()改送FORCE_BINDsave_payment_token()正式環境無 CreditHash 直接拒存並寫 error log,僅 sandbox 保留卡號替身以維持可測試性- 新增
warn_if_subscription_token_missing():綁卡未取得 CreditHash 時於訂單留下警示備註與_payuni_token_bind_failedmeta,讓失敗當場可見 - 新增
request_credit_api(),續扣改打{base_api_url}/credit(信用卡幕後 Token 交易),V1 fallback 保留為第二層
驗證
- 233 個整合測試全綠;新增
SubscriptionV3TokenBindingTest(8 tests)鎖住三層根因,含一條直接讀 JS 檔比對前後端常數同值的測試 - sandbox 端到端跑通:綁卡回傳
CreditHash與CreditLife、Token 存入payuni-credit-subscription-v3閘道、續扣回SUCCESS 授權成功、訂閱轉為 active
⚠️ 升級後注意
- 需向 PayUni 確認商店已開通信用卡 Token 功能並綁定 IP(官方文件列為前置作業),否則程式碼改對了 PayUni 仍不會回傳 CreditHash
- 既有已失敗的訂閱因 PayUni 端從未建立 Token,無法補救,需會員重新綁卡