2026年8月1日

2026年8月1日

WordPressのWooCommerce商品タイプをカスタマイズする方法

はじめに

「WooCommerceに「サブスクリプション商品」や「予約商品」など独自の商品タイプを追加したい」「商品データタブに専用フィールドを追加したい」「特定の商品タイプだけの購入フローを実装したい」——WC_Productを継承してカスタム商品タイプを実装できます。

症状・原因

WooCommerceのデフォルト商品タイプ(シンプル・バリアブル・グループ化・外部)では表現できない商品形態が必要な場合、カスタム商品タイプを実装します。サブスクリプション・デジタルコンテンツ・予約商品などが代表例です。

解決手順

ステップ1:WC_Productを継承したクラスを作成する

// includes/class-wc-product-subscription.php
if ( ! defined( 'ABSPATH' ) ) exit;

class WC_Product_Subscription extends WC_Product {

    // 商品タイプ名(クラス名から自動導出: wc-product-{type})
    public string $product_type = 'subscription';

    public function __construct( $product ) {
        $this->supports[] = 'ajax_add_to_cart';

        parent::__construct( $product );
    }

    // 商品タイプを返す
    public function get_type(): string {
        return 'subscription';
    }

    // サブスクリプション固有のゲッター
    public function get_subscription_period(): string {
        return $this->get_meta( '_subscription_period', true ) ?: 'month';
    }

    public function get_subscription_length(): int {
        return (int) $this->get_meta( '_subscription_length', true );
    }

    public function get_subscription_price(): float {
        return (float) $this->get_meta( '_subscription_price', true );
    }

    // サブスクリプション固有のセッター
    public function set_subscription_period( string $period ): void {
        $allowed = [ 'day', 'week', 'month', 'year' ];
        $this->update_meta_data(
            '_subscription_period',
            in_array( $period, $allowed, true ) ? $period : 'month'
        );
    }

    public function set_subscription_length( int $length ): void {
        $this->update_meta_data( '_subscription_length', max( 0, $length ) );
    }

    public function set_subscription_price( float $price ): void {
        $this->update_meta_data( '_subscription_price', wc_format_decimal( $price ) );
    }

    // 価格HTML(フロントエンドに表示する価格)
    public function get_price_html( string $price = '' ): string {
        $period   = $this->get_subscription_period();
        $label    = [ 'day' => '日', 'week' => '週', 'month' => '月', 'year' => '年' ];
        $price_html = wc_price( $this->get_subscription_price() );
        return sprintf( '%s / %s', $price_html, $label[ $period ] ?? '月' );
    }
}

ステップ2:商品タイプをWooCommerceに登録する

// my-subscription-plugin.php: プラグインのメインファイル

// クラスファイルを読み込む
add_action( 'plugins_loaded', function(): void {
    if ( ! class_exists( 'WC_Product' ) ) {
        return;
    }
    require_once plugin_dir_path( __FILE__ ) . 'includes/class-wc-product-subscription.php';
} );

// 商品タイプとしてWooCommerceに登録
add_filter( 'product_type_selector', function( array $types ): array {
    $types['subscription'] = 'サブスクリプション';
    return $types;
} );

// WC_Productクラスのオートロードに登録
add_filter( 'woocommerce_product_class', function( string $classname, string $product_type ): string {
    if ( 'subscription' === $product_type ) {
        return 'WC_Product_Subscription';
    }
    return $classname;
}, 10, 2 );

ステップ3:商品データタブとフィールドを追加する

// functions.php: 商品データパネルにサブスクリプションタブを追加

// 商品データタブを追加
add_filter( 'woocommerce_product_data_tabs', function( array $tabs ): array {
    $tabs['subscription'] = [
        'label'    => 'サブスクリプション',
        'target'   => 'subscription_product_data',
        'class'    => [ 'show_if_subscription' ],  // このタブを表示するクラス
        'priority' => 21,
    ];
    return $tabs;
} );

