WooCommerce Subscription 定期定額金流串接


忙了一個半月終於有時間把手邊的工作整理一下,好久沒有連續高密度的開發,雖然在寫的當下很爽快,有完整的時間可以專注程式的邏輯,但一離開電腦後就覺得整個人虛脫,只能跟沙發及 Youtube 合為一體,然後睡一覺醒來繼續這樣的循環…

但還好這禮拜老婆有帶我出去走走,我們去了南港兩天一夜,才發現到南港車站跟我小時候的記憶完全不一樣,樓有多高就高、路有多寬就多寬,金融園區要多大有多大,還差點在中信兄弟旗艦店買了整套 Passion Girl 的人形桌牌,然後在台北流行音樂中心文化館暴露年紀,沈浸在七年級生的音樂回憶中,也在 37 度高溫下走在沒有遮蔽物的路上走到快中暑,讓我想起為何我們都不在夏天出遊了。

這陣子我的工作環境也改變了不少,像是開始改用 PhpStorm、導入 CI/CD 流程、一杯咖啡喝一整天、去共享工作空間上班,有時間我再逐一分享出來這些新體驗,這禮拜想先紀錄這陣子好不容易克服的大魔王:定期定額金流串接。

WooCommerce Subscription 定期定額金流串接

有別於一般金流的一次性付款,WooCommerce Subscription 訂閱外掛可以實現週期性付款的功能,也就是以月或年為單位,時間到自動向消費者進行請款,像是 SaaS 服務多半是用定期定額付費,就不用讓消費者重新刷卡。

在 WooCommerce 生態圈中要實現定期定額的功能,最多人使用的是 WooCommerce Subscription 訂閱外掛,目前台灣廠商開發的定期定額金流外掛也多半是支援它,因此該文會以此外掛為主,逐步說明串接定期定額金流的流程與注意事項。

一、定期定額金流類型

根據金流商的不同,在處理定期定額付款有兩種方式:

1. 自動定期扣款

指的是金流商可以接受定期扣款參數,像是可扣款頻率、扣款日期、扣款金額,網站這邊只要在結帳時把相關參數傳送過去後,之後的扣款都會由金流商「自動」處理,網站這邊只要關心當顧客不續訂時,將金流商的自動扣款功能透過 API 來停用即可。

以串接上的體驗來說,該類型應該會比較好處理,只要在第一次結帳跟不續訂時呼叫金流商的 API 即可,中間的扣款流程網站這邊完全不需要擔心。

2. 手動呼叫扣款

是指當顧客第一次完成結帳後,我們會從金流商那邊取得一串加密過的字串,然後後續扣款就會使用這個字串來進行請款,相較於原生支援,該類型的金流因為要在時間到時「手動」呼叫金流商 API 做請款的動作,因此整個過程會比較繁瑣,除了要處理排程的扣款,還要擔心 API 失敗的後續處理。

剛好這次專案就是要處理這類型的定期定額金流,因此以下說明皆會以手動呼叫扣款的開發流程來進行說明。

二、定期定額扣款流程

分為以下幾個大步驟:

  1. 顧客在結帳頁輸入卡號送出
  2. 金流商回傳付款加密字串
  3. 網站資料庫紀錄加密字串而非真實卡號
  4. 結帳完成後產生一張一般訂單與訂閱訂單
  5. 使用 WooCommerce Subscription 排程 woocommerce_scheduled_subscription_payment進行定期扣款
  6. 排程觸發後產生新的一般訂單,並與原始的訂閱訂單做關聯
  7. 如果排程執行扣款成功,會自動產生下一期扣款的排程,原始的訂閱訂單狀態會「已啟用」
  8. 如果排程執行扣款失敗,新的一般訂單會顯示失敗,原始的訂閱訂單會狀態會顯示「保留」

這邊要注意的是排程的勾點名稱在官方文件上是寫 scheduled_subscription_payment_{gateway_id},但實際上要用這個 woocommerce_scheduled_subscription_payment_{gateway_id} 帶有 WooCommerce 前綴的勾點才是正確的。

另外該專案還要滿足以下條件:

  • 需要紀錄卡號(加密字串),讓顧客下次結帳可以選擇不同的信用卡
  • 讓顧客可以在我的帳號頁自行新增、刪除與設定預設扣款信用卡
  • 排程扣款失敗時,可以讓管理員手動點擊付款
  • 支援有試用期的定期定額商品(初始訂單為零元)

