iT邦幫忙

2026 iThome 鐵人賽

0

WooCommerce 區塊結帳頁的付款方式清單是 React 建立的,資料來自 Store API,前端要顯示一個付款方式需要知道三件事:

  1. 它叫什麼名字、要顯示什麼說明
  2. 什麼情況下可以用(例如貨到付款不適用於虛擬商品)
  3. 選了它之後要在畫面上顯示什麼(有些金流要填卡號,有些什麼都不用填)

這三件事 PHP 的 WC_Payment_Gateway 都無法處理,它只知道怎麼在伺服器端處理付款。所以 WooCommerce 另外設計了一層:PHP 端宣告資料與腳本,JS 端註冊要怎麼畫。

換句話說一個支援區塊結帳的金流要寫兩份,包含上一篇提到的收款邏輯,以及這一篇要介紹的區塊結帳:

檔案 負責
class-wc-gateway-newebpay.php 收款邏輯、訂單狀態、背景通知
class-newebpay-blocks-support.phpsrc/newebpay-blocks.js 讓它出現在區塊結帳頁

PHP 的部分:繼承 AbstractPaymentMethodType

use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;

final class NewebPay_Blocks_Support extends AbstractPaymentMethodType {

	/**
	 * 必須跟 WC_Gateway_NewebPay 的 $id 完全一致.
	 *
	 * @var string
	 */
	protected $name = 'newebpay';

	public function initialize() {
		$this->settings = get_option( 'woocommerce_newebpay_settings', array() );
	}

	/**
	 * 決定要不要載入這個金流的前端腳本.
	 */
	public function is_active() {
		return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
	}

	/**
	 * 註冊並回傳前端腳本的 handle.
	 */
	public function get_payment_method_script_handles() {
		$asset_file = MY_PLUGIN_PATH . 'build/newebpay-blocks.asset.php';

		// 還沒 build 就不要註冊,避免整個結帳頁掛掉.
		if ( ! file_exists( $asset_file ) ) {
			return array();
		}

		$asset = require $asset_file;

		wp_register_script(
			'newebpay-blocks',
			MY_PLUGIN_URL . 'build/newebpay-blocks.js',
			$asset['dependencies'],
			$asset['version'],
			true
		);

		return array( 'newebpay-blocks' );
	}

	/**
	 * 傳給前端的資料.
	 */
	public function get_payment_method_data() {
		return array(
			'title'       => $this->get_setting( 'title' ),
			'description' => $this->get_setting( 'description' ),
			'supports'    => $this->get_supported_features(),
		);
	}
}

這個類別的結構直接對照 WooCommerce 自己的 CashOnDelivery.php(在 src/Blocks/Payments/Integrations/),$name 要跟 WC_Payment_Gateway$id 一模一樣,這是兩層之間唯一的連結,打錯會發生前端顯示付款方式,但下單後會說「付款方式無效」。

is_active() 決定的是要不要把這個金流的前端腳本載進結帳頁,區塊結帳要顯示一個付款方式要兩個條件同時成立:

  1. JS 有註冊 — 由 is_active() 決定
  2. 它出現在 Store API 回傳的 payment_methods — 由 is_available() 決定

第二項的來源在 src/StoreApi/Schemas/V1/CartSchema.php

'payment_methods' => array_values( wp_list_pluck( WC()->payment_gateways->get_available_payment_gateways(), 'id' ) ),

get_available_payment_gateways() 就是逐一呼叫 is_available()。所以 is_available() 在區塊結帳這邊握有最終否決權,前一篇寫的那些條件會自動生效,不用再接一次。所以兩層的分工是:is_active() 管「是否載 JS」,is_available() 管「顧客看不看得到」。

get_payment_method_script_handles 這個方法負責 JS 註冊時的名稱,注意這裡只有 wp_register_script(),沒有 wp_enqueue_script(),因為 WooCommerce 會把所有啟用中金流回報的 handle 收集起來,當成結帳區塊腳本的相依項目:

