profile

WordPress 開發日常

WooCommerce Subscription 定期定額金流串接

Published 9 months ago • 1 min read

忙了一個半月終於有時間把手邊的工作整理一下,好久沒有連續高密度的開發,雖然在寫的當下很爽快,有完整的時間可以專注程式的邏輯,但一離開電腦後就覺得整個人虛脫,只能跟沙發及 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() 有點類似的陣列,差別在於是重新導向回付款方式頁:

<?php public function add_payment_method(){ // 處理新增付款方式 return array( 'result' => '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 開發日常

每次遇到想要跟我學習 WordPress 外掛開發的朋友來信,總是只能丟些教學文件給他們,畢竟我也不知道他們想做什麼東西,只能提供官方文件給他們參考,雖然這幾年來也寫了不少,但好像都缺少比較完整的開發教學。 想到之前為了教老婆開發而整理出的一套課程大綱,似乎有這個契機可以把它發展下去,但如果只是講理論而沒應用好像無法立即派上用場,於是想到可以整合之前寫過的小工具作為內容,就可以設計出真的能做出東西的教材。 剛好上週為了除錯的事情順手寫了一支日誌外掛,整合理論與實務的教學計畫如下: -- 前言 日誌紀錄是開發者與網站維護人員最重要的好夥伴,除了可以在開發當下輸出執行結果來確認程式的邏輯外,當網站發生錯誤時也能從紀錄中去檢查問題所在原因。查看 WooCommerce 的日誌紀錄步驟為:進入後台側選單 > 點選 WooCommerce > 狀態 > 日誌紀錄 > 選擇日誌檔 > 查看,就會顯示相關的資訊。 一、WooCommerce 內建日誌紀錄的問題 WooComerce 內建的日誌有以下幾個痛點: 選單的目錄層級過多,日誌檔太多時東西不好找...

6 months ago • 1 min read

上週五工作到一半,家裡的門鈴響起,開門後是衛生局的人員通報說社區附近出現登革熱案例,需要整棟樓進行消毒,正當我想著那我就不要出門就好,結果想不到對方說連屋內都要消毒時,我一整個傻眼,家裡這麼多吃的穿的睡的,全部都要暴露在殺蟲劑下,一想到頭就暈。 第一時間先上網查一下這是不是新型態的詐騙手法,才知道原來對臺南人來說這已是司空見慣的作業流程,還查到「養生膠帶」這個神器,它可以把大型傢俱鋪上一層塑膠袋作為防護,於是當晚趕快去水電行買了兩捲,想說隔天早上再蓋就好。 到了隔天一早九點,大樓就傳來噴藥機的巨大聲響,衛生局人員敲每戶的門,說要開始進行消毒了,那時候我們才剛吃完早餐,一整個被殺個措手不及,只好趕快把養生膠帶鋪上,但因為太趕,很多傢俱都沒有鋪到,就這樣半推半就的被請出家裡。 坐進電梯前看到消毒人員拿著在戶外消毒水溝的大型噴藥機直接在家裡面狂噴,不知為何有種荒謬感,好像我家是喪屍病毒外洩的實驗室必須徹底消毒一番,然後跟著鄰居們一起被迫撤離家園,似乎也拉近了鄰居之間的距離(?)...

6 months ago • 1 min read

這兩週在忙著準備 WordCamp 簡報以及擺攤相關事宜,雖然既累人又噴錢,但過程還是充滿了樂趣,我弄了宣傳 DM、易拉展、桌上立牌,以及手工裁切貼紙,好久沒做平面設計了,光設計一份 DM 就搞了五小時。 我是用 Figma 做的,由於它是專門用來做介面設計的,因此色彩模式並沒有 CMYK,所以只能用 RGB 輸出,為了要精準還原電腦上的顏色,前前後後不曉得打樣了多少次,最後還是睜一隻眼閉一隻眼讓它過了,不然可能花上七天七夜還搞不定… 明天就是期待已久的 WordCamp Taiwan,我覺得身為聽眾真的超幸福,只要人到屁股坐下,就能獲得這些講者們累積好多年的經驗與知識,會後還能去扒著他們不放問問題,光這樣想就覺得值回票價,我想分享一下身為開發者的我會去聽哪幾場,以及我是如何認識這些大大的: 上午 9:30 - WordPress 社群中的成長旅程 by Eric 在 WordCamp Keynote 由台灣人主講印象中這還是頭一次,我覺得這超棒的,以往的 Keynote 都是由 WordPress 官方代表來負責,通常會宣傳新的功能或是官方想要傳達的主題,這次是由 Eric...

7 months ago • 1 min read
Share this post