// 商品データパネルのHTMLを出力
add_action( 'woocommerce_product_data_panels', function(): void {
    global $post;
    $product = wc_get_product( $post->ID );
    if ( ! $product instanceof WC_Product_Subscription ) {
        $product = null;
    }
    ?>
    <div id="subscription_product_data" class="panel woocommerce_options_panel">
        <div class="options_group">
            <?php
            woocommerce_wp_text_input( [
                'id'          => '_subscription_price',
                'label'       => 'サブスクリプション価格(¥)',
                'value'       => $product ? $product->get_subscription_price() : '',
                'type'        => 'number',
                'custom_attributes' => [ 'min' => 0, 'step' => 1 ],
            ] );

            woocommerce_wp_select( [
                'id'      => '_subscription_period',
                'label'   => '請求サイクル',
                'value'   => $product ? $product->get_subscription_period() : 'month',
                'options' => [
                    'day'   => '毎日',
                    'week'  => '毎週',
                    'month' => '毎月',
                    'year'  => '毎年',
                ],
            ] );

            woocommerce_wp_text_input( [
                'id'          => '_subscription_length',
                'label'       => '契約期間(0=無期限)',
                'value'       => $product ? $product->get_subscription_length() : 0,
                'type'        => 'number',
                'custom_attributes' => [ 'min' => 0, 'step' => 1 ],
            ] );
            ?>
        </div>
    </div>
    <?php
} );

// フィールドの保存
add_action( 'woocommerce_process_product_meta', function( int $post_id ): void {
    $product = wc_get_product( $post_id );
    if ( ! $product instanceof WC_Product_Subscription ) {
        return;
    }
    if ( isset( $_POST['_subscription_price'] ) ) {
        $product->set_subscription_price( (float) wc_clean( $_POST['_subscription_price'] ) );
    }
    if ( isset( $_POST['_subscription_period'] ) ) {
        $product->set_subscription_period( wc_clean( $_POST['_subscription_period'] ) );
    }
    if ( isset( $_POST['_subscription_length'] ) ) {
        $product->set_subscription_length( (int) wc_clean( $_POST['_subscription_length'] ) );
    }
    $product->save();
} );

ステップ4:フロントエンドの表示とカートへの追加

// functions.php: サブスクリプション商品のカート追加をカスタマイズ

// カートに追加するときのバリデーション
add_filter( 'woocommerce_add_to_cart_validation', function(
    bool $passed, int $product_id, int $quantity
): bool {
    $product = wc_get_product( $product_id );
    if ( ! $product instanceof WC_Product_Subscription ) {
        return $passed;
    }

    // サブスクリプションはカートに1つだけ
    foreach ( WC()->cart->get_cart() as $cart_item ) {
        if ( $cart_item['product_id'] === $product_id ) {
            wc_add_notice( 'このサブスクリプションはすでにカートに入っています。', 'error' );
            return false;
        }
    }

    return $passed;
}, 10, 3 );

// カートアイテムの価格を設定
add_action( 'woocommerce_before_calculate_totals', function( WC_Cart $cart ): void {
    foreach ( $cart->get_cart() as $cart_item ) {
        $product = $cart_item['data'];
        if ( $product instanceof WC_Product_Subscription ) {
            $product->set_price( $product->get_subscription_price() );
        }
    }
} );

ステップ5:JavaScriptで商品タイプ別の表示制御

// admin/js/product-type.js: 商品タイプ変更時の表示制御
jQuery( function( $ ) {
    // 商品タイプ切り替え時にサブスクリプション用フィールドを表示/非表示
    $( '#product-type' ).on( 'change', function() {
        var type = $( this ).val();
        if ( type === 'subscription' ) {
            $( '.show_if_subscription' ).show();
            $( '.hide_if_subscription' ).hide();
            // 通常価格フィールドを非表示
            $( '._regular_price_field' ).hide();
            $( '._sale_price_field' ).hide();
        } else {
            $( '.show_if_subscription' ).hide();
        }
    } ).trigger( 'change' );
} );

注意事項

  • product_typeプロパティ(public string $product_type = 'subscription')はクラス定義と一致させる必要があります。これがWooCommerceの商品タイプ検出に使用されます。
  • カスタム商品タイプのカートへの追加・決済処理はwoocommerce_before_calculate_totalswoocommerce_checkout_order_processedフックで制御できます。本格的なサブスクリプション機能にはWooCommerce Subscriptionsプラグインの使用を検討してください。
  • woocommerce_product_classフィルターはWooCommerce 3.0以降で使用できます。

まとめ

WooCommerceカスタム商品タイプの実装は「WC_Productを継承したクラスを作成→get_type()で商品タイプ名を返す→product_type_selectorフィルターで商品タイプドロップダウンに追加→woocommerce_product_classフィルターでクラスをマッピング→woocommerce_product_data_tabs/panelsフックで管理画面タブを追加→woocommerce_process_product_metaで保存」の流れで整備します。関連記事:WordPressのWooCommerceチェックアウトをカスタマイズする方法WordPressでWooCommerceカスタム決済ゲートウェイを実装する方法

お気軽にご相談ください

お見積りへ お問い合わせへ