// WooCommerce 的 PaymentMethodRegistry.php.
$script_handles = array_merge( $script_handles, $payment_method->get_payment_method_script_handles() );
return array_unique( array_filter( $script_handles ) );

.asset.php 是自動 build 的產物

npm run build@wordpress/scripts)會在 JS 旁邊產生一個同名的 .asset.php,內容只有一行:

<?php return array(
	'dependencies' => array( 'react-jsx-runtime', 'wp-i18n', 'wc-blocks-registry' ),
	'version'      => 'd266e71708363dd3d4b9'
);

它幫你解決兩件事:

  • 相依清單:你的 JS 裡 import 了什麼,build 工具會掃出來自動列成 WordPress 認得的 handle,之後加了新的 import 也不會忘記更新這串名單。
  • 版本號:那串是檔案內容算出來的雜湊,內容一改就變。丟給 wp_register_script() 當版本,網址就變成 newebpay-blocks.js?ver=d266e717…,改了程式碼後重新整理瀏覽器就不會被快取咬著。

$asset = require $asset_file; 把陣列取出來。

這個寫法第一次看到會覺得怪,關鍵在於 require 在 PHP 裡是一個表達式,會回傳被載入檔案 return 出來的值,大多數人只把它當成「把另一個檔案引入」,但它其實有回傳值。

所以 .asset.php 就是一個純資料檔,不定義函式、不定義類別、不動任何全域變數,只有一個 returnrequire 執行它、拿到陣列、指派給 $asset,而那兩個鍵剛好對應 wp_register_script() 的第三與第四個參數。

這種「用 PHP 陣列當設定檔」的模式在 WordPress 與 Laravel 都很常見,比 JSON 划算的地方在於檔案會進 OPcache,第二次之後連讀檔跟編譯都省了。

有三種情況要分清楚:

寫法 $asset 拿到什麼
require 一個有 return 的檔案 那個 return 的值,也就是我們要的陣列
require 一個沒有 return 的檔案 int(1),代表載入成功而已,接著 $asset['dependencies'] 就會噴錯
require_once 而且是第二次載入 bool(true),不是陣列

require_once 的情況要特別小心, get_payment_method_script_handles() 在一次請求裡有機會被呼叫超過一次(前台結帳、後台編輯結帳區塊都會走到),寫成 require_once 第二次就會拿到 true,然後 true['dependencies'] 會找不到東西而噴錯,因此取值一定要用 require

file_exists() 防止設定檔尚未產生時的錯誤

.asset.php 是 build 產物,通常會被放進 .gitignore,所以這些情況它都會不存在:clone 完還沒跑 npm install && npm run build、部署腳本漏跑 build、CI 打包時忘了把 build/ 放進 zip。

require 找不到檔案是 fatal errorinclude 才只是 warning),這個方法又剛好是在結帳頁載入時被呼叫的,結果就是整個結帳頁白畫面,因此需要它來防呆。

最後 NewebPay_Blocks_Support 類別註冊要掛在專屬的 hook 上:

add_action(
	'woocommerce_blocks_payment_method_type_registration',
	function ( $registry ) {
		$registry->register( new NewebPay_Blocks_Support() );
	}
);

JS 的部分:使用 registerPaymentMethod ``

前端用 @woocommerce/blocks-registry 提供的 registerPaymentMethod()

import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { decodeEntities } from '@wordpress/html-entities';
import { getSetting } from '@woocommerce/settings';
import { __ } from '@wordpress/i18n';

const settings = getSetting( 'newebpay_data', {} );
const label = decodeEntities( settings.title ) || __( '信用卡付款', 'my-plugin' );

const Content = () => {
	return decodeEntities( settings.description || '' );
};

const Label = ( props ) => {
	const { PaymentMethodLabel } = props.components;
	return <PaymentMethodLabel text={ label } />;
};

