Skip to content

v3.5.19 PayUni V2:重試同一張訂單必撞 CREDIT04001

Choose a tag to compare

@j7-dev j7-dev released this 09 Sep 08:27
· 40 commits to master since this release

修正統一金流 PAYUNi V2:顧客重試同一張訂單必定付款失敗

開啟 3D 驗證的站台(payuni_3d_auth = yes,預設值),顧客在結帳頁重按送出、或從「再次嘗試付款」重試同一張訂單時,統一金流必定回 CREDIT04001 已存在相同商店訂單編號,後台堆出一串重複的待付款空單,而且完全查不到失敗原因。

不是分期專屬:Credit / CreditInstallment / CreditSubscription / Atm / Cvs 五個閘道,以及後台「手動續扣」與 WooCommerce Subscriptions 續扣重試,全部中獎。一次付清通常失敗一次就過,所以只留一張殘單、不容易被注意到。

根因 A:_payuni_order_suffix 只在付款成功後才 +1,等於從未生效

商店訂單編號 MerTradeNo = {訂單ID}{尾碼},尾碼存在訂單 meta _payuni_order_suffix。官方 UPP v2 規格載明商店訂單編號10 分鐘內不可重複,授權失敗、逾時、或顧客在 3D 驗證頁中斷,都已經把該編號用掉。

但遞增只寫在 Response::card_response() 的 payment_complete() 之後,失敗分支完全不碰它——只在「不再需要它」的時候才 +1,在「需要它」的失敗情境永遠不動。

改前 #34916 14:06:24 → MerTradeNo 34916 → CREDIT04001 → 自動複製出 #34917
        14:06:52 → MerTradeNo 34916 → CREDIT04001 → 自動複製出 #34918
        後台:3 張單、共用同一把 order_key、全部沒有失敗備註

改後 #34916 14:06:24 → MerTradeNo 34916   (送出即取號,尾碼落地為 1)
        14:06:52 → MerTradeNo 34916-1 → 授權成功,就地完成
        後台:1 張單、失敗原因與發卡行都查得到

根因 B:API 階段的失敗完全不留訂單備註

Request::build_request() 在統一金流同步回錯誤時只丟一則前台通知就 return,Request.php 全檔 0 處 add_order_note()。於是形成兩種截然不同的可觀測性——授權階段失敗(3D callback)有完整備註,API 階段失敗卻零備註,後台只剩一張沒有任何線索的待付款單。而回應裡其實帶著 Status、Message、CardBank、Card4No、ResCodeMsg 等欄位,是外掛自己丟掉的。

修正內容

  • 新增 Request::burn_order_suffix():取號後立即 +1 並存檔,不論後續成功或失敗,這組編號都不會再被用第二次。get_transaction_args()(一般交易)與 build_subscription_request()(定期定額續扣、後台手動續扣)共用
  • 第一次仍是裸編號(34916),第二次才變 34916-1,統一金流後台既有的編號格式不受影響;回呼端本來就以 explode('-')[0] 還原訂單 ID,故加尾碼不影響對帳
  • Response::card_response() 成功分支多餘的 +1 一併移除(留著會讓序號每筆成功交易跳兩號,日後以尾碼反查交易時會對不上)
  • 新增 Response::format_failure_note(),由 API 階段與授權階段共用,兩邊的失敗備註格式因此一致;build_request() 失敗分支改為寫入該備註
  • 失敗備註排除 card_hash:那是統一金流回傳的 CreditHash,可直接拿來續扣,屬於憑證而非診斷資訊,不該留在後台任何人都看得到的訂單備註裡。card_response() 原本的備註也一併排除
  • build_hash_request()(新增付款方式的 5 元綁卡)刻意不改:每次都新建訂單、用完刪除,訂單 ID 天然不重複
  • CREDIT04001 → woomp_copy_order() 的既有 fallback 保留:規格是「10 分鐘內不可重複」而非永久,且升級前殘留的舊訂單沒有這個 meta,仍需要保險

驗證

  • 新增 tests/phpunit/integration/PayuniMerTradeNoTest.php(6 測試 21 斷言),涵蓋首次/二次送出的編號與尾碼落地、續扣路徑遞增、失敗備註排除 card_hash、複製訂單編號唯一性、build_request() 失敗寫入備註
  • 紅燈驗證:以修復前的程式碼跑同一組測試會紅 4 項,其中核心迴歸為「第二次送出仍是裸編號」(期望 12-1、實際 12)
  • 242 個整合測試全綠;phpcs 對照修改前 baseline 無新增違規

升級後注意

  • 升級前殘留、且沒有 _payuni_order_suffix meta 的待付款單,在 10 分鐘內第一次重試仍會撞一次 CREDIT04001,之後即自我修復。實務上直接取消那些殘單即可
  • 後台失敗訂單備註會變多(ATM/超商/信用卡在 API 階段失敗現在都會留一則),這正是本次要補的可觀測性
  • 既有的失敗備註格式微調:多了「統一金流交易失敗」標題,且不再包含 card_hash

修復 #131