WP Manifestindependent plugin directory
manifest / ecommerce / ys-shopline-via-woocommerce

YS Shopline via WooCommerce

Support Shopline Payments for WooCommerce, including HPOS and Subscriptions. Supports Credit Card, ATM, JKOPay, Apple Pay, LINE Pay, and Chailease BNPL.

by YangSheep · github.com/ya19880104/ys-shopline-via-woocommerce · website

★ 4stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/ya19880104/ys-shopline-via-woocommerce/archive/refs/heads/master.zip

整合 Shopline Payments 金流至 WooCommerce 的外掛,支援 HPOS 和 WooCommerce Subscriptions。

版本資訊

  • 目前版本:3.6.10
  • PHP 需求:>= 8.0
  • WordPress 需求:>= 6.0
  • WooCommerce 需求:7.0 - 10.4

支援的付款方式

付款方式 Gateway ID 說明
信用卡 ys_shopline_credit 信用卡一次付清
信用卡分期 ys_shopline_credit_installment 信用卡分期付款
信用卡訂閱 ys_shopline_credit_subscription 訂閱制付款,整合 WC Subscriptions
ATM 虛擬帳號 ys_shopline_atm ATM 轉帳付款(可設繳費期限)
JKOPay ys_shopline_jkopay 街口支付
Apple Pay ys_shopline_applepay Apple Pay
LINE Pay ys_shopline_linepay LINE Pay
Chailease BNPL ys_shopline_bnpl 中租零卡分期(可設付款期限)

主要功能

  • HPOS 相容:完全支援 WooCommerce High-Performance Order Storage
  • Block Checkout:已停用(目前僅支援傳統結帳頁)
  • 訂閱支援:與 WooCommerce Subscriptions 整合
  • 儲存卡管理:My Account 頁面管理已儲存的付款工具
  • Webhook 整合:自動接收 Shopline 付款狀態通知
  • 狀態同步:自動和手動訂單狀態同步
  • 沙盒模式:支援測試環境切換

安裝方式

  1. 上傳外掛至 wp-content/plugins/ 目錄
  2. 在 WordPress 後台啟用外掛
  3. 前往 WooCommerce > 設定 > 付款 設定各付款方式
  4. 前往 WooCommerce > 設定 > Shopline Payment 設定 API 金鑰

設定說明

API 金鑰設定

在 WooCommerce > 設定 > Shopline Payment 頁面設定:

  • 測試模式:啟用後使用沙盒環境
  • Merchant ID:商家 ID
  • API Key:API 金鑰
  • Sign Key:簽章金鑰

Webhook 設定

在 Shopline 商家後台設定 Webhook URL:

https://your-domain.com/wp-json/ys-shopline/v1/webhook

開發者資訊

詳細的開發文件請參考 DEVELOPMENT.md。


變更紀錄

3.6.10 - 2026-10-03