registerPaymentMethod( {
	name: 'newebpay',                    // 同樣要對上 gateway 的 id.
	label: <Label />,
	content: <Content />,                // 選了之後顯示的內容.
	edit: <Content />,                   // 編輯器裡的預覽.
	canMakePayment: () => true,          // 什麼條件下可用.
	ariaLabel: label,
	supports: {
		features: settings.supports ?? [],
	},
} );

幾個要點:

getSetting( 'newebpay_data' ) 讀的就是 PHP 端 get_payment_method_data() 回傳的資料。命名規則是 {$name}_data,這是約定,不是你能自己取的。

canMakePayment 是一個函式,收到購物車狀態回傳布林值,像貨到付款那種「虛擬商品不能用」的邏輯就寫在這裡。藍新沒有這種限制所以直接回 true,但如果你要做「滿一千元才能分期」,這裡就是實作的位置。

content 是選了這個付款方式之後顯示的區域。藍新的 MPG 是導向到藍新的頁面刷卡,所以這裡只放說明文字。如果是那種要在站上直接填卡號的金流(例如 Stripe Elements),信用卡表單就畫在這裡。

decodeEntities 別省略。設定值從 PHP 過來時中文和特殊符號會被 HTML 編碼,不解碼的話標題會顯示成 &#20449;&#29992;&#21345; 這種東西。

建置:wp-scripts 不認得 @woocommerce

這支 JS 用了 JSX 和 @woocommerce/blocks-registry,這個檔案需要編譯, 因此用 @wordpress/scripts 就好,跟之前做做區塊時同一套:

{
  "scripts": {
    "build": "wp-scripts build src/newebpay-blocks.js --output-path=build"
  }
}

然後你會發現 npm run build 直接失敗:

Module not found: Error: Can't resolve '@woocommerce/blocks-registry'