接下來針對以上流程與專案需求說明開發上的一些注意事項。

三、結帳頁信用卡表單

由於我們必須使用顧客的真實卡號來取得金流商回傳的付款加密字串,在結帳頁中需要提供信用卡卡號的輸入介面,如下圖:

在我開發的第一個版本中,因為沒有注意到 WooCommerce 有內建的輸入介面,所以就直接在金流類別中的 payment_field() 直接手刻信用卡表單,後來是因為要整合更換卡號功能,也就是下圖中的頁面,最後還是只能全部改寫成內建的方法:

1. 信用卡表單

要在 Payment Gateway 類別裡面使用 WooCommerce 內建的信用卡表單,首先需要繼承的類別是不是 WC_Payment_Gateway 而是 WC_Payment_Gateway_CC,後者一樣是繼承前者而來,只是多了 Credit Card (CC) 信用卡相關功能,該類別有一個 form() 方法,定義了信用卡表單的 HTML。

接著在設定 support 屬性的地方加入 tokenization 即可,如果同時要支援訂閱的話,也是在這邊加入以下參數:

​程式碼範例​

這邊有一個需要注意的地方是 WC_Payment_Gateway_CC 的信用卡表單 input 是不帶有 name 屬性,這代表我們無法在 process_payment() 中用 $_POST[“card-number”] 來取得真實的卡號,根據官方的回覆說這是為了避免開發者將卡號透過 HTTP 進行傳輸。

比較好的作法是需要傳輸的資料都是加密後的結果,避免中間被攔截而造成卡號外洩,可能的作法是金流商提供前端套件將卡號做加密後再行傳送,但該專案串接的金流商並未提供該機制,還是只能將信用卡卡號作為參數來進行傳送。

為了實現這一點,我在我的金流類別中複寫了 form() 方法,基本架構一模一樣,只是加入了 input 的 name 屬性,讓我可以從後端拿到信用卡的相關資料:

2.新增信用卡

要讓顧客可以在前台自行新增付款方式,在我們的金流類別中需要實作 add_payment_method() 方法,然後從我們自己加入 name 的 form() 裡面拿到信用卡資料,再去做一次結帳資料的傳送,最後返回一個跟 process_payment() 有點類似的陣列,差別在於是重新導向回付款方式頁:

'success', 'redirect' => wc_get_endpoint_url( 'payment-methods' ), ) }

由於新增信用卡並非是結帳流程,而我們必須要透過金流商的付款 API 才能取得付款加密字串,因此這邊要額外處理付款參數的傳送,特別是訂單金額的部分,新增付款方式不會有訂單金額,但金流商 API 零元訂單是不允許的,所以需要先刷 10 元然後再透過退款 API 來退款。

這邊我遇到的狀況是在付款 API 呼叫完的當下立即執行退款,結果一直遇到退款發生錯誤,原因出在付款 API 的流程還沒走完,退款 API 會找不到要退哪一筆單,理論上要多接一個訂單查詢 API 來確認這件事,但實務上退款這件事應該不用「即時」完成,因此我的解決辦法是付款 API 打完後,安排一個一分鐘後的排程來進行退款 API 的呼叫,就能避免上述問題的發生。

四、付款加密字串處理

WooCommerce 提供一個 WC_Payment_Token_CC 的類別來處理 Token,也就是付款用的加密字串,在資料庫中有兩張表是來紀錄 Token 資訊的,分別是 wp_woocommerce_payment_tokens 與 wp_woocommerce_payment_tokenmeta,而該類別也提供方法讓開發者可以儲存 Token。

無效或遺漏 Payment token

我設計的方法是在金流商回傳 Token、卡號末四碼後來建立屬於該顧客的 Token 資訊,寫入方法很簡單,透過以下程式碼即可。

然後在結帳的時候如果顧客選擇已經儲存的卡號,就可以使用 WC_Payment_Tokens 類別的 get() 方法來取得 Token 物件,然後再用 get_token() 方法找到付款加密字串:

​程式碼範例​

這邊我踩到一個卡關卡超久的雷:在結帳的時候按下送出,一直出現「無效或遺漏 Payment token」的錯誤訊息,爬了原始碼跟改變寫法一直無法排除,後來才發現原來是在寫入 Token 的 $token->set_expiry_year() 方法年份格式錯誤。

