前情提要:上一篇我們設計了自己的 API 來取得 WooCommerce 的付款方式列表,藉此要用在之後結帳頁的付款方式顯示,同時還分析了 WooCommerce Store API 的基礎架構,並且透過文件找到擴充自訂資料的方法,接下來我們就改用 Store API 的邏輯來處理付款方式的取得。
在研讀文件後,發現到 Store API 的擴充方式並沒有如我想像中的自由,在解釋有哪些限制之前,先來做個必要的名詞解釋以便後續的理解。我們要先知道 API 請求路徑的組成,以 /cart 為例,要取得購物車內容的路徑如下:https://wc.test/wp-json/wc/store/v1/cart/
- namespace 命名空間 – 指的是請求路徑中
wc/store/v1
這一段,主要作用是可以跟其他路徑做區別,避免名稱跟其他路徑相同 - endpoint 端點 – 指的是請求路徑中
cart
的地方,也就是具體的請求內容
要擴充 Store API 的時候我的第一直覺是要自訂 endpoint 端點,如果以取得付款方式來說,可能是新增一個像這樣的路徑:https://wc.test/wp-json/wc/store/v1/gateways/
,然後使用 GET 方法來進行請求,但文件跟程式碼翻了老半天,沒看到有相關的擴充寫法,後來才知道邏輯跟我想的不同。
Store API 的 ExtendSchema
Store API 的擴充邏輯是先決定你想要把新增的資料放在哪一個端點下面,然後於指定的端點下會有一個 extensions
區塊來顯示新增內容,具體的顯示方式如下:

可以看到當我請求 /cart 的時候最下面多了一個 extensions
,裡面有一個 gateway
的 key,並且列出了所有付款方式的設定內容,而這就是 Store API ExtendSchema
類別的主要作用,將新的資料呈現在既有的端點裡面。
但並非每個端點都能使用 ExtendSchema
,目前有四個(文件只有寫到三個),分別是 /cart、/cart/items、/products 以及 /checkout,如果你需要讓其他端點也能使用 ExtendSchema
的話,可以參考文件說明:https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/rest-api/extend-rest-api-new-endpoint.md
但由於把 ExtendSchema
加入到新端點這部分目前還沒看到有勾點可以處理,因此你有這樣的需求話只能發 PR 請他們加入或是要求增加新的勾點,以我現在的需求放在 /cart 裡面就夠用了。
透過 ExtendSchema 新增付款方式列表
接下來進行 ExtendSchema
的實作,我們的目標是在 /cart 的端點下新增一個 gateway
,裡面會列出後台有啟用的付款方式,主要步驟有:
- 註冊端點資料
- 取得付款方式
- 整理付款方式欄位的結構描述( Schema )
- 在 WooCommerce 載入完成後初始化付款方式類別
我們拆解每個步驟來說明:
一、註冊端點資料
首先先建立一個類別名叫 Gateway,記得要用命名空間避免跟其他外掛打架,該類別會使用到 ExtendSchema
來檢查當初始化的時候所傳入的物件,同時宣告一個常數來設定於 extension
中要顯示的名稱:
<?php
namespace WOOCAT\APIs;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
defined( 'ABSPATH' ) || exit;
class Gateway {
/**
* Stores Rest Extending instance.
*
* @var ExtendSchema
*/
private static $extend;
/**
* Plugin Identifier, unique to each plugin.
*
* @var string
*/
const IDENTIFIER = 'gateway';
/**
* Bootstraps the class and hooks required data.
*
* @param ExtendSchema $extend_rest_api An instance of the ExtendSchema class.
*
* @since 3.1.0
*/
public static function init( ExtendSchema $extend_rest_api ) {
self::$extend = $extend_rest_api;
self::extend_store();
}
}
靜態方法 init()
裡面要先取得 ExtenSchema
類別,然後會在 extend_store()
裡面使用 ExtenSchema
的 register_endpoint_data()
方法來把我們要的資料顯示在既有的端點之中,該方法要傳入五個參數,分別是:要在哪個端點顯示、在該端點中的辨識名稱、資料顯示的回呼函式以及結構資料的回呼函式與類型,extend_store()
實作如下:
class Gateway {
// 略...
/**
* Registers the actual data into each endpoint.
*/
public static function extend_store() {
// Register into `cart`
self::$extend->register_endpoint_data(
array(
'endpoint' => 'cart',
'namespace' => self::IDENTIFIER,
'data_callback' => __CLASS__ . '::extend_cart_data',
'schema_callback' => __CLASS__ . '::extend_cart_schema',
'schema_type' => ARRAY_A,
)
);
}
}
由於我們要在 /cart 顯示付款方式,所以 endpoint 參數設定為 cart,而 namespace 實際指的是下圖中的地方:

