mcpField
W
woocommerce-blocks

woocommerce-blocks

github.com/finsilabs开发6076 次安装5 天前更新
代码仓库

关于 woocommerce-blocks

用于扩展 WooCommerce React 购物车与结账区块的开发技能,指导插件开发者添加结账字段、注入界面、保存自定义数据及调整支付行为。

主要功能

  1. 01注册结账内嵌区块
  2. 02通过 SlotFill 注入界面
  3. 03使用 Store API 保存数据
  4. 04服务端字段验证
  5. 05自定义支付方式

适用场景

  1. 01在结账时添加礼品或配送字段
  2. 02在购物车或结账页展示促销内容
  3. 03创建自定义结账步骤
  4. 04按条件停用支付方式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill woocommerce-blocks

安装后在 Claude Code 中输入/woocommerce-blocks确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/build-configuration-and-client-side-chec/
criteria.json
task.md
evals/inner-block-registration-and-store-api-p/
criteria.json
task.md
evals/slotfill-ui-injection-and-cart-data-acce/
criteria.json
task.md

详细介绍

WooCommerce Blocks

概述

WooCommerce Blocks 使用由 React 驱动的 Gutenberg 区块,取代基于经典简码的购物车和结账流程。自定义插件可以通过注册内部区块(结账步骤中的自定义字段)、使用 SlotFills(将 UI 注入预定义的注入点),以及扩展 Store API 来保存和检索自定义数据,从而扩展结账区块。自版本 8.3 起,基于区块的结账流程是新建 WooCommerce 商店的默认选项。

何时使用此技能

  • 为结账表单添加自定义字段(礼品留言、送货日期选择器、增值税号)
  • 向购物车或结账区块注入促销内容或追加销售横幅
  • 创建包含额外业务逻辑的自定义结账步骤
  • 在现有 WooCommerce 网站上替换旧版简码结账流程
  • 构建无需修改核心模板即可扩展结账行为的插件

核心说明

  1. 注册结账内部区块

    内部区块是呈现在结账步骤中的 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 [];
        }
    }
    
  2. 创建内部区块 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,
    });
    
  3. 扩展 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();
        }
    });
    
  4. 使用 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",
    });
    
  5. 使用 @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