Skip to content

v3.5.17 PayUni V3 訂閱綁卡與續扣修復

Latest

Choose a tag to compare

@j7-dev j7-dev released this 01 Sep 11:41

修正 PayUni V3 定期定額訂閱:綁卡取不到 CreditHash、續扣必然失敗

PayUni V3 定期定額的續扣自上線起從未成功過。正式站於 2026-09-01 全數扣款失敗,逐行查證後為三層獨立缺陷。

根因

1. UseTokenType 送了 2(記憶卡號)

依 PayUni 文件,記憶卡號僅供下次結帳自動帶出卡號,不會壓可幕後扣款的 CreditHash。訂閱續扣需要的是「約定信用卡」,必須送 3(強制約定,消費者無法取消)。

2. 前端 TOKEN_TYPE 常數語意與官方顛倒

constants.module.js 定義為 REMEMBER_CARD: 1SUBSCRIPTION_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.jsTOKEN_TYPE 對齊官方定義(1=約定信用卡/2=記憶卡號/3=強制約定信用卡)
  • get_sdk_token() 接受 EUseTokenTypeBootstrap 依購物車是否含訂閱商品決定,並經 USE_TOKEN_TYPE localize 給前端。SDK_TOKEN 在頁面載入時就已綁定某個 UseTokenType,故前端一律以後端值為準,不自行推導
  • process_zero_amount_token()process_positive_amount() 改送 FORCE_BIND
  • save_payment_token() 正式環境無 CreditHash 直接拒存並寫 error log,僅 sandbox 保留卡號替身以維持可測試性
  • 新增 warn_if_subscription_token_missing():綁卡未取得 CreditHash 時於訂單留下警示備註與 _payuni_token_bind_failed meta,讓失敗當場可見
  • 新增 request_credit_api(),續扣改打 {base_api_url}/credit(信用卡幕後 Token 交易),V1 fallback 保留為第二層

驗證

  • 233 個整合測試全綠;新增 SubscriptionV3TokenBindingTest(8 tests)鎖住三層根因,含一條直接讀 JS 檔比對前後端常數同值的測試
  • sandbox 端到端跑通:綁卡回傳 CreditHashCreditLife、Token 存入 payuni-credit-subscription-v3 閘道、續扣回 SUCCESS 授權成功、訂閱轉為 active

⚠️ 升級後注意

  • 需向 PayUni 確認商店已開通信用卡 Token 功能並綁定 IP(官方文件列為前置作業),否則程式碼改對了 PayUni 仍不會回傳 CreditHash
  • 既有已失敗的訂閱因 PayUni 端從未建立 Token,無法補救,需會員重新綁卡