修正分期付款成功後,後台訂單付款資訊未顯示期數(GitHub issue #1)。

  • 在既有「SHOPLINE 訂單付款資訊」區塊的付款方式下方顯示「分期期數」,讀取信用卡分期或中租零卡分期已儲存於該訂單的期數;舊訂單已有資料即可顯示,不需重新付款或呼叫 SHOPLINE API。
  • 僅顯示目前分期付款方式的有效期數。一般信用卡、ATM、LINE Pay、Apple Pay、街口與信用卡訂閱不會因殘留的分期資料而誤顯示期數;信用卡分期重新選擇一次付清時清除前次期數。
  • 維持原有區塊位置與表格設計;付款請求、狀態同步、ATM 配號、退款及訂閱流程不變。

3.6.9 - 2026-08-30

修正 ATM 從「訂單付款頁」重試時,付款請求不會送出、顧客拿不到虛擬帳號。

SHOPLINE 回報:虛擬帳號這個付款方式,沒有執行 payment.pay(),因此金流端未請求付款、未配號。

根因:訂單付款頁的處理依 remote_outcome 分流,缺值一律保守擋下。YSGatewayBase 的 handle_next_action() 一直都回傳這個鍵,只有 ATM 的覆寫版沒有。於是顧客從訂單付款頁重試 ATM 時,交易明明已在 SHOPLINE 端建立,前端卻收到「付款結果確認中」的失敗,SDK 的付款程序從不執行、虛擬帳號從不產生,再試又被既存交易保護擋住。此路徑與瀏覽器中斷無關,必定重現。

  • ATM 的 nextAction 回應補回 remote_outcome 與 failureUrl 兩個鍵,與 YSGatewayBase 一致。這是補上既有機制的缺漏,不新增任何機制;訂單狀態流程、通知信、庫存與既有的重複付款保護皆不變動。
  • 沙盒實測:修正前交易停在 CREATED、無虛擬帳號;修正後 SDK 正常導向收銀台,交易進入 CUSTOMER_ACTION 並完成配號。

補充實測紀錄:曾評估以 confirm.autoConfirm = true 讓 SHOPLINE 伺服器端直接配號、擺脫瀏覽器依賴。沙盒實測結果為參數被靜默忽略(送出 true,交易查詢回傳 autoConfirm 為 false、狀態仍 CREATED、無虛擬帳號),故未採用。同批實測反向證實虛擬帳號確實由 payment.pay() 觸發。

3.6.8 - 2026-08-16

加密退款查詢節奏,並提供後台「立即查詢退款結果」按鈕。

  • 退款自動查詢節奏由 30 秒/2/10 分鐘/1/6 小時 改為 30 秒/1/3/5/10 分鐘/1/6 小時,前段更密集,絕大多數退款可在商家仍停留於後台時就完成確認。
  • 訂單付款資訊區塊在退款確認中時,會顯示狀態說明(已送出 API、尚未收到確認結果、退款參考編號、最後回報狀態)與「立即查詢退款結果」按鈕;查詢採 AJAX,確認成功或失敗後自動重新載入頁面,讓訂單狀態、退款單與金額一次反映到 WooCommerce 原生畫面。
  • 手動查詢不會推進自動排程階段,商家多按幾次也不會縮短系統自動確認的總時窗;查詢期間以訂單鎖序列化,不會與排程或 webhook 互相干擾。
  • 每小時同步新增退款安全網:以「有進行中退款」為條件挑單,補救 Action Scheduler 失效或事件遺失的情況(退款訂單多為處理中/已完成,不在原本的付款掃描範圍內)。
  • 在途訂單備註改為明確說明「退款請求已送出 API,但尚未收到確認結果」,並列出自動查詢節奏與手動查詢位置。

3.6.7 - 2026-08-16

退款改為以 SHOPLINE 最終狀態收斂,避免 WooCommerce 提前顯示已退款。

  • SHOPLINE 建立退款回 HTTP 200 後不再直接視為完成;新增 SUCCEEDED/in-flight/failed/unknown 分類與退款查詢 API,只有明確 SUCCEEDED 才保留或補建 WooCommerce 退款。
  • 退款仍在處理或結果未知時,讓 WooCommerce 刪除暫存退款,同時保存金額、原因、品項退款、稅額與回補庫存決策;以固定 reference、idempotency key、訂單鎖與 30 秒/2 分鐘/10 分鐘/1 小時/6 小時排程持續確認。同步階段只做一次立即查詢,不會阻塞後台操作。
  • 排程或 webhook 確認成功後,使用 refund_payment => false 按原始快照只補建一次本地退款,不會再次呼叫 SHOPLINE;遠端成功但本地補登失敗時改列人工審核並保留稽核資料。
  • 中租 zingala 銀角零卡的部分退款會在 API 前以中文訊息擋下;信用卡部分退款維持支援。
  • 付款尚未完成結算的訂單改為在 API 前擋下並說明原因(SHOPLINE 對未結算交易只回通用的 1008 Status error);付款最終失敗的訂單會自動回到等待付款,不需要退款。
  • 退款 webhook 與查詢結果必須精確核對退款編號、參考編號、付款交易、金額及幣別。排程失敗、回應不符或逾時未決會在訂單頁與訂單列表顯示紅色人工審核提示,管理員結案時會保留歷程並解除 active attempt。

3.6.6 - 2026-08-15

修正非同步付款確認後未寄出商家「新訂單」通知。

  • SHOPLINE 信用卡走 3DS/非同步確認時,訂單狀態流為 pending → 付款確認中 → 處理中。WooCommerce 的「新訂單」通知只監聽 pending/failed/cancelled 轉入已付款狀態,因此由「付款確認中」轉入時不會觸發,商家收不到新訂單信(顧客信、發票、訂單資料皆正常)。
  • 將 付款確認中 → 處理中/已完成 接入 WooCommerce 原生 transactional email 流程,並以原生 WC_Email_New_Order 寄送,保留商家既有的啟用開關、收件人、主旨與模板設定;同時相容延後寄送(deferred email)。
  • 寄送前重新讀取訂單,必須同時具備付款日期與 SHOPLINE 已付款證據(付款狀態或確認歷程)才寄出;人工把未收款的確認中訂單改為處理中不會誤寄。
  • 以訂單層級 MySQL 鎖搭配 WooCommerce 原生 _new_order_email_sent 旗標防止 webhook、排程查詢與回站同時收斂造成重複寄送;寄送失敗最多重試三次,郵件停用或未設收件人則不重試、也不會日後自動補寄。
  • 不回補歷史訂單;付款狀態、庫存、發票、訂閱與顧客郵件行為皆未變更。

    3.6.5 - 2026-08-09

修正 3DS 非同步確認完成後,首購訂閱仍停在 pending。

  • WooCommerce Subscriptions 原生只把 pending/on-hold/failed 轉入付款完成狀態視為首次付款;母訂單經 ys-confirming → processing/completed 時因此未啟用關聯訂閱。現在透過 WCS 6.9+ 提供的 wcs_is_subscription_order_completed 相容 filter,僅在舊狀態為 ys-confirming、新狀態是 WooCommerce 付款完成狀態且父訂單已有 date_paid 時,交回 WCS 原生啟用流程。
  • 不把 ys-confirming 加入 woocommerce_valid_order_statuses_for_payment,確認期間仍禁止重新付款;也不直接呼叫訂閱 payment_complete(),保留 WCS 對 ended、cancelled、active 與續扣排程的原生保護。
  • enter_confirmation() 原有 renewal 排除維持不變,因此修正只涵蓋首購/母訂單,不改續扣交易流程。WooCommerce 將訂單人工改為 processing 本來就會寫入 date_paid 並視為已收款,本版維持該原生語意。
  • 新增 standalone 契約與 dev-checkout 真實 WCS/HPOS 探針,覆蓋舊行為重現、非同步啟用、下一次續扣排程、重送冪等、非付款目的狀態與管理員 processing 語意。

3.6.4 - 2026-07-27

修正 vendor Hub Client 重複宣告 HPOS 造成 WooCommerce error log 污染。

  • bundled Hub Client 升級至 2.0.5,移除 library 內以 vendor __FILE__ 呼叫 FeaturesUtil::declare_compatibility() 的無效宣告,避免 WooCommerce 將非外掛主檔判定為 Invalid plugin file。
  • HPOS 相容性仍由 SHOPLINE 主外掛以 YS_SHOPLINE_PLUGIN_FILE 正確宣告,付款、訂單與 HPOS 功能語意不變。
  • 保留 Hub Client 的資料表 activation hook 與 plugins_loaded schema fallback;本次不改資料庫初始化流程。

3.6.3 - 2026-07-25

恢復 SHOPLINE Payment 舊版獨立後台入口,並保留電商工具箱整合。

  • bundled Hub Client 升級至 2.0.4:中央註冊的 ys-toolbox 統一顯示為「電商工具箱」(dashicons-store、位置 56),晚期排序會固定將「系統資訊」「聯絡我們」放在最後,並沿用共用的 HTTPS-only Hub URL override(YS_CART_HUB_URL/ys_cart_hub_url,無效值回退正式站)。
  • 恢復 v2.4.4 以前的獨立頂層入口 admin.php?page=ys_shopline_payment(SHOPLINE 金流),並移至 WooCommerce 區段後、電商工具箱 上方;同時保留 v2.4.5 起的電商工具箱子選單 admin.php?page=ys-shopline-payment。
  • 共用 ys-toolbox 已由同站舊版 Hub Client 先註冊時,仍將其「YS Plugin」顯示名稱與頁面標題校正回「電商工具箱」;不改 slug、callback 或既有子選單。
  • 兩個 endpoint 共用同一個設定頁 callback、manage_options 權限與 option group;不複製設定、不改設定鍵,也不影響付款流程。
  • 舊版頂層 endpoint 重新載入原有 color picker 管理員資產,並新增可重跑管理員選單契約防止後續整合再次移除相容入口。
  • 設定頁既有粉紫品牌配色與版面維持不變;本版只調整入口位置及共用工具箱排序。

3.6.2 - 2026-07-20

修正錢包回站短暫顯示「等待付款」與跨 gateway customer ID 洩漏。

  • LINE Pay/Apple Pay 等非 ATM 付款完成導轉回商店後,若第一輪 SHOPLINE 查詢仍為 CREATED/CUSTOMER_ACTION,訂單改進入 wc-ys-confirming,感謝頁顯示中立的「付款確認中」並鎖定重付;paid webhook/排程查詢仍以 exact attempt 收斂,付款完成後自動轉為已付款狀態。初次 create 的 nextAction、顧客取消後既有 prior-trade resolver 與 ATM 離線待繳流程不變。
  • 修正共用 Regular fallback 未檢查 gateway 能力:會員只要有本地信用卡 token,就會讓 LINE Pay/Apple Pay/街口/ATM/中租請求誤帶 paymentCustomerId。現在 customerToken、customer identity 建立/查詢、綁卡/快捷路由及 paymentCustomerId 僅限信用卡、信用卡分期與信用卡訂閱;非卡 gateway 即使收到舊快取、第三方 filter 或異常 mode,也會在前後端最終輸出邊界固定回一般付款。
  • 對精確 1005 + Customer not found 增加安全自癒:只失效該請求實際使用且仍相符的 user customer mapping 與 instruments cache,避免晚到請求刪除並行重建的新 ID;customer-token 階段會重建一次並重取 token,create-trade 階段只清理後安全退回重試,不自動建立第二筆交易。其他 1005 不清資料,WC tokens 與訂閱 meta 保留供稽核。
  • 付款完成入口改共用 order-scoped completion lock,涵蓋 webhook、回站查詢、人工/排程同步、立即成功與訂閱續扣;以 date_paid 作為不可逆 paid history,避免晚到 PROCESSING 把 ATM 等已完成訂單降回 on-hold,也避免查詢與 webhook 交錯時重跑 WooCommerce 付款完成 hooks。完整退款仍可正常轉入 refunded。
  • LINE Pay/Apple Pay 的第一個自動補查維持 120 秒;這是原付款 attempt 的確認窗,不是 ATM 專屬鎖。確認中會在建立交易前一致阻擋全部替代付款方式(含 ATM),僅在 exact paid/terminal/customer-action 收斂後恢復既有安全切換流程。
  • 新增回站生命週期、五種非卡 gateway 四種 mode、信用卡相容路徑、stale customer 重建、generic 1005 負向與並行替換保護等可重跑契約。

3.6.1 - 2026-07-19

強化 Apple Pay/LINE Pay 確認鎖的即時收斂,並補齊建立交易 unknown 診斷。

  • 修補一個可確認的降害缺口:訂單已進入 indeterminate 確認鎖、但本地尚無 trade ID 時,v3.6.0 的 trade.customer_action webhook 只用 trade ID 找訂單,無法即時收斂,必須等待排程查詢。此缺口不是 ATM 專屬鎖;客戶案件最初如何進入 unknown 仍須以當時 SHOPLINE log 判定,不把尚未取得的證據寫成既定根因。
  • trade.customer_action 對確認中訂單新增 reference fallback,並交由付款確認服務做 exact-attempt 核對;referenceOrderId、trade ID、SHOPLINE payment method、金額與幣別完全相符時,立即清除確認鎖、保留 trade ID 並回到 pending,讓下一次付款進入既有 resolve_prior_trade() 的取消/重查/歸檔流程後安全切換 ATM。
  • 建立交易被分類為 unknown 時,單筆 WooCommerce ERROR log 現在包含 request ID、HTTP status、SHOPLINE error/payment message、response key 清單,以及有無 tradeOrderId/nextAction;只記結構與錯誤資訊,不記 paySession、token 或完整 response body。
  • 解鎖不等於無條件放行:prior-trade resolver 若查不到剛接管的交易,仍保留 trade 並 fail-closed,checkout 與 order-pay 顯示中立的稍後重試訊息,不建立第二筆交易。
  • mismatch、malformed、已有付款歷史或收斂鎖忙碌皆維持 fail-closed;沒有放寬 AUTHORIZED/PROCESSING/未知狀態的重付限制,也未修改 ATM、前端、訂閱或一般信用卡付款流程。
  • 新增可重跑契約,覆蓋 LINE Pay/Apple Pay exact CUSTOMER_ACTION、unknown 診斷資料、無本地 trade ID 的 reference fallback、金額不符維持確認鎖,以及解鎖後遠端重查失敗不得建立新交易。

3.6.0 - 2026-07-17

新增付款結果確認生命週期,將「尚未確認」與一般待付款/保留狀態明確分離。

  • 新增 WooCommerce 自訂狀態 wc-ys-confirming(後台/客戶顯示「付款確認中」)。信用卡、分期、Apple Pay、LINE Pay、街口與中租遇到 AUTHORIZED/PROCESSING/PENDING,或 create/query 回應不明時,會進入此狀態並鎖定重新付款;不視為已付款、不觸發出貨。
  • 付款 attempt 以精確 referenceOrderId、trade/session ID、SHOPLINE payment method、實送金額與幣別識別。排程查詢、每小時 safety-net、redirect 與 webhook 共用同一套嚴格收斂規則;mismatch、malformed、多筆 active 或未知狀態一律 fail-closed。
  • 信用卡/錢包於約 2m、5m、15m、30m、1h、3h、6h查詢;中租於約 5m、15m、1h、6h、24h查詢。時間經過只會觸發查詢,不會單憑逾時宣告失敗;最終仍不明則保留鎖定並進入後台「SLP審核」待辦。
  • 自動查詢達上限時,除後台紅色待辦、訂單 note 與 log 外,會依 reference 冪等寄送一封「需立即核對」管理員通知;只通知商店管理員,不向顧客宣告成功或失敗。
  • 明確已付款結果在訂單級 MySQL named lock 內執行一次 payment_complete();明確 FAILED/EXPIRED/CANCELLED 才退回 pending、依 WooCommerce 標準旗標釋放庫存並重新開放付款。客戶與商店管理員各收一封中立通知,依 reference 冪等,不宣稱「未扣款」。
  • 終態退回後,感謝頁、我的訂單與 order-pay 付款表單均顯示「付款確認未完成」中立提示;order-pay 直接使用原生付款按鈕,不重複插入第二個付款命令。
  • unknown 後若查得 CREATED/CUSTOMER_ACTION,會回到既有 prior-trade resolver:保留 trade ID,下一次付款先取消/重查/棄用,維持 Apple Pay/LINE Pay 關閉後換方式的安全流程;ATM 已取得虛擬帳號的離線待繳流程不變。
  • 已有 date_paid 的訂單不會被晚到 AUTHORIZED/PROCESSING/CUSTOMER_ACTION/FAILED/CANCELLED/EXPIRED webhook 改回未付款或釋放庫存;若競態造成已付款訂單停在確認中,會清除不一致鎖並恢復 paid 狀態,不重跑付款完成 hook。
  • WooCommerce Subscriptions 續扣維持 v3.5.36 的獨立 fail-closed 流程;新增 attempt/confirmation meta 全數排除 renewal 複製,避免上一期鎖定資料污染下一期。
  • 新增 RD、實作計畫與版控測試:狀態分類、排程、HPOS CRUD、strict session/webhook、customer-pending 收斂、通知冪等、paid-history、convergence lock、續扣 meta 隔離、後台人工審核可見性及付款關鍵機制靜態回歸哨兵。

3.5.39 - 2026-07-17

修正會員信用卡分期的新卡/存卡/既有卡三條付款路徑。

  • v3.5.38 依文件假設 customerToken 足以讓分期 SDK 顯示既有卡;測試站現行 SHOPLINE SDK 實測不成立,必須同時提供 paymentInstrument.bindCard.enable=true 才會渲染既有卡列表。因此分期改為顯示可選的儲存開關,且 defaultSwitchStatus=false、mustAccept=false,新卡預設仍不儲存。
  • 前端在 createPayment() 之前保存付款工具選擇,避免 SDK 建立 paySession 時替換 DOM,導致既有卡在 checkout/order-pay 被誤判成新卡。SDK 若明確回傳 paymentInstrumentId,仍以 SDK 回傳為最高優先。
  • SHOPLINE 現行儲存開關是 CSS-module 自製控制項,沒有原生 checkbox;移除「看到儲存文字就視為已勾選」的錯誤 fallback,改為僅認 checkbox_* 內的 active_* 狀態。未勾選不再誤送 CardBindPayment。
  • 分期後端三態固定為:new → Regular、new_save → CardBindPayment、saved → QuickPayment;一般信用卡與訂閱路由不變。
  • 新增/擴充 PHP 與 Node 契約,鎖定 SDK options、SDK 自製開關、DOM snapshot、checkout/order-pay 共用選卡與後端三態路由。

測試站 sandbox 實機驗證:分期 saved/new/new_save 三路、主信用卡 saved、order-pay saved、零元試用初始訂閱與 Recurring renewal 均成功;另重跑 HPOS 庫存 failed 回補、冪等、已付款 guard 與非 SHOPLINE scope。

3.5.38 - 2026-07-17

修正會員信用卡分期「新卡不儲存」必然失敗(User authorization verification failed)。

  • 根因:分期 SDK 對登入會員啟用 paymentInstrument.bindCard,但顧客選「新卡不儲存」時後端送 paymentBehavior=Regular → SDK/API 不一致,SHOPLINE 拒絕(客戶站 #1933、#1934、#1936、#1937、#1938、#1942 六筆異常訂單共同條件)。此組合自 v3.5.13 起即存在。
  • 修正=分期回歸 SHOPLINE 官方「一般付款」定位(官方規格:分期不顯示儲存卡片選項;會員既有卡列表僅需 customerToken):
    • 前端能力分離(覆核修正):GATEWAY_CONFIG 拆為 supportsSavedCards(可顯示既有卡)與 supportsBindCard(可綁新卡)——分期=supportsSavedCards:true+supportsBindCard:false。SDK options 組裝抽出為 buildSdkOptions() 並設最終能力閘門:只有 supportsBindCard 的 gateway 允許 paymentInstrument.bindCard;後端設定、前端預設重建及 sdkOptions deep merge 任一來源誤傳,皆於最終 options 剝除。customerToken 照常傳遞供既有卡列表。
    • 既有卡偵測改依 supportsSavedCards(不再依 bindCard 狀態)——分期會員選既有卡仍正確產生 mode=saved → QuickPayment;isSaveCardRequested 對不支援綁卡的 gateway 一律 false(分期永不產生 new_save)。移除已無消費者的 isBindCardEnabled() 與容器 bind-card-enabled 旗標。
    • 後端:分期 SDK 設定移除 paymentInstrument.bindCard 與 forceSaveCard、保留 customerToken;分期路由改為 既有卡(saved)→ QuickPayment、其餘(new/new_save/未帶 mode)一律 Regular。升級後須清除頁面/CDN/最佳化外掛的舊 JS 快取,確保瀏覽器載入 v3.5.38 能力分離版本。
  • 一般信用卡與訂閱不變更(一般信用卡顧客仍可自選是否儲存新卡、訂閱維持 CardBind 流程),僅納入回歸測試。
  • 新增版控契約測試:PHP(分期 SDK 設定於會員/訪客/bind-only 情境一律不含 paymentInstrument/forceSaveCard;直接執行真實 prepare_payment_data() 鎖定分期 new/new_save/缺 mode → Regular、saved → QuickPayment,並覆蓋主卡/訂閱回歸)+ Node 前端契約 node tests/js/checkout-contract.test.js(載入真實前端檔驗證:最終 SDK options、前端實際產生的 mode——含 checkout 與 order-pay 共用呼叫點、主卡/訂閱回歸)。

修正獨立安裝(無第三方回補外掛)時,付款失敗訂單庫存永久卡住。

  • WooCommerce core 只在 cancelled/pending 狀態轉換掛庫存回補(wc_maybe_increase_stock_levels),failed 不掛;但本外掛自身會把訂單轉為 failed(redirect 失敗、webhook trade.failed/trade.expired、每小時狀態同步、訂閱續扣失敗、後台手動)。v3.5.37 修正旗標後,獨立安裝下 failed 訂單的預扣庫存仍無人回補。
  • 於 YSStatusManager::handle_order_status_change(既有 woocommerce_order_status_changed 監聽、既有 is_shopline_order() 把關)新增:未實收訂單轉入 failed 時呼叫 wc_maybe_increase_stock_levels()。單一掛點統一涵蓋所有 failed 轉換路徑;僅限 SHOPLINE 金流訂單,不全域掛 hook、不影響其他金流;旗標把關維持冪等(未扣過=no-op、第三方已回補=no-op、失敗後重新付款會由標準入口重新扣庫存)。
  • 已付款 guard(覆核修正):已實收訂單(date_paid 非空,例如 processing 被後台手動轉 failed)不回補——款項仍在而庫存放出=超賣風險,回補應由退款/取消流程決定(轉 cancelled 時仍由 WooCommerce core 標準回補)。判斷用 date_paid 而非 is_paid()(訂單已轉 failed 時後者恆為 false)。
  • 新增版控契約測試:未付款 failed(pending/on-hold/自訂狀態)觸發回補、已付款 failed 不回補(不以 old_status 判斷)、非 SHOPLINE/空 method/非 failed 不觸發。

3.5.37 - 2026-07-16

修正付款未完成時訂單庫存旗標缺失,導致失敗/取消訂單無法回補庫存。

  • 信用卡 3DS/其他 nextAction 付款及 ATM 虛擬帳號改用 WooCommerce 標準 wc_maybe_reduce_stock_levels(),扣庫存時同步建立訂單層 _order_stock_reduced 旗標。
  • 付款失敗或取消時,WooCommerce/第三方結帳強化可依標準旗標執行 wc_maybe_increase_stock_levels();既有 on-hold 與成功付款路徑維持冪等,不會重複扣庫存。
  • 新增版控契約測試,鎖定 SHOPLINE 兩條 nextAction 路徑不得再直接呼叫 wc_reduce_stock_levels()。

3.5.36 - 2026-07-15

小範圍風險修正(承 v3.5.35/v3.5.36 多輪 CODEX/Claude 覆核)

  • 🔴 API 錯誤三態+不明付款封鎖(防雙扣):process_payment() 遇 API 錯誤不再一律當「未受理」。新增 Utils\YSApiError 分類器:僅「明確 rejected allowlist」(建立前參數/驗簽/權限/設定錯誤,或銀行拒絕、卡片失效、定期付款無法完成等明確終止結果)視為 rejected,其餘(timeout/空回應/JSON 解析失敗,以及 1001 交易已存在/4003 通路 timeout/4458 processing/1018 未知原因等業務碼)一律 unknown。所有 return 改帶 remote_outcome(accepted/rejected/unknown,消費端完整 switch、未知值 fail-closed)取代原 bool accepted。unknown 時標記訂單 _ys_shopline_indeterminate_ref 並在主結帳與 order-pay 產生新 referenceOrderId/冪等鍵前封鎖再建交易,避免「遠端已建交易但我方未收到→下次用新鍵建第二筆」的雙扣。
  • 不明付款收斂(全金流):webhook trade.succeeded/captured 新增 exact-indeterminate-reference 路徑——以精確 referenceOrderId 命中封鎖訂單、核對 gateway+金額+幣別後接管交易入帳(不再僅限 ATM reference fallback);trade.failed/expired 以 exact reference 解除封鎖、允許重試。另加 query_session 正向接管:查得 paymentDetails.tradeOrderId 才接管,查不到不放行(官方將 paymentDetails 定義為選填)。
  • 重新付款失敗回復語意:依 remote_outcome 完整分流——rejected 才完整還原付款方式 ID+title(含清空、含同 ID 不同 title)並以頁內 failure 呈現原因;unknown 不還原(可能已建交易,還原會孤兒化)改顯示「確認中、勿重複付款」;accepted 原樣回傳。
  • 訂閱首扣 meta 僅在明確受理時寫:YSCreditSubscription 改以 remote_outcome === 'accepted' 為條件(不再用 result==='success'/accepted!==false,避免 rejected 的 success+redirect 或 unknown 誤寫綁卡 meta);order-not-found 分支補三態。
  • 同末四碼自動選卡點錯風險:卡片唯一性改依 distinct SHOPLINE instrument ID($token->get_token())而非 WC token 筆數;PHP 僅在唯一時送 default_card_last4。前端 _tryAutoSelectDefaultCard 再加一層:只在 SDK 實際渲染出恰好一個 last4 相符 item 時才自動點選(本地 token 不等於 SDK 渲染),撞號則不點、改手選。
  • abandoned-paid 三分類+backfill+可結案:guard_abandoned_trade_paid 依 already_paid 三分——duplicate_paid(現行已付款=重複收款→退舊)/abandoned_paid_current_exists(棄用實收、現行另有交易,其狀態未查證、要求人工核對,不宣稱待付款,舊交易才是實收→勿盲退)/paid_no_current_trade(棄用唯一收款、未入帳);冪等改「本筆已建 review data 才跳過」,使 v3.5.35 僅寫 paid-list 的舊事件(如 #11824)能 backfill 補上旗標;持久化 _ys_shopline_manual_review 後台可追蹤(訂單列表「SLP審核」欄🔴/✔+編輯頁告警),「訂單動作→標記已結案」(_ys_shopline_manual_review_resolved);一律不自動 payment_complete。
  • 覆核二輪 P0 加固:① 成功回應缺 tradeOrderId(契約必填)一律 unknown+標記封鎖(nextAction/CREATED 亦然),杜絕「無 marker、無 trade ID→下次遞增 reference 建第二筆」的繞過。② query_session 正向接管改核對 session root(referenceId+amount+幣別)並只接管 paymentDetails 中「已收款」那筆(不選第一筆,杜絕 [FAILED old, SUCCEEDED new] 選到 old_failed 後誤放行;paymentDetails 元素僅含 tradeOrderId/status/paymentMethod,reference/amount 在 root)。③ 移除「任何 TRADE_ORDER_ID 即清 marker」的泛化(舊失敗交易 meta 不再誤清;解除封鎖僅靠 is_paid 或 webhook exact convergence)。④ webhook 接管前的方式核對改比對 marker 存的期望 SHOPLINE payment method vs webhook payload payment.paymentMethod(非本地 WC gateway)。
  • 覆核三輪:接管全面 fail-closed(杜絕 fail-open 漏接管):① query_session 正向接管改為 session root 必須完整且完全相符(referenceId+amount+幣別任一缺失或不符即不接管,取代「有值才比、缺值放行」);paid detail 必須 paymentMethod 與期望 SHOPLINE method 相符(官方列為必填,缺失視為不符);依 distinct tradeOrderId 去重後恰好一筆已收款才接管,零筆或多筆(雙付款異常)一律維持封鎖。② webhook 接管:payload 缺 payment.paymentMethod 視為 malformed → 拒絕(不再僅憑 reference/金額/幣別就清 marker 入帳)。③ 抽出單一共用嚴格 selector Utils\YSTradeStatus::select_representative_trade_id()(唯一已收款 > 唯一顧客處理中 > 留空),YSSessionDTO::from_response 狀態同步不再盲取 paymentDetails[0](同修 [FAILED old, SUCCEEDED new] 誤選舊失敗筆),resolver 與狀態同步共用同一規則、不再各行其是。
  • 覆核四輪:selector 納入在途/未知+marker 保存實送 envelope:① 共用 selector 改為 忽略 terminal-safe、其餘可辨識狀態(已收款/顧客處理中/在途 PROCESSING·AUTHORIZED·PENDING)皆算 active、未知或空狀態一律 fail-closed 回空、去重後恰好一筆 active 才回——修正原本只看「已收款/顧客處理中」而忽略在途與未知交易,導致每小時狀態同步(YSSessionDTO→YSStatusManager)誤寫較低風險或已付款交易、遮蔽另一筆仍可能收款的交易(後續重付或第二筆 capture 形成未告警的重複收款)。單筆在途會被回傳供狀態同步追蹤,接管入帳仍由 is_paid() 把關。② indeterminate marker 改保存「實際送給 SHOPLINE 的 request envelope」 — mark_indeterminate() 接收 $payment_data,記錄實送 referenceOrderId/amount.value/currency/confirm.paymentMethod,不再用 Woo 訂單總額與 gateway method 重算。修正零元訂閱回歸:CardBind 實送 amount=10100 但 marker 記 0,導致 unknown 後 query_session/paid webhook 金額核對永遠失敗、訂單持續鎖定。
  • 覆核五輪:selector 對 malformed 明細 fail-closed+DTO root 不得繞過:① 共用 selector 對 malformed 明細(非陣列/缺 tradeOrderId/缺 status)一律回空,唯一允許忽略(continue)的是「欄位完整且 terminal-safe」——杜絕「另一筆無法識別但仍在途的交易」被靜默略過後、resolver 誤接管較低風險那筆並清 marker([SUCCEEDED paid-a, PROCESSING 缺 tradeOrderId] 原會接管 paid-a)。② YSSessionDTO::from_response 一律經共用 selector 從 paymentDetails 產生交易 ID,root tradeOrderId 不再優先繞過安全判斷——原本「root 有值就用、root 空才跑 selector」會讓多筆 active/malformed 時用 root 遮蔽 selector 的 fail-closed,把較低風險或錯的那筆寫進訂單 meta。
  • 排程續扣納入同一三態防雙扣契約:process_subscription_payment() 不再把所有 WP_Error 當成可換卡重試;只有明確 rejected 才能從訂閱綁定卡 fallback 到使用者預設卡。timeout/空回應/解析失敗、成功樣回應缺 tradeOrderId、未知狀態一律保存實送 reference/金額/幣別/方式為 indeterminate,停止第二張卡與後續新 reference;PROCESSING/PENDING/AUTHORIZED 等在途狀態保留交易並轉 on-hold。官方明列的銀行拒絕與定期付款 4900~4902 納入明確 rejected,避免正常拒絕被誤鎖。
  • 續扣前次交易與跨期 meta 隔離:每次排程扣款在建立新交易前先收斂 indeterminate 與本期既存 trade;已收款完成訂單、終態才放行重試、在途/未知/查詢失敗均阻擋。依 WooCommerce Subscriptions 的 opt-out 複製機制,新增 wcs_renewal_order_meta_query 排除交易 ID、reference、狀態、review、indeterminate 等 order-scoped meta,只保留 subscription-scoped customer/instrument;並以 {renewal_order_id}_ reference 前綴辨識及清除既有跨期殘留。
  • query_session 正向收斂完成入帳:嚴格核對後確認唯一已收款交易時,除寫入 trade/status 與清 marker,也立即呼叫 payment_complete();排程續扣不再依賴不存在的前端 redirect handler 才完成訂單。
  • 版控契約測試:新增 standalone selector/DTO/API 三態/訂閱續扣整合測試,固定命令 php tests/run.php;測試涵蓋 timeout 與業務碼 1001/4003 unknown 不 fallback、缺 trade ID 封鎖、4450/4900 明確拒絕才換卡、同卡不重試、90011_ 不誤判為訂單 9001_ reference 前綴、退款/空白/未知前次狀態、既存 in-flight/indeterminate pre-create guard、跨期 meta 排除。tests/ 由 .gitattributes export-ignore 排除,不進 release zip。

3.5.35 - 2026-07-14

修正 order-pay(重新付款)頁取消 Apple Pay/3DS/授權視窗後無法換其他付款方式

故障模式(客戶站實錄):顧客在重新付款頁按下付款後關閉 Apple Pay/3DS 視窗,交易停在 CREATED/CUSTOMER_ACTION(官方文件:未付款交易可能長達 6 小時才 EXPIRED);既存交易保護只放行 FAILED/EXPIRED/CANCELLED,導致切換任何付款方式都被擋,且錯誤原因滯留 session、前端只看到通用錯誤。另外切換付款方式後 SDK 掛載未完成時按付款會直接 alert 斷頭。

  • 前次交易統一解決(resolve_prior_trade):CREATED/CUSTOMER_ACTION(顧客端未完成=無授權、無款項在途)先 best-effort 取消(VA 跳過,實測不支援)→一律重查取權威狀態,依重查結果嚴格分類:PAID→導付款結果頁;TERMINAL→乾淨放行;仍 CREATED/CUSTOMER_ACTION→棄用舊交易放行(歸檔 _ys_shopline_abandoned_trade_ids+⚠️ note);PROCESSING/AUTHORIZED/未知/重查失敗一律 blocked,不棄用、不改任何 meta(Review P1-1 fail-closed,杜絕款項在途時誤放行造成重複扣款)。實測 SHOPLINE 取消 API 僅支援已授權交易,對顧客未完成家族一律拒絕(VA「not support cancel」、LinePay「can not cancel」),故此家族靠「重查仍未完成」判定棄用。主結帳同單重試共用同一邏輯(check_prior_trade_status 改為轉接層),一併解決主結帳「關視窗後卡既存交易至過期(最長 6 小時)」死鎖。
  • 歸檔交易 webhook 防護(Review P1-2):被棄用的舊交易若顧客事後仍完成付款,其 paid/captured webhook(trade.succeeded/trade.captured)在入口即以 referenceOrderId 反推訂單、比對歸檔清單攔截:不觸發 payment_complete、不覆寫現行交易 meta,改記 🔴 重複收款警示 note+ERROR log、將該筆記入 _ys_shopline_abandoned_trade_paid_ids 供人工退款。修正歸檔清單原本 write-only(舊 ATM 事後入帳覆寫現行交易、舊卡/LinePay 事後成功被靜默丟棄)。
  • P0 付款方式延後提交+失敗回復:ajax_pay_for_order 過去先儲存新付款方式再判斷舊交易,失敗的變更嘗試會污染 payment method,進而把其他金流的有效交易誤當 ATM 清掉(產生兩筆可付款交易=重複收款風險)。改為前次交易解決成功後才改寫付款方式,付款未成立即回復原方式。
  • 移除 ATM 盲目清理出口:改由 resolve_prior_trade 統一取消/清理;ATM 同方式重用前以遠端實況驗證交易確為 VirtualAccount 且待顧客付款(verify_reusable_offline_trade),本地 meta 不再單獨信任。
  • 訂單級付款互斥:ajax_pay_for_order 以 MySQL GET_LOCK(每訂單一鎖)序列化 cancel→query→create 鏈,雙分頁/連點併發只允許一條,其餘回覆「另一筆付款請求正在處理中」。
  • order-pay 等待 SDK 掛載:付款當下改以 isMountHealthy(gatewayId) 判定(instance 存在+容器 iframe/內容健在,Review P2:instance 在但 iframe 已被結帳更新替換脫離 DOM 時也算不健康),不健康則輪詢等待掛載完成後自動續行(共用 DEFER 常數;5 秒未健康 forceRemount 自癒一次、20 秒硬逾時才報錯;等待期間鎖定付款鈕、切換金流即中止等待)。
  • 信用卡已存卡快速結帳健康判斷(Review P1-3):isMountHealthy() 對 CreditCard 原本只認 PCI iframe,但會員已存卡情境 SDK 渲染的是 saved-card 列表(class 含 shoplinepayments_item_,無 iframe)→ 被誤判不健康 → 主結帳 placeOrder 走 isCheckoutBusy→deferPlaceOrder→ 5 秒 forceRemount → 紅字「付款元件重新載入中」,等於擋掉已存卡快速結帳(wecoware 客戶站實錯)。改為「iframe 或 saved-card 列表任一存在」皆健康,只有容器空/只剩 loading spinner/DOM 被替換才 defer/remount。此判斷三處共用(onUpdatedCheckout/doMount/isCheckoutBusy+order-pay),一併修好主結帳與 order-pay。
  • 錯誤訊息透傳:ajax_pay_for_order 失敗時把 WC session error notices 收攏為純文字 messages 回傳並清空(原因不再滯留 session);前端 order-pay 全面以頁內 WooCommerce notice 取代 alert()(自動 sanitize+escape,支援多行)。
  • 共用狀態分類器:新增 Utils\YSTradeStatus(TERMINAL_SAFE/PAID_RISK/CUSTOMER_PENDING),YSStatusManager 常數改為別名,取消閉環與前次交易解決共用單一事實來源。

3.5.34 - 2026-07-11

修正快速付款(自動填入)在結帳更新期間送單導致 3DS 未啟動、交易卡在 CREATED

正式站故障模式:自動填入觸發的 WooCommerce 結帳更新(update_checkout)在使用者按下單前後落地,替換付款區 DOM 並換掉 SDK instance,導致後端已建立 SHOPLINE 交易、前端卻無法執行 payment.pay(nextAction)(3DS 永不啟動),交易停在 CREATED 直到約 1 小時後 EXPIRED;期間同單重試被既存交易保護阻擋。

  • 結帳更新沉澱 gate:placeOrder 入口以實體狀態(更新事件在途 / wc-ajax 請求在途 / SDK 掛載健康)判斷可否起跑;未就緒則鎖定下單按鈕並自動等待沉澱(最多 5 秒)後續行,把「等幾秒再按就不會錯」自動化。
  • 掛載健康判斷分金流家族(isMountHealthy,三處共用):只有卡片家族(信用卡/分期/訂閱,paymentMethod=CreditCard)的 SDK 渲染 PCI iframe,健康=iframe 存在;ATM / LINE Pay / Apple Pay / JKO / BNPL 只渲染 DIV/按鈕,健康=instance 存在+容器在 DOM+SDK 已渲染內容——對非卡金流要求 iframe 會讓 gate 永遠 busy 而完全無法下單(首版實測 regression,已修並回歸各金流)。
  • 逾時=fail-closed 檢查點(非開閘):等待到 5 秒檢查點時,仍有實體在途證據(wc-ajax XHR 在飛 / 更新事件在 grace 期內)=慢而未壞 → 不清狀態、不放行,續等真正的完成信號(20 秒硬上限後停止並提示重整,不自動送單);只有「無任何在途證據且掛載本體壞死」才走 forceRemount(中止在途請求、狀態機歸零後重掛,脫離 state 卡在 loading 的死局)。
  • wc-ajax 在途以真實 jqXHR 集合追蹤:折價券、第三方加購/移除等 cart 突變只在自己的 AJAX 成功後才觸發 update_checkout,事件 flag 看不到前置請求;以全域 ajaxSend/ajaxComplete 維護在途 jqXHR 集合並以 readyState 過濾自癒(不用會漂移、需逾時清零的裸計數器)。
  • 提交期間凍結生命週期:updated_checkout 於提交中不再清除 instance、不重掛、不解除提交鎖(解鎖只留 checkout_error);被略過的更新在失敗解鎖後補做重掛。
  • nextAction 沿用原 instance:建立 paySession 的 SDK instance 以參數一路傳遞至 processNextAction(主結帳 + pay-for-order 皆改),不再於回應後從全域 map 重查。
  • instance 遺失時導向訂單付款頁:訂單與交易已建立卻無 instance 可用時,導向 pay-for-order 頁讓訂單可見並重掛重試(原本僅顯示「請重新整理」,訂單隱形)。
  • 腳本單例守衛(防衛性):結帳頁腳本被外部區塊重繪機制重複執行時,只允許第一份存活,避免多套 closure 互搶掛載。
  • 取消失敗改以遠端實況分級:本地 meta 已終態時跳過 cancel API;取消失敗(1008 僅為通用 Status error,另有 6001/6002/6401 等)一律查詢交易實際狀態再定級——遠端已終態=warning 無風險;遠端已收款=ERROR+「立即退款」備註;遠端進行中=ERROR+後台核對備註;查詢失敗=ERROR+無法確認風險備註。
  • 取消結果追蹤閉環:取消 API 回 200 但狀態非終態(官方:可能 PROCESSING)或取消失敗但遠端仍進行中時,排程單次 followup(2 分鐘後首查、之後每 5 分鐘、最多 5 次)主動確認最終結果——已取消訂單不在 pending/on-hold 的 cron 掃描範圍,沒有這條路徑付款狀態會永久停住;trade.cancelled webhook 對已取消訂單也改為補寫付款狀態 meta(不動訂單狀態),雙路收斂。
  • 下單按鈕共用鎖(window 級引用計數):defer 與 YS 結帳優化外掛共用 window.__ysPlaceOrderLocks 引用計數——「各自記取得前狀態」在雙持有情境仍會互解(A 持鎖期間 B 取鎖,A 先釋放會 enable 掉 B 的鎖),改為 count 歸零才考慮 enable、尊重首位取鎖時的外部 disable;resetAllSubmitLocks() 對 #place_order 僅在無人持鎖時 enable。
  • 取消追蹤斷鏈補齊:取消失敗且首查也失敗時同樣排程 followup(不再寫備註即斷);followup 執行時 API 暫時不可用改為重排下一次;wp_schedule_single_event 失敗留 ERROR。
  • 取消終態集合補齊:安全終態納入 REFUNDED(款項已退回);已收款風險納入 PARTIALLY_REFUND(部分退款=仍有款項在店家側),避免重試耗盡後誤報。
  • 舊 ATM 入帳 fallback 核對:referenceOrderId fallback 命中後,入帳前核對 gateway 歸屬+paymentMethod === VirtualAccount(此 fallback 僅服務舊 ATM 帳號入帳)+金額+幣別;金額讀取採官方 trade.succeeded 電文欄位 payment.paidAmount(備援 order.amount),未帶金額或幣別、或任一不符即拒絕自動入帳(fail-closed,留 ERROR 與訂單備註)——官方格式 7 案例測試矩陣全數通過。
  • 取消流程終態集合全面套用:本地跳過檢查(cancel 前)與取消 API 即時回應分類與 followup 使用同一組 TRADE_TERMINAL_SAFE / TRADE_PAID_RISK 集合——已退款交易不再呼叫取消 API;即時回應為安全終態直接收斂、為已收款立即示警,不再延後兩分鐘等 followup 分類。

3.5.33 - 2026-07-01

修正「已授權待確認」感謝頁標題寫死為分期,未跟隨付款方式

  • 一次付清信用卡訂單在 hitrust 3DS 中間態時,感謝頁綠色標章原本一律顯示「分期付款授權成功,等待銀行確認」,即使實際為一次付清。
  • YSOrderDisplay::render_authorized_pending_notice 改為依 WC gateway 動態選標題:
    • 信用卡分期 / 中租零卡 → 「分期付款授權成功,等待銀行確認」
    • 一次付清信用卡 / 訂閱 → 「信用卡授權成功,等待銀行確認」
    • 其餘 → 「付款授權成功,等待銀行確認」

3.5.32 - 2026-06-23

修正 SDK initData.paymentInstrument 參數異常 (4208):移除 textType

  • v3.5.30 / v3.5.31 在 bindCard.protocol 內新增的 textType,在 switchVisible:true(顯示勾選框)+ 有 customerToken 的情境下會觸發 SDK 錯誤 4208「initData.paymentInstrument 參數異常」,導致信用卡結帳無法載入。
  • 三處(訂閱 / 一般信用卡 / bind-only)皆移除 textType。原始可正常綁卡的設定本來就沒有 textType,移除不影響綁卡。
  • 保留關鍵修法:bindCard.enable:true(訂閱 / bind-only 強制;一般信用卡對登入用戶開放)與 mustAccept:false。

3.5.31 - 2026-06-23

一般信用卡:儲存卡片選項對所有登入用戶開放(與 customerToken 解耦)

  • 問題:一般信用卡 gateway 的「儲存卡片」勾選框原本綁在 customerToken 是否存在。首次顧客若 customer/token API 尚未成功,看不到勾選框 → 無法儲存卡片。
  • YSCreditCard::get_sdk_config:閘門改為「是否登入」(get_current_user_id())。登入用戶一律顯示勾選框(switchVisible:true、預設不勾、用戶自選),新增 textType。
  • customerToken 仍照舊用於顯示既有卡列表;「能否儲存新卡」僅取決於是否登入。
  • 訪客(未登入)不送 paymentInstrument → 不顯示勾選框,無法儲存卡片(維持原樣)。

3.5.30 - 2026-06-23

修正訂閱 / 綁卡的 SDK bindCard.enable,讓 SHOPLINE 真正建立 paymentInstrument

  • 根因:SLP 客服確認,SHOPLINE 以 SDK 的 paymentInstrument.bindCard.enable 為準;即使 Server API 帶 savePaymentInstrument:true,若 SDK enable=false 仍不綁卡並回 savePaymentInstrument:false。
  • 訂閱 gateway 原本 enable 綁在「是否有 customerToken」,導致首次訂閱顧客(無既有 token) 被送 enable=false → 卡片永遠綁不上 → 訂閱無法續扣。
  • YSCreditSubscription::get_sdk_config:bindCard.enable 改為一律 true(與 customerToken 解耦),mustAccept 改 false,新增 textType。
  • YSGatewayBase::get_sdk_config(新增付款方式 / 訂閱變更付款 / $0 試用 bind-only):mustAccept 改 false,新增 textType(enable 本就為 true)。
  • 一般信用卡(非訂閱)儲存卡片維持不變:仍由顧客自行勾選(switchVisible:true、預設關閉)。

3.5.27 - 2026-05-21

Guard admin phone notice from checkout filters

  • Stop the SHOPLINE settings page from calling WC()->checkout()->get_checkout_fields() while rendering the phone notice.
  • Read billing phone availability from WooCommerce address fields instead, avoiding fragile woocommerce_checkout_fields callbacks in wp-admin.
  • Add a regression test that simulates a third-party checkout field filter failure and confirms the settings notice still renders.

3.5.26 - 2026-05-19

Unify SHOPLINE admin box spacing

  • Remove the postbox content padding from both SHOPLINE admin payment boxes.
  • Use the same no-inner-frame table styling for order payment info and subscription card binding.
  • Remove the duplicate inline order payment heading so both boxes use only the WooCommerce postbox title.

3.5.25 - 2026-05-19

Unify subscription card box styling

  • Remove the extra inner widefat border from the subscription-bound card table.
  • Keep the subscription-bound card table visually aligned with WooCommerce admin postbox tables.

3.5.24 - 2026-05-19

Move subscription card binding to its own admin box

  • Render the subscription-bound card table as an independent WooCommerce admin meta box.
  • Keep the subscription card box on subscription edit screens only, between the order data area and order items.
  • Keep regular order payment summaries out of subscription edit screens.

3.5.23 - 2026-05-19

Minimize card BIN storage and logs

  • Redact card BIN/IIN fields such as bin, cardBin, and issuerBin in SHOPLINE API debug logs and general logger context.
  • Remove card BIN/IIN fields from stored _ys_shopline_payment_detail snapshots before saving order meta.
  • Sanitize legacy payment detail snapshots on read so admin/frontend displays never consume BIN fields.
  • Keep card display limited to last4 and keep recurring payments using paymentInstrumentId.

3.5.22 - 2026-05-19

Minimize admin order payment panels

  • Move the SHOPLINE order payment summary into a low-priority order meta box so it appears below the order items section.
  • Reduce the order payment summary to payment method, payment status, and payment identifier only.
  • Keep customer saved-card lists only on wp-admin > Users > Edit User.
  • Render subscription-bound card details only on subscription edit screens, below order data and above order items.
  • Keep the admin order render path local-only; it does not call the SHOPLINE API or force card sync.

3.5.21 - 2026-05-19

Restore admin order payment panel

  • Add a SHOPLINE payment meta box to WooCommerce order and subscription edit screens, including HPOS screens.
  • Show the customer's local saved cards, default/expired status, and masked SHOPLINE instrument IDs.
  • Show subscription card bindings, next payment date, and whether the bound card still exists locally.
  • Show order payment details for SHOPLINE credit card, subscription, ATM, JKOPay, Apple Pay, LINE Pay, and Chailease BNPL records from local order meta.
  • Keep the order edit render path local-only; it does not call the SHOPLINE API or force card sync.
  • Add tests/admin-order-payment-box-static.php to lock the order/subscription admin panel.

3.5.20 - 2026-05-19

Add admin user card overview

  • Add a SHOPLINE payment section to wp-admin > Users > Edit User.
  • Show local WooCommerce saved cards, default/expired status, token IDs, and masked SHOPLINE instrument IDs.
  • Show SHOPLINE WooCommerce Subscriptions bindings and flag subscriptions whose instrument ID has no local saved card.
  • Show recent local SHOPLINE payment records for the user.
  • Add a manual admin-only card sync button; opening the user edit page reads local data only and does not call the SHOPLINE API.
  • Add tests/admin-user-cards-static.php to lock the admin display, permission, nonce, and performance boundaries.

3.5.19 - 2026-05-11

Fix Chailease BNPL installment count

  • Pass configured BNPL installmentCounts into the SHOPLINE SDK.
  • Send the selected BNPL count from checkout and pay-for-order as ys_shopline_bnpl_installment.
  • Create BNPL trades with confirm.paymentMethodOptions.installments.count, not legacy confirm.installment.
  • If only one BNPL count is configured, use it as a backend fallback when the frontend does not post a count.
  • Add tests/bnpl-installment-static.php to lock the 36-period BNPL flow.

3.5.18 - 2026-05-09

重新打包 3.5.17(程式碼相同,修正打包格式)

  • 3.5.17 zip 因使用 PowerShell Compress-Archive 打包,路徑分隔符為反斜線,Linux 解壓後檔名變成 literal \ 而非資料夾,且檔名包含版本號導致 WordPress 建立巢狀資料夾。
  • 本版 zip 改用 Python zipfile 打包(正斜線路徑),檔名 ys-shopline-via-woocommerce.zip 不帶版本號,符合 WordPress plugin 安裝慣例。
  • 程式碼與 3.5.17 完全相同,無新功能、無 bug fix。
  • 已撤回 3.5.17 release,請改用本版。

3.5.17 - 2026-05-09 [已撤回]

付款方式 ICON 本機化與後台開關

  • 新增「啟用付款方式 ICON」後台設定,預設開啟,可關閉所有付款方式圖示。
  • ATM、街口支付、LINE Pay、zingala 圖示改為外掛內建 WebP,避免結帳頁依賴外部圖片 URL。
  • 信用卡、分期、訂閱保留 Visa / Mastercard / JCB 本機圖示,並一併受 ICON 開關控制。
  • Apple Pay 補回外掛內建 SVG 圖示,並一併受 ICON 開關控制。
  • 新增 tests/gateway-icons-static.php regression test,鎖定本機資產與開關行為。

3.5.16 - 2026-05-07

修正 ATM 保留/等待付款訂單的前端顯示與變更付款方式流程

  • ATM 已取得虛擬帳號時,pending / on-hold 感謝頁與訂單頁都顯示「等待轉帳」與 ATM 繳費資訊。
  • 離線付款且已有繳費資訊時,按鈕改為「變更付款方式」;未取得離線付款資訊時保留「立即付款」。
  • 允許 ATM on-hold 訂單進入 WooCommerce order-pay,讓保留狀態也能變更付款方式。
  • 變更付款方式送出前會清除舊 ATM 虛擬帳號、舊 trade id 與付款狀態 meta,避免新付款流程仍被舊離線付款資訊干擾。
  • 新增 tests/offline-payment-display-static.php 靜態 regression test。

3.5.15 - 2026-05-05

修正 redirect return 雙重觸發導致重複完成訂單 / 重複備註

  • YSRedirectHandler 在 SUCCEEDED/SUCCESS/CAPTURED 分支加入 payment completion claim lock。
  • YSWebhookHandler 的 trade.succeeded / trade.captured 也使用同一把 lock,避免 webhook 與 redirect return 同時完成同一筆訂單。
  • 完成付款前會寫入 _ys_shopline_payment_complete_lock,立即重新讀取訂單確認 lock owner,並再次檢查 is_paid()。
  • 只有取得 lock 且 fresh order 尚未付款的 request 會執行 payment_complete() 與新增「SHOPLINE 付款已確認」備註。
  • 針對 SHOPLINE 信用卡 / 分期在 3DS 後可能延遲回傳或使用者重整 return URL 的情境,避免同一筆訂單被兩個 PHP request 同時完成。
  • 新增 tests/redirect-double-fire-static.php 靜態 regression test。

3.5.14 - 2026-05-02

修正 SHOPLINE gateway 註冊開關與 WooCommerce 啟閉狀態責任邊界

  • 外掛設定頁的各付款方式開關改為只控制是否註冊到 WooCommerce payment gateways。
  • 註冊後實際是否在結帳、加卡、重新付款流程可用,改由 WooCommerce 原生付款方式 enabled / is_available() 判定。
  • ys_shopline_enabled 總開關關閉時不再註冊任何 SHOPLINE gateway。
  • 直接 AJAX 入口補上 WooCommerce enabled / availability 檢查,避免關閉的付款方式仍可被前端流程呼叫。

升級指南(自動 migration)

升級到 3.5.14 時,外掛會自動執行一次性 migration(plugins_loaded priority 5):

  1. 將舊版 ys_shopline_{gateway}_enabled option 同步到 WooCommerce 原生 woocommerce_{gateway_id}_settings 的 enabled key。
  2. 完成後寫入 ys_shopline_db_version = 3.5.14,後續不再重跑。
  3. 已被使用者主動設定的 enabled 值不會被覆蓋(只在 settings 沒設過 enabled 時寫入)。

升級後可至「WooCommerce > 設定 > 付款」確認各 SHOPLINE 付款方式的啟用狀態與排序。日常啟閉與排序由此頁面管理,外掛設定頁僅控制是否「註冊」進來。

總開關注意事項

ys_shopline_enabled 全局關閉時:

  • 所有 SHOPLINE gateway 不會註冊到 WooCommerce。
  • ⚠️ 既有訂單退款、訂閱續扣(Recurring)會失敗,因為依賴 WC()->payment_gateways->payment_gateways() 取得 gateway 實例。
  • 請在沒有未完結 SHOPLINE 訂單與 active 訂閱時才關閉,或先用 WooCommerce > Payments 個別停用即可。

3.5.13 - 2026-04-29

修正 saved card + 信用卡分期資料流

根因

信用卡分期應使用 SHOPLINE CreditCard SDK 搭配 installmentCounts,不是 ChaileaseBNPL。ChaileaseBNPL 是中租零卡 / BNPL 路徑,切換後只會顯示期數選擇,不會渲染信用卡或已儲存卡 UI。

修法

  • 分期 gateway 保持 paymentMethod: CreditCard,並傳入 installmentCounts。
  • 分期重新啟用 customerToken + paymentInstrument.bindCard,讓 SDK 可顯示已儲存卡與新卡儲存選項。
  • 前端送出 ys_shopline_payment_instrument_mode:saved / new / new_save / regular。
  • 後端依模式決定 API:
    • saved:QuickPayment + paymentCustomerId + paymentMethodOptions.installments.count,不送 savePaymentInstrument。
    • new_save:CardBindPayment + savePaymentInstrument:true + paymentMethodOptions.installments.count。
    • new:Regular + paymentMethodOptions.installments.count。
  • 已驗證 sandbox saved-card 分期成功:referenceOrderId=1311_1,CreditCard + QuickPayment + installments.count=3,SHOPLINE 回 SUCCEEDED。

注意

舊測試中 1018 Business error 經 SHOPLINE 回覆確認為 sandbox 端設定/行為問題;插件端避免再把既有卡誤送成 CardBindPayment + savePaymentInstrument:true。

3.5.12 - 2026-04-29

分期 saved-card 停用策略一致性修補

  • 前端 ys_shopline_credit_installment 改為 supportsBindCard:false,避免後續 fallback 邏輯把分期誤判為可綁卡 gateway。
  • 分期 SDK config 明確移除 customerToken / paymentInstrument / forceSaveCard,確保分期只走新卡輸入,不顯示既有卡或儲存卡 UI。
  • 靜態 regression test 改為驗證 v3.5.11 的新產品策略:分期不混用 bindCard / saved card。
  • README 修正 Block Checkout 目前為停用狀態,避免交付文件與程式行為不一致。

3.5.11 - 2026-04-29

分期不混用綁卡 + PROCESSING/AUTHORIZED 中間態 + ICON 本地化 + 分期期數送單筆修正

Codex 部分(先 push)

  • 信用卡分期被建成一次付清的資料流修正:SDK 回傳分期期數送到後端 ys_shopline_installment,後端建立交易時保留 confirm.installment(修「送單筆而非分期」bug)
  • 重新付款頁的 SDK config 用訂單金額判斷 installmentCounts(pay-for-order 頁 cart 為空問題)
  • Redirect return 遇 CREATED/PROCESSING 短暫 polling 3×2 秒重查交易狀態
  • get_installment_context_total() helper 抽取(從 cart / order 統一抓金額)

本次補充(Claude 部分)

A. 分期 gateway 強制 disable bindCard / customerToken / saved card UI

問題:v3.5.10 之前分期 gateway 同時啟用 bindCard,但 SHOPLINE 規格將「分期」歸類為一般收款,「綁卡/快捷/定期」是另一獨立場景,兩者混用造成:

  • 1290 / #1291 撞 1018 Business error / CONFIRM_FAILED(user 選 saved card → SDK 不暴露 instrumentId → 後端誤走 CardBindPayment → SHOPLINE 拒絕重綁同卡)

  • 分期下誤觸發 v3.5.10 BIND_CARD_NOT_PERSISTED 警告
  • 分期下 v3.5.8 default 卡 auto-select 邏輯誤跑

修法:

  • YSCreditInstallment::get_sdk_config:unset($config['customerToken']),不再注入 paymentInstrument.bindCard
  • YSGatewayBase::prepare_payment_data:對 ys_shopline_credit_installment 強制 $use_bind_card = false
  • 分期一律走 Regular paymentBehavior,跟業界標竿(Stripe/綠界/藍新)對齊

影響:

  • 一般信用卡(ys_shopline_credit)saved card 流程完全不變
  • 訂閱 gateway(ys_shopline_credit_subscription)的 bindCard 完全保留
  • 只有分期 gateway 行為改變:UI 不顯示 saved cards,使用者一律「使用新卡」填卡

B. PROCESSING/AUTHORIZED 中間態處理 + 感謝頁綠標

問題:v3.3.3 起就存在的 UX bug — 高額信用卡 / 分期走 hitrust 3DS 後 SHOPLINE 進 status=PROCESSING + subStatus=AUTHORIZED 中間態(卡片已授權,等待 capture/settle 1-3 分鐘),YSRedirectHandler 只認 SUCCEEDED → 訂單卡 pending → 感謝頁顯示「付款尚未完成」。

修法:

  • YSRedirectHandler::check_and_update_order 加 subStatus 讀取 + 中間態分支
    || ( 'PROCESSING' === $status && in_array( $sub_status, ['AUTHORIZED', 'CAPTURING'], true ) )

    → 訂單設 on-hold + meta PAYMENT_AUTHORIZED_PENDING=yes + order note

  • YSOrderDisplay::display_payment_status_notice 加綠章「已授權,等待金流回傳確認」
    • 友善訊息「您的卡片已成功授權,金額已預留。SHOPLINE 通常在 1-3 分鐘內完成最終確認,您將收到通知信。」
  • 後續 webhook trade.succeeded 觸發時 payment_complete() 自動清除中間態

C. 信用卡 brand ICON 本地化

問題:YSCreditCard::get_icon / YSCreditInstallment::get_icon 用 WC()->plugin_url()/assets/images/icons/credit-cards/*.svg,但 WC 6+ 已移除這些 icon,造成圖示破損。

修法:

  • 改用插件自帶的 assets/images/visa.svg / mastercard.svg / jcb.svg(已存在)
  • 透過 YS_SHOPLINE_PLUGIN_URL(自帶 https)載入
  • 移除 use WC_HTTPS; 匿用 import

D. Codex P2 webhook race 修正

Codex 提出:v3.5.10 在 YSWebhookHandler::handle_trade_succeeded 路徑也呼叫 maybe_note_bind_card_not_persisted 會產生 race condition:

  • SHOPLINE 兩個 webhook 非同時到(trade.succeeded 先 / customer.instrument.binded 後)
  • 第一個來時就標 BIND_CARD_NOT_PERSISTED 警告
  • 第二個來時雖建 token,但 admin notes 已留錯誤警告

修法:webhook 路徑不寫 warning,改成只在 redirect handler(同步 GET response 已有完整資料)寫。webhook 流程由 trade.succeeded 自然觸發 sync_payment_token。

E. 採納 Codex working tree 重構

  • 抽 YSRedirectHandler::maybe_note_bind_card_not_persisted() 為 public static method(冪等 + 型別防衛 + is_bool/is_scalar 處理 non-scalar savePaymentInstrument)
  • 加 meta keys:BIND_CARD_ATTEMPTED / BIND_CARD_NOT_PERSISTED / PAYMENT_AUTHORIZED_PENDING
  • 不再覆寫 PAYMENT_STATUS=SUCCEEDED_BIND_NOT_PERSISTED,改用獨立 meta 保留 SHOPLINE 原始狀態
  • 統一魔法字串 → constant

F. 不影響的部分

  • 一般 CC + saved card 下單 ✓ 不變
  • 一般 CC + 新卡 + 勾儲存 ✓ 不變
  • 訂閱(CardBind / Recurring / change-payment)✓ 完全不變
  • 刪卡 / 新增卡 / unbind API ✓ 不變

G. polling + on-hold + webhook 三層中間機制

SHOPLINE PROCESSING 中間態處理鏈:

  1. redirect handler 短期 polling(codex,max 6 秒)— 快速 settle 直接 SUCCEEDED
  2. on-hold + 綠章 fallback(claude)— polling timeout 還在 PROCESSING/AUTHORIZED 走此路徑
  3. webhook trade.succeeded — payment_complete + 清除 PAYMENT_AUTHORIZED_PENDING meta

涵蓋從 sandbox 立即 settle 到 hitrust 4 分鐘 settle 全光譜。

3.5.10 - 2026-04-25

新卡儲存失敗的明確警告(CardBindPayment silent fail 偵測)

問題(v3.5.9 實測 #1270)

登入用戶用新卡 + 勾儲存下單 NT$95:

  • 我們正確發送:paymentBehavior=CardBindPayment + savePaymentInstrument=true
  • SHOPLINE 第一次回(CREATED):降級 paymentBehavior=Regular
  • SHOPLINE 第二次回(SUCCEEDED):savePaymentInstrument=false + 沒有 paymentInstrumentId
  • 訂單付款成功 → processing,但 WC 沒建 token
  • 管理員只看 order notes 完全不知為何沒儲卡 → silent fail

根因(最常見):SHOPLINE sandbox non-3D 流程不支援 CardBindPayment(amount 去末兩位為奇數會走 non-3D 直接成功)。生產環境少見此降級,但其他可能:

  1. 卡片不支援 binding(issuer 限制)
  2. 商家帳號未開通 binding 功能
  3. 金額/風控未觸發 3DS

修正

Step 1 - process_payment 階段標記(YSGatewayBase.php):

if ( 'CardBindPayment' === $payment_behavior && empty( $payment_instrument_id ) && $user_id ) {
    $order->update_meta_data( YSOrderMeta::BIND_CARD_ATTEMPTED, 'yes' );
}

Step 2 - redirect handler 收到 SHOPLINE response 後判斷(YSRedirectHandler.php):

  • 若 paymentInstrumentId 為空 且 order meta BIND_CARD_ATTEMPTED=yes → 寫 order note 警告 + meta BIND_CARD_NOT_PERSISTED=yes → log warning(含 SHOPLINE response 的 savePaymentInstrument flag 與降級後的 paymentBehavior)

Step 3 - webhook-first 時序補強(YSWebhookHandler.php):

  • 若 webhook 先把訂單標成已付款,也會走同一個 warning helper
  • _ys_shopline_payment_status 保留 SHOPLINE 原始付款狀態(例如 `SUCC

This README is longer than the copy stored here. Read the rest on GitHub →