mcpField
W
woocommerce-blocks

woocommerce-blocks

github.com/finsilabs開発6076 回インストール6 日前に更新
リポジトリ

woocommerce-blocks について

WooCommerce の React ベースのカートおよびチェックアウトブロックを拡張する開発スキルです。プラグイン開発者向けに、項目や UI、独自データ、決済動作の追加方法を案内します。

主な機能

  1. 01チェックアウト内ブロックの登録
  2. 02SlotFill による UI の挿入
  3. 03Store 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 を拡張してカスタムデータを保存・取得することで、Checkout Block を拡張できます。バージョン 8.3 以降、新しい WooCommerce ストアではブロックベースのチェックアウトがデフォルトになっています。

このスキルを使用する場面

  • チェックアウトフォームにカスタムフィールド(ギフトメッセージ、配送日の選択、VAT 番号)を追加するとき
  • カートまたはチェックアウトブロックに、プロモーションコンテンツやアップセルバナーを挿入するとき
  • 追加のビジネスロジックを備えたカスタムのチェックアウトステップを作成するとき
  • 既存の WooCommerce サイトで、従来のショートコードチェックアウトを置き換えるとき
  • コアテンプレートを変更せずにチェックアウトの動作を拡張するプラグインを構築するとき

基本手順

  1. Checkout Inner Block を登録する

    内部ブロックは、チェックアウトのステップ内にレンダリングされる 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. 内部ブロックを使わずに UI を挿入するため、SlotFills を使用する

    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 を返すwp_register_script の dependencies 配列に @woocommerce/block-data パッケージが含まれていることを確認してください。ストアはグローバルでは利用できません

関連スキル

  • @woocommerce-plugin-development
  • @woocommerce-rest-api
  • @gutenberg-block-development
  • @checkout-flow-optimization
  • @woocommerce-subscriptions

コメント