WooCommerce Store API 實戰(五)- 擴充類別

前情提要:上一篇我們設計了自己的 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 區塊來顯示新增內容,具體的顯示方式如下:

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-03.jpg

可以看到當我請求 /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() 裡面使用 ExtenSchemaregister_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 實際指的是下圖中的地方:

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-04.jpg

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;
    }
}

輸出的結果如下:

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-05.jpg

這樣的好處是如果未來需要金流設定的其他欄位,我可以直接把 unset 註解掉加回來。

三、整理付款方式欄位的結構描述

雖然找到很多關於 JSON 結構資料描述的優點,但目前還感受不到它的好,如果每個付款方式的回傳欄位都要描述,真的會花上不少時間整理,因此目前我只先處理銀行轉帳跟支票付款兩個付款方式,未來有整合其他金流再處理了。

另一個麻煩點在於這份結構描述不是固定的,它會根據有啟用的付款方式來回傳,因此這邊需要用判斷式來決定要回傳的結構,至於該如何整理可以參考下方的對照圖:

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-06.jpg

每個欄位要說明它的資料類型、欄位目的以及是否僅供讀取,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 就可以看到以下的結果:

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-03.jpg

取得物流資料

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

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-07.jpg

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

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-08.jpg

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

https://oberonlai.blog/wp-content/uploads/woocommerce-store-api-practice-4/woocommerce-store-api-practice-09.jpg

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

總之只能先等看看了,接下來會繼續處理顧客註冊與登入!

文章標籤Store API

目錄

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料

Picture of 賴俊吾 / Oberon Lai
賴俊吾 / Oberon Lai

現為全職 WordPress 工程師,網站開發經歷 11 年,專攻前端工程與 WordPress 佈景主題、外掛客製化開發

訂閱電子報

Hi,我是 Oberon,我會固定在每週五早上發送接案心得以及與 WordPress 相關的電子報,同時也會分享一些實用的開發知識,讓你在 WordPress 的接案路上不孤單!

專注於分享 WordPress 開發、接案技巧、專案管理等自由工作者必備知識與心得

© 2024 想點創意科技有限公司

想點創意科技有限公司 | 統一編號 90516823
Designed by Hend Design | 隱私權政策

訂閱電子報

Hi,我是 Oberon,我會固定在每週五早上發送接案心得以及與 WordPress 相關的電子報,同時也會分享一些實用的開發知識,讓你在 WordPress 的接案路上不孤單!