原因是 wp-scripts 內建的 dependency extraction plugin 只認得 @wordpress/* 開頭的套件,它會把這些 import 換成瀏覽器裡已經存在的全域變數(wp-i18nwp-html-entities 這些)。@woocommerce/* 不在它的名單上,webpack 就當成一般的 npm 套件去 node_modules 裡找,當然找不到。

解法是換成 WooCommerce 自己維護的那一份:

$ npm install --save-dev @woocommerce/dependency-extraction-webpack-plugin

再寫一份 webpack.config.js,把預設的那個換掉:

const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
const WooCommerceDependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );

module.exports = {
	...defaultConfig,
	plugins: [
		...defaultConfig.plugins.filter(
			( plugin ) =>
				plugin.constructor.name !== 'DependencyExtractionWebpackPlugin'
		),
		new WooCommerceDependencyExtractionWebpackPlugin(),
	],
};

換完再 build 就會過,產出的 .asset.php 長這樣:

<?php return array('dependencies' => array('react', 'wc-blocks-registry', 'wc-settings', 'wp-html-entities', 'wp-i18n'), 'version' => '3daf03117ffb295291f7');

wc-blocks-registrywc-settings 有出現在依賴清單裡才是正確的 — 這也是為什麼 PHP 端要 require 那個 .asset.php,而不是自己手寫依賴清單。

驗收方式

改完之後,這樣確認每一層都對:

  1. build/ 裡有 .js.asset.php,而且依賴清單有 wc-blocks-registry → 建置設定沒問題
  2. 付款方式有沒有出現在區塊結帳頁is_active()、註冊 hook 與 gateway 的 is_available() 三個都沒問題
  3. 標題與說明顯示正確(沒有亂碼)get_payment_method_data()decodeEntities 沒問題
  4. 選了之後能按下單name 跟 gateway 的 id 對得上
  5. 下單後有導向到藍新 → 昨天那層的 process_payment() 沒問題

還有一個測試時很容易誤判的點:Store API 的購物車回應有快取,改完後台設定直接重整結帳頁,前端拿到的 payment_methods 可能還是舊的,更新一下購物車(加個商品)再重整才會刷新,不然會以為程式沒生效。

以上都確認之後就可以在付款方式這邊看到我們新增的藍新金流:

WooCommerce 有個機制會在載入前檢查金流腳本的依賴,如果你的 JS 依賴了不存在的套件,它會在前台印出警告,所以如果你看到結帳頁跳出「某某 payment method 的腳本依賴有問題」的訊息就是在提醒你。

最常見的原因就是上面那段:.asset.php 沒產生,或是依賴沒有正確轉成 wc-blocks-registry 這種 handle,而是被打包進去或留下了 npm 的套件名稱。

這一段給 AI 的提示要包含什麼

串接金流的話可以請 AI 參考內建的 Payment Gateway,譬如以下提示詞:

newebpay 這個 WooCommerce 金流加上區塊結帳支援。PHP 端繼承 AbstractPaymentMethodType,參考 WooCommerce 內建的 src/Blocks/Payments/Integrations/CashOnDelivery.php 的寫法;JS 端用 registerPaymentMethod,name 要跟 gateway 的 id 一致。用 wp-scripts 建置,記得換成 @woocommerce/dependency-extraction-webpack-plugin

指名內建 Payment 當範本比讓它憑記憶寫可靠得多,而且那個檔案就在使用者的電腦上,AI 可以直接讀。提示最後那句關於 webpack 的補充也別省略。我實測過,不講的話 AI 會照著 @wordpress/scripts 的標準寫法給你一份 package.json 就收工,然後你會拿到那個 Module not found

金流三部曲到這裡結束。接下來兩篇處理台灣電商的另外兩件必備單品:下一篇講電子發票,重點在如何製作條件顯示的自訂結帳欄位。

文章目錄:https://oberonlai.blog/category/2026-ithome/


Hi, 我是 Oberon Lai,十多年前我從一個不懂程式的平面設計師,一頭栽進 WordPress 的世界。從佈景主題到外掛開發,從接案到自研產品,這段旅程讓我深刻理解:好的技術不只是寫出能跑的程式碼,而是真正解決人的問題。

我積極投入參與社群,公開演講紀錄如下:

我專精 WordPress 開發,從企業形象網站的設計與開發、佈景主題客製化,到既有網站的改版升級,提供完整的 WordPress 建置服務。開發面涵蓋外掛開發與維護、區塊編輯器(Gutenberg)客製區塊、ACF 與 Custom Post Type 的資料架構設計,以及 REST API 整合與 Multisite 多站架構建置。同時也協助網站效能改善與 SEO 調校、安全性檢測與強化,並導入自動化部署與版本控制流程,搭配長期的技術顧問與維運支援,讓網站上線後也能穩定運作。

亦提供 WooCommerce 商店的建置與設定,並串接綠界、LINE Pay、藍新等台灣主流金流。可依需求進行結帳頁面客製化、訂單狀態自動化流程設計,以及商品管理與庫存系統的客製開發;也支援 WooCommerce Subscription 定期定額、REST API 應用開發、報表與數據匯出等進階需求。此外,透過購物流程 UX 改善、電商網站效能調校與 HPOS 高效能訂單儲存相容開發,全面提升營運效率,並提供電商營運技術顧問服務。

AI 浪潮席捲而來,我選擇擁抱而非恐懼,我把 AI 融入開發工作流以及客戶的產品中,也持續累積「AI 看不見的部分」:真實踩坑經驗、最新漏洞情報那些只有第一線工程師才看得見的細節,如果你有任何 WordPress 的客製化需求或是 AI 開發相關的問題非常歡迎加入 LINE 官方帳號與我聯繫:

https://page.line.me/vrf7844t?oat_content=url&openQrModal=true

如果想要獲取 AI 開發實戰經驗也能訂閱我的電子報,每週五上午準時出刊:

https://oberonlai.blog/wordpress-newsletter/


上一篇
實作一個 WooCommerce 金流串接:以藍新 NewebPay 為例
下一篇
用 WooCommerce 結帳區塊做出客製化發票輸入欄位
系列文
從一句話到一個網站:用 Vibe Coding 開發 WordPress Block Theme 的 30 天38
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言