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
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 付款狀態通知
- 狀態同步:自動和手動訂單狀態同步
- 沙盒模式:支援測試環境切換
安裝方式
- 上傳外掛至
wp-content/plugins/目錄 - 在 WordPress 後台啟用外掛
- 前往 WooCommerce > 設定 > 付款 設定各付款方式
- 前往 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_loadedschema 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 離線待繳流程不變。 - 修正共用
Regularfallback 未檢查 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_actionwebhook 只用 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;後端設定、前端預設重建及sdkOptionsdeep 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 失敗、webhooktrade.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)取代原 boolaccepted。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 payloadpayment.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 入帳)。③ 抽出單一共用嚴格 selectorUtils\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,roottradeOrderId不再優先繞過安全判斷——原本「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/4003unknown 不 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以 MySQLGET_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.cancelledwebhook 對已取消訂單也改為補寫付款狀態 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/tokenAPI 尚未成功,看不到勾選框 → 無法儲存卡片。 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,若 SDKenable=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_fieldscallbacks 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
widefatborder 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, andissuerBinin SHOPLINE API debug logs and general logger context. - Remove card BIN/IIN fields from stored
_ys_shopline_payment_detailsnapshots 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.phpto 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.phpto lock the admin display, permission, nonce, and performance boundaries.
3.5.19 - 2026-05-11
Fix Chailease BNPL installment count
- Pass configured BNPL
installmentCountsinto 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 legacyconfirm.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.phpto 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.phpregression 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):
- 將舊版
ys_shopline_{gateway}_enabledoption 同步到 WooCommerce 原生woocommerce_{gateway_id}_settings的enabledkey。 - 完成後寫入
ys_shopline_db_version = 3.5.14,後續不再重跑。 - 已被使用者主動設定的
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.bindCardYSGatewayBase::prepare_payment_data:對ys_shopline_credit_installment強制$use_bind_card = false- 分期一律走
RegularpaymentBehavior,跟業界標竿(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+ metaPAYMENT_AUTHORIZED_PENDING=yes+ order noteYSOrderDisplay::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-scalarsavePaymentInstrument) - 加 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 中間態處理鏈:
- redirect handler 短期 polling(codex,max 6 秒)— 快速 settle 直接 SUCCEEDED
- on-hold + 綠章 fallback(claude)— polling timeout 還在 PROCESSING/AUTHORIZED 走此路徑
- 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 直接成功)。生產環境少見此降級,但其他可能:
- 卡片不支援 binding(issuer 限制)
- 商家帳號未開通 binding 功能
- 金額/風控未觸發 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 metaBIND_CARD_ATTEMPTED=yes→ 寫 order note 警告 + metaBIND_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 →