关于 woocommerce-blocks
用于扩展 WooCommerce React 购物车与结账区块的开发技能,指导插件开发者添加结账字段、注入界面、保存自定义数据及调整支付行为。
主要功能
- 01注册结账内嵌区块
- 02通过 SlotFill 注入界面
- 03使用 Store API 保存数据
- 04服务端字段验证
- 05自定义支付方式
适用场景
- 01在结账时添加礼品或配送字段
- 02在购物车或结账页展示促销内容
- 03创建自定义结账步骤
- 04按条件停用支付方式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill woocommerce-blocks安装后在 Claude Code 中输入/woocommerce-blocks确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/build-configuration-and-client-side-chec/criteria.jsontask.mdevals/inner-block-registration-and-store-api-p/criteria.jsontask.mdevals/slotfill-ui-injection-and-cart-data-acce/criteria.jsontask.md详细介绍
WooCommerce Blocks
概述
WooCommerce Blocks 使用由 React 驱动的 Gutenberg 区块,取代基于经典简码的购物车和结账流程。自定义插件可以通过注册内部区块(结账步骤中的自定义字段)、使用 SlotFills(将 UI 注入预定义的注入点),以及扩展 Store API 来保存和检索自定义数据,从而扩展结账区块。自版本 8.3 起,基于区块的结账流程是新建 WooCommerce 商店的默认选项。
何时使用此技能
- 为结账表单添加自定义字段(礼品留言、送货日期选择器、增值税号)
- 向购物车或结账区块注入促销内容或追加销售横幅
- 创建包含额外业务逻辑的自定义结账步骤
- 在现有 WooCommerce 网站上替换旧版简码结账流程
- 构建无需修改核心模板即可扩展结账行为的插件
核心说明
-
注册结账内部区块
内部区块是呈现在结账步骤中的 React 组件。它们需要通过 PHP 注册区块,并提供 JS/React 前端:
<?php // my-checkout-fields/my-checkout-fields.php add_action('woocommerce_blocks_loaded', function () { if (!class_exists('Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface')) { return; } require_once __DIR__ . '/class-my-checkout-integration.php'; add_action( 'woocommerce_blocks_checkout_block_registration', function ($integration_registry) { $integration_registry->register(new My_Checkout_Integration()); } ); });<?php // class-my-checkout-integration.php use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface; class My_Checkout_Integration implements IntegrationInterface { public function get_name() { return 'my-checkout-fields'; } public function initialize() { $this->register_block_frontend_scripts(); $this->register_inner_block(); } private function register_block_frontend_scripts() { wp_register_script( 'my-checkout-fields-frontend', plugin_dir_url(__FILE__) . 'build/frontend.js', ['wc-blocks-checkout', 'wp-element'], filemtime(plugin_dir_path(__FILE__) . 'build/frontend.js'), true ); } private function register_inner_block() { register_block_type(plugin_dir_path(__FILE__) . 'build/blocks/gift-message/block.json'); } public function get_script_handles() { return ['my-checkout-fields-frontend']; } public function get_editor_script_handles() { return []; } public function get_script_data() { return []; } } -
创建内部区块 React 组件
// src/blocks/gift-message/index.js import { registerCheckoutBlock } from "@woocommerce/blocks-checkout"; import { __ } from "@wordpress/i18n"; import { useEffect, useState } from "@wordpress/element"; import { useExtensionCartUpdateData, extensionCartUpdate, } from "@woocommerce/blocks-checkout"; const Block = ({ children, checkoutExtensionData }) => { const [giftMessage, setGiftMessage] = useState(""); // Persist gift message to cart extension data const handleChange = (e) => { const value = e.target.value; setGiftMessage(value); extensionCartUpdate({ namespace: "my-checkout-fields", data: { gift_message: value }, }); }; return ( <div className="wc-block-checkout__gift-message"> <label htmlFor="gift-message"> {__("Gift message (optional)", "my-checkout-fields")} </label> <textarea id="gift-message" value={giftMessage} onChange={handleChange} placeholder={__("Write your message here...", "my-checkout-fields")} rows={3} maxLength={200} /> <span className="character-count">{giftMessage.length}/200</span> </div> ); }; registerCheckoutBlock({ metadata: { name: "my-checkout-fields/gift-message", title: "Gift Message", category: "woocommerce", parent: ["woocommerce/checkout-shipping-methods-block"], attributes: {}, }, component: Block, }); -
扩展 Store API 以持久化自定义数据
<?php // Extend the Store API Cart schema to accept and store custom extension data add_action('woocommerce_blocks_loaded', function () { woocommerce_store_api_register_endpoint_data([ 'endpoint' => Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema::IDENTIFIER, 'namespace' => 'my-checkout-fields', 'schema_callback' => function () { return [ 'gift_message' => [ 'description' => 'Gift message for the order', 'type' => 'string', 'context' => ['view', 'edit'], 'readonly' => false, 'sanitize_callback' => 'sanitize_textarea_field', ], ]; }, 'schema_type' => ARRAY_A, ]); // Save the gift message to WC session when cart is updated woocommerce_store_api_register_update_callback([ 'namespace' => 'my-checkout-fields', 'callback' => function (array $data) { if (isset($data['gift_message'])) { WC()->session->set('gift_message', sanitize_textarea_field($data['gift_message'])); } }, ]); }); // Transfer session data to order meta on checkout add_action('woocommerce_checkout_order_created', function ($order) { $gift_message = WC()->session->get('gift_message', ''); if (!empty($gift_message)) { $order->update_meta_data('_gift_message', $gift_message); $order->save(); } }); -
使用 SlotFills 注入 UI,无需内部区块
SlotFills 比内部区块更简单,可将内容注入预定义的插槽:
// src/frontend.js import { registerPlugin } from "@wordpress/plugins"; import { ExperimentalOrderMeta } from "@woocommerce/blocks-checkout"; import { __ } from "@wordpress/i18n"; import { useSelect } from "@wordpress/data"; import { CART_STORE_KEY } from "@woocommerce/block-data"; const CartUpsellBanner = () => { const cartTotal = useSelect((select) => { const cart = select(CART_STORE_KEY).getCartData(); return cart?.totals?.total_items; }); const freeShippingThreshold = 5000; // $50.00 in cents const remaining = freeShippingThreshold - parseInt(cartTotal ?? "0"); if (remaining <= 0) return null; return ( <div className="free-shipping-banner"> {__(`Add $${(remaining / 100).toFixed(2)} more for free shipping!`, "my-checkout-fields")} </div> ); }; registerPlugin("my-cart-upsell", { render: () => ( <ExperimentalOrderMeta> <CartUpsellBanner /> </ExperimentalOrderMeta> ), scope: "woocommerce-checkout", }); -
使用
@wordpress/scripts构建并加载资源// package.json { "scripts": { "build": "wp-scripts build src/frontend.js src/blocks/gift-message/index.js", "start": "wp-scripts start src/frontend.js src/blocks/gift-message/index.js" }, "devDependencies": { "@wordpress/scripts": "^30.0.0" } }// src/blocks/gift-message/block.json { "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "my-checkout-fields/gift-message", "title": "Gift Message", "category": "woocommerce", "parent": ["woocommerce/checkout-shipping-methods-block"], "textdomain": "my-checkout-fields", "editorScript": "file:../../build/blocks/gift-message/index.js", "script": "file:../../build/blocks/gift-message/index.js", "style": "file:../../build/blocks/gift-message/style-index.css" }
示例
结账字段验证
自定义结账数据应通过 Store API 更新回调在服务端进行验证。客户端筛选器(__experimentalRegisterCheckoutFilters)只能修改显示值(例如价格格式),不能验证表单字段。
<?php
// Server-side validation in the Store API update callback
woocommerce_store_api_register_update_callback([
'namespace' => 'my-checkout-fields',
'callback' => function (array $data) {
if (isset($data['gift_message'])) {
$message = sanitize_textarea_field($data['gift_message']);
// Reject messages that contain URLs
if (preg_match('#https?://#i', $message)) {
throw new \Automattic\WooCommerce\StoreApi\Exceptions\RouteException(
'invalid_gift_message',
__('Gift messages cannot contain links.', 'my-checkout-fields'),
400
);
}
WC()->session->set('gift_message', $message);
}
},
]);
在客户端,处理 extensionCartUpdate 返回的错误响应,以显示验证消息:
import { extensionCartUpdate } from "@woocommerce/blocks-checkout";
const handleChange = async (e) => {
const value = e.target.value;
setGiftMessage(value);
try {
await extensionCartUpdate({
namespace: "my-checkout-fields",
data: { gift_message: value },
});
setError(null);
} catch (err) {
setError(err.message);
}
};
在特定购物车条件下禁用某种支付方式
<?php
// Hide "Pay Later" payment method if cart contains digital-only products
add_filter(
'woocommerce_blocks_payment_method_type_registration',
function ($payment_method_registry) {
$payment_method_registry->register(
new class implements \Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface {
public function is_active() { return true; }
public function get_payment_method_script_handles() { return []; }
public function get_payment_method_data() { return []; }
public function get_name() { return 'custom-payment-guard'; }
public function initialize() {}
}
);
return $payment_method_registry;
}
);
add_filter('__experimental_woocommerce_blocks_payment_gateway_features_list', function ($features, $name) {
if ($name === 'pay-later') {
// Check if all items in cart are virtual/downloadable
$cart_has_physical = false;
foreach (WC()->cart->get_cart() as $item) {
$product = $item['data'];
if (!$product->is_virtual() && !$product->is_downloadable()) {
$cart_has_physical = true;
break;
}
}
if (!$cart_has_physical) {
$features['available'] = false;
}
}
return $features;
}, 10, 2);
最佳实践
- 对于必须在页面重新加载后保留的数据,使用
extensionCartUpdate,而不是本地状态——它会通过 Store API 将数据存储在 WooCommerce 会话中 - 在服务端清理所有自定义数据——对于任何写入订单元数据的数据,PHP 的
sanitize_textarea_field、sanitize_text_field和absint都不可或缺 - 使用
@wordpress/scripts构建——它会为 WordPress/Gutenberg 项目自动处理依赖提取、webpack 配置和资源版本管理 - 为内部区块使用
block.json——区块注册表要求提供block.json清单;它还支持在编辑器中自动加载资源 - 同时测试经典结账和区块结账——某些商店仍可能使用简码结账;仅在检测到区块结账时才有条件地加载脚本
- 使用
woocommerce_store_api_register_update_callback进行服务端验证——客户端验证可能被绕过;始终在回调中重新验证扩展数据 - 为所有元数据键和命名空间添加插件别名作为前缀,以避免与其他插件冲突
常见问题
| 问题 | 解决方案 |
|---|---|
| 编辑器中未显示内部区块 | 确保 block.json 中的 parent 与目标结账步骤的区块名称完全匹配;使用浏览器开发者工具确认父区块名称 |
| 扩展数据未持久化到订单 | 添加 woocommerce_checkout_order_created 钩子,将会话数据转移到订单元数据中——Store API 会话数据不会自动复制到订单 |
| SlotFill 组件未渲染 | 在 registerPlugin 中必须指定 scope: "woocommerce-checkout";省略该项或使用错误的范围会导致组件无法渲染,且不会显示错误 |
使用 @wordpress/scripts 时出现构建错误 | 必须在 package.json 脚本或 wp-scripts.config.js 中指定入口点;默认情况下只会构建 src/index.js |
| WooCommerce 降级后区块失效 | 在 composer.json 中固定 @woocommerce/blocks-checkout 软件包版本,使其与已安装的 WooCommerce 版本一致 |
useSelect(CART_STORE_KEY) 返回 undefined | 确保 @woocommerce/block-data 软件包位于 wp_register_script 的 dependencies 数组中——该数据存储并非全局可用 |
相关技能
- @woocommerce-plugin-development
- @woocommerce-rest-api
- @gutenberg-block-development
- @checkout-flow-optimization
- @woocommerce-subscriptions
评论
支持 Markdown