data_callback 與 schema_callback 下面會實作,而 schema_type 使用 ARRAY_A 代表是使用 key value 的形式來描述結構資料。接下來我們看 extend_cart_data()
靜態方法裡面是如何取得付款方式資料的。
二、取得付款方式
上一篇文章中我們使用以下的方法來取得付款方式:
$gateways = WC()->payment_gateways->payment_gateways();
$enabled = array();
foreach ( $gateways as $gateway ) {
if ( $gateway->enabled === 'yes' ) {
$enabled[ $gateway->id ] = get_option( 'woocommerce_' . $gateway->id . '_settings' );
}
}
return rest_ensure_response( $enabled );
這邊有兩個可以改進的地方,第一個是有 get_available_payment_gateways()
這個方法,用了它之後就可以不用再做 if ( $gateway->enabled === 'yes' )
的判斷就能直接取得啟用中的付款方式。
第二個是用 get_option( 'woocommerce_' . $gateway->id . '_settings' )
取得付款方式的設定會有問題,如果是銀行轉帳的話詳細資料是寫在 woocommerce_bacs_accounts
而非 woocommerce_bacs_settings
裡面,所以必須要再針對銀行轉帳去組成我們要的資料,光想就覺得麻煩,因此我換個角度,先取得所有 gateway 的資料再去移除我不需要的,修改如下:
class Gateway {
// 略
public static function extend_cart_data() {
$gateways = WC()->payment_gateways()->get_available_payment_gateways();
if ( ! $gateways ) {
return;
}
foreach ( $gateways as $gateway ) {
unset( $gateway->settings );
unset( $gateway->form_fields );
unset( $gateway->plugin_id );
unset( $gateway->id );
unset( $gateway->errors );
unset( $gateway->method_title );
unset( $gateway->method_description );
unset( $gateway->has_fields );
unset( $gateway->countries );
unset( $gateway->availability );
unset( $gateway->supports );
unset( $gateway->view_transaction_url );
unset( $gateway->new_method_label );
unset( $gateway->pay_button_id );
unset( $gateway->order_button_text );
unset( $gateway->enabled );
unset( $gateway->locale );
unset( $gateway->chosen );
}
return $gateways;
}
}
輸出的結果如下:

這樣的好處是如果未來需要金流設定的其他欄位,我可以直接把 unset 註解掉加回來。
三、整理付款方式欄位的結構描述
雖然找到很多關於 JSON 結構資料描述的優點,但目前還感受不到它的好,如果每個付款方式的回傳欄位都要描述,真的會花上不少時間整理,因此目前我只先處理銀行轉帳跟支票付款兩個付款方式,未來有整合其他金流再處理了。
另一個麻煩點在於這份結構描述不是固定的,它會根據有啟用的付款方式來回傳,因此這邊需要用判斷式來決定要回傳的結構,至於該如何整理可以參考下方的對照圖:

每個欄位要說明它的資料類型、欄位目的以及是否僅供讀取,JSON Schema 還有很多屬性可以描述欄位,甚至還可以用它設定該欄位的最大、最小值、長度、是否為 email 格式等等的功用,然後前端就可以使用 Ajv JSON schema validator 這個套件來檢查回傳資料是否有符合正確的格式。
四、在 WooCommerce 載入完成後初始化付款方式類別
準備好 Gateway
類別後我們使用勾點 woocommerce_block_loaded
來實作它,在實作前我們要先載入 StoreApi
以及 ExtendSchema
,在上一篇我們提到 Store API 把每個類別都放在容器裡面,因此當我們要取得 ExtendSchema
不能直接把它 new 出來,而是要使用註冊表的 get
方法來取得:
use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
/**
* Extend Store API
*/
add_action(
'woocommerce_blocks_loaded',
function() {
$extend = StoreApi::container()->get( ExtendSchema::class );
Gateway::init( $extend );
}
);
可以看到我們呼叫了 StoreApi::container()
的 get
來取得 ExtendSchema
,然後再把它傳入 Gateway
的初始化方法中,藉此讓 Gateway
可以使用 ExtendSchema
註冊端點的 register_endpoint_data()
方法。
完成以上的步驟之後請求 /cart 就可以看到以下的結果:

取得物流資料
搞定付款方式後我們還剩下物流資料以及登入註冊的部分,我先處理物流的部分,我們需要可以取得下圖中的資訊:

我一樣嘗試使用 ExtendSchema
來新增一個 Shipping
類別,但不知道為何輸出的資料都一樣是付款方式,另外比較讓我在意的地方是在原本 /cart 裡面就已經有運送方式的相關欄位,如果還要再輸出一次運送資料就會有重複的欄位,這樣的方法好像有點冗余。

比較洽當的做法應該是去增加既有欄位的資料才對,但遺憾的是爬了程式碼之後這部分並沒有提供任何 勾點可以加入新的欄位:

因此稍早已開票給開發團隊希望他們可以在這邊加個勾點,但看他們目前還有兩百多個 issue 還沒解心裡就有個底了,目前能想到的解法是一樣使用 ExtendSchema
來處理,放下精神潔癖的矜持,不然就是乾脆用 register_rest_route()
新增一個端點 /cart/shipping 來取得,只是這樣在做前端的時候要多一次請求,而且還要處理麻煩的 shipping package 的問題。
總之只能先等看看了,接下來會繼續處理顧客註冊與登入!