我一直以為信用卡的年份都是兩位數,像是 2023 寫 23 就好,但是這邊必須要四位數也就是 2023,不然就會發生錯誤,另外補充 Payment Token 的文件不是在 WooCommerce 官網上而是在 Github:https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API#adding-payment-token-api-support-to-your-gateway​

小結

跟一般金流比起來,定期定額需要考慮到的情境比較多,特別是在定期扣款以及儲存 Token 這一塊,需要反覆測試以及小心不要把不該儲存的資訊儲存下來,但整體的基礎架構還是可以從一般的金流類別來加以擴充。為避免篇幅太長,下次有機會我再把具體的程式碼整理出來~

WordPress 開發日常

Read more from WordPress 開發日常

之前朋友約出來喝咖啡時他分享下一個階段的 AI 可能會發展成監督式的 AI,也就是當 AI 自己在跑的時候,會有另外一個 AI 來檢查它現在做的每一件事情是否都真的有往正確的方向推進。 當下聽到覺得這個 idea 很棒,但具體要怎麼實作完全沒有概念,更不用說這樣子 token 消耗的成本會不會變成兩倍?而且監督 AI 的 AI 需要有什麼能力?該以哪一種方式來進行監督?然後又該如何把找出的問題回報給原本的 AI 呢? 這一切問題在我最近研究 Claude Code 以外的工具時意外發現了解決方案,那就是之前我拿來做為 Discord Bot 的 AI 引擎 OMP。 OMP 是 PI 的分支,想理解什麼是 OMP 可以先從 PI 開始理解,PI 是一套開源且輕量化的 Coding CLI,也是跟 Claude Code 一樣跑在終端機的寫程式 Agent。 相較於 Claude Code 的封閉,PI 完全開源,並且支援多家不同的模型,PI 強調有需要什麼工具再自己額外安裝,就不會像 Claude Code 一樣原廠給你什麼就只能用什麼。 而 OMP 是 PI 的分支,它的概念跟...

之前分享我請 AI 設計的一套專門用來管理手邊待辦清單的專案管理軟體,邊用的時候我就在邊思考著:當我新增這些任務之後,我還是要回到終端機去把這些任務的描述、相關的文件以及完成的目標告訴 AI,然後等它跑完之後我再驗收,再回到我的計時軟體裡面來記錄是已完成還是該暫停。 如果可以在開立任務的時候,就能直接指派 AI 來做這件事情,是不是就可以少一個重複描述、開發需求的流程?剛好有了這個想法之後,就在 YouTube 上面看到一個叫做 Auto Claude 的專案。 它把 Claude 直接跟看板軟體整合,並且可以在裡面詢問 AI 關於專案的細節以及產生開發藍圖,也能設定專案的亮點與相關資訊。 我看到後驚為天人,覺得這似乎是另外一種理想的 AI 開發軟體,本想直接下載裝起來用,但請 Claude 研究後發現它有一些問題,於是就想說自己來開發,順便理解關於整個 Loop Engineering 的概念。 Claude Orchestrator 這次一樣是使用 Glaze 來開發,請它建立好專案的環境之後,然後再回到終端機去接手後續的多工並行。我的做法是先請 AI 去參考 Auto...

常常在社群媒體上看到各種自動化工具的廣告,從早期的 n8n 開始到現在 AI Agent 自動完成任務,這些工具跟流程都很棒。但是回到使用者本身,我們真的知道自己有哪些工作是可以被自動化或是應該「自動化」的嗎? 這就讓我回想起以前給自己的工作要求,那就是要把手上所做的每一件事情都記錄下來,並且可以明確看到每項任務的執行時間以及頻率,這樣事後才有辦法做回顧,我把這樣的理念也應用到承接專案的層面上。 這也是為什麼我有一些專案都是採取時薪計價的開發模式,因為我需要讓客戶知道他的預算都花在哪些項目,來藉此把資源用在合適的任務上。 我最早使用的即時軟體叫做 Ora,這是一套結合看板管理、敏捷開發流程、時數統計,甚至還有內建聊天討論串的一套強大工具。 它長期都是免費的,付費方案是團隊成員超過計算,所以我都是使用它來進行與客戶的專案管理,它的介面十分清楚且一目瞭然,可以縱觀地知道是哪一類型的任務花最多時間,也可以細看到具體是在處理哪一個問題所消耗的時數。 但當某天我打開準備使用它的時候,發現它被另外一套專案管理軟體 ClickUp...