mcpField
W
woocommerce-subscriptions

woocommerce-subscriptions

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

woocommerce-subscriptions について

定期課金向けの公式有料プラグイン、WooCommerce Subscriptionsを拡張する開発者向けスキルです。サブスクリプションのライフサイクルフック、プログラムによる照会、プラン変更、決済再試行、カスタムメールを扱います。

主な機能

  1. 01サブスクリプション商品の作成
  2. 02ライフサイクルフックとステータス処理
  3. 03プログラムによるサブスクリプション照会
  4. 04アップグレードとダウングレードの日割り計算
  5. 05決済再試行とカスタムメール

活用シーン

  1. 01定期課金プランの構築
  2. 02サブスクリプション状態に応じたアクセス許可
  3. 03更新と決済失敗のワークフローのカスタマイズ

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/woocommerce-subscriptionsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
evals/failed-payment-retry-suspension-and-rein/
criteria.json
task.md
evals/plan-switching-with-proration-and-safe-c/
criteria.json
task.md
evals/subscription-product-creation-and-lifecy/
criteria.json
task.md

詳細

WooCommerce Subscriptions

概要

WooCommerce Subscriptions(公式の有料プラグイン)は、サブスクリプション商品タイプ(シンプルなサブスクリプションとバリエーションのあるサブスクリプション)を追加し、請求間隔(日次、週次、月次、年次)、無料トライアル、登録料、日割り計算によるアップグレード/ダウングレードを設定できます。Stripe、PayPal Reference Transactions、自動継続課金に対応するその他の決済ゲートウェイと連携します。豊富な WordPress アクションとフィルターを利用して、サブスクリプションのライフサイクルに独自のロジックを組み込めます。

このスキルを使う場面

  • 商品やサービスを継続課金方式で販売するとき(SaaS、メンバーシップ、定期購入ボックス)
  • サブスクリプションのアップグレード、ダウングレード、プラン切り替えを実装するとき
  • 独自の更新ロジックを構築したり、決済失敗時の再試行に関するビジネスルールを追加したりするとき
  • サブスクリプションのステータスをアクセス制御と連携させるとき(例:メンバーシップサイトのコンテンツ制限)
  • サブスクリプションメールや管理画面のレポートを独自データで拡張するとき
  • サブスクリプションのアドオンや、数量に応じた単価調整を作成するとき

基本的な手順

  1. プログラムからサブスクリプション商品を作成する

    <?php
    // Create a simple subscription product via code (e.g., in a migration script)
    $product = new WC_Product_Subscription();
    $product->set_name('Monthly Pro Plan');
    $product->set_status('publish');
    $product->set_regular_price('29.99');
    
    // Save first so the product gets an ID
    $product->save();
    
    // Subscription-specific meta (product must be saved before setting post meta)
    update_post_meta($product->get_id(), '_subscription_price', '29.99');
    update_post_meta($product->get_id(), '_subscription_period', 'month');
    update_post_meta($product->get_id(), '_subscription_period_interval', '1');
    update_post_meta($product->get_id(), '_subscription_length', '0'); // 0 = forever
    update_post_meta($product->get_id(), '_subscription_trial_length', '14');
    update_post_meta($product->get_id(), '_subscription_trial_period', 'day');
    update_post_meta($product->get_id(), '_subscription_sign_up_fee', '0');
    
  2. サブスクリプションのライフサイクルイベントにフックする

    WooCommerce Subscriptions は、ライフサイクルの各段階で特定のアクションを実行します。

    <?php
    
    // When a new subscription is created (after successful first payment)
    add_action('woocommerce_subscription_status_active', function ($subscription) {
        $user_id = $subscription->get_user_id();
        $plan = $subscription->get_items(); // Array of WC_Order_Item_Product
    
        // Grant access — e.g., set user role or update capabilities
        $user = new WP_User($user_id);
        $user->add_role('subscriber_member');
    
        // Track in analytics
        error_log("Subscription {$subscription->get_id()} activated for user {$user_id}");
    });
    
    // When a renewal payment succeeds
    add_action('woocommerce_subscription_renewal_payment_complete', function ($subscription, $last_order) {
        $user_id = $subscription->get_user_id();
        // Extend access, send renewal receipt, update CRM
        do_action('my_plugin_renewal_processed', $user_id, $subscription);
    }, 10, 2);
    
    // When a renewal payment fails
    add_action('woocommerce_subscription_payment_failed', function ($subscription, $last_order) {
        $user_id = $subscription->get_user_id();
        $retry_count = $subscription->get_failed_payment_count();
    
        // Notify user and optionally pause access
        if ($retry_count >= 2) {
            $user = new WP_User($user_id);
            $user->remove_role('subscriber_member');
            // Send dunning email
            $subscription->update_status('on-hold');
        }
    }, 10, 2);
    
    // When subscription is cancelled
    add_action('woocommerce_subscription_status_cancelled', function ($subscription) {
        $user_id = $subscription->get_user_id();
        $user = new WP_User($user_id);
        $user->remove_role('subscriber_member');
    });
    
  3. プログラムからサブスクリプションを検索する

    <?php
    
    // Get all active subscriptions for a user
    function get_user_active_subscriptions(int $user_id): array {
        return wcs_get_users_subscriptions($user_id, ['active']);
    }
    
    // Check if a user has an active subscription to a specific product
    function user_has_active_subscription_to_product(int $user_id, int $product_id): bool {
        $subscriptions = wcs_get_subscriptions_for_product($product_id, 'any', ['customer_id' => $user_id]);
        foreach ($subscriptions as $subscription) {
            if ($subscription->has_status('active')) {
                return true;
            }
        }
        return false;
    }
    
    // Get subscription by ID
    function get_subscription_details(int $subscription_id): ?WC_Subscription {
        $subscription = wcs_get_subscription($subscription_id);
        if (!$subscription) return null;
    
        return $subscription;
    }
    
    // Usage
    $subscription = get_subscription_details(1234);
    if ($subscription) {
        echo $subscription->get_status();           // 'active', 'on-hold', 'cancelled', etc.
        echo $subscription->get_next_payment_date(); // ISO 8601 date
        echo $subscription->get_total();            // Current billing amount
    }
    
  4. プランのアップグレードとダウングレードを処理する

    <?php
    
    // Add proration logic for plan switches
    add_filter('woocommerce_subscriptions_switch_proration', function ($proration_amount, $subscription, $new_order, $product, $switch_cart_item) {
        // Custom proration: charge/credit based on days remaining in billing period
        $next_payment = strtotime($subscription->get_next_payment_date());
        $last_payment = $subscription->get_date('last_payment');
        $billing_period_days = ($next_payment - strtotime($last_payment)) / DAY_IN_SECONDS;
        $days_remaining = ($next_payment - time()) / DAY_IN_SECONDS;
    
        $old_daily_rate = (float)$subscription->get_total() / $billing_period_days;
        $new_product_price = (float)$switch_cart_item['data']->get_price();
        $new_daily_rate = $new_product_price / $billing_period_days;
    
        // Credit remaining days at old rate, charge at new rate
        $proration_amount = ($new_daily_rate - $old_daily_rate) * $days_remaining;
    
        return round($proration_amount, 2);
    }, 10, 5);
    
    // Trigger a plan switch programmatically
    function switch_subscription_plan(int $subscription_id, int $new_product_id): bool {
        $subscription = wcs_get_subscription($subscription_id);
        if (!$subscription) return false;
    
        // Remove existing item and add new product
        foreach ($subscription->get_items() as $item_id => $item) {
            $subscription->remove_item($item_id);
        }
    
        $item = new WC_Order_Item_Product();
        $item->set_product(wc_get_product($new_product_id));
        $item->set_quantity(1);
        $subscription->add_item($item);
    
        // Recalculate totals
        $subscription->calculate_totals();
        $subscription->save();
    
        return true;
    }
    
  5. 決済失敗時に再試行し、請求日を同期する

    <?php
    
    // Trigger an immediate payment retry (e.g., from an admin action)
    function retry_failed_subscription_payment(int $subscription_id): bool {
        $subscription = wcs_get_subscription($subscription_id);
        if (!$subscription || !$subscription->has_status('on-hold')) {
            return false;
        }
    
        // Create a renewal order and attempt payment
        $renewal_order = wcs_create_renewal_order($subscription);
        if (is_wp_error($renewal_order)) {
            return false;
        }
    
        // Process payment using the subscription's payment method
        $payment_gateway = wc_get_payment_gateway_by_order($subscription);
        if ($payment_gateway && method_exists($payment_gateway, 'scheduled_subscription_payment')) {
            $payment_gateway->scheduled_subscription_payment(
                $renewal_order->get_total(),
                $renewal_order
            );
        }
    
        return true;
    }
    
    // Synchronize all active subscriptions to renew on the 1st of the month
    add_filter('woocommerce_subscriptions_synced_next_payment_date', function ($next_payment_date, $product, $from_timestamp, $trial_end_timestamp) {
        if ($product->get_meta('_subscription_period') === 'month') {
            $next_month = date('Y-m-01', strtotime('+1 month'));
            return strtotime($next_month);
        }
        return $next_payment_date;
    }, 10, 4);
    

例

一時停止と再開のフロー
<?php

// Admin-triggered suspension with reason logging
function suspend_subscription_with_reason(int $subscription_id, string $reason): void {
    $subscription = wcs_get_subscription($subscription_id);
    if (!$subscription) return;

    // Store suspension reason as meta before status change
    $subscription->update_meta_data('_suspension_reason', $reason);
    $subscription->update_meta_data('_suspension_date', current_time('mysql'));
    $subscription->save();

    // Change status to on-hold (fires woocommerce_subscription_status_on-hold action)
    $subscription->update_status('on-hold', sprintf('Suspended: %s', $reason));
}

// Reinstate and reset payment date
function reinstate_subscription(int $subscription_id): void {
    $subscription = wcs_get_subscription($subscription_id);
    if (!$subscription || !$subscription->has_status('on-hold')) return;

    // Reset next payment date to avoid immediately triggering a renewal
    $subscription->set_date('next_payment', strtotime('+1 month'));

    // Activate subscription
    $subscription->update_status('active', 'Reinstated by admin');
    $subscription->save();
}
独自のサブスクリプションメール
<?php

// Add a custom "Renewal Reminder" email 7 days before renewal
add_filter('woocommerce_email_classes', function ($email_classes) {
    require_once plugin_dir_path(__FILE__) . 'class-renewal-reminder-email.php';
    $email_classes['My_Renewal_Reminder_Email'] = new My_Renewal_Reminder_Email();
    return $email_classes;
});

// Schedule the emails via WooCommerce action scheduler
add_action('woocommerce_scheduled_subscription_payment', function ($subscription_id) {
    $subscription = wcs_get_subscription($subscription_id);
    if (!$subscription) return;

    // Schedule reminder 7 days before next payment
    $next_payment = strtotime($subscription->get_next_payment_date()) - (7 * DAY_IN_SECONDS);
    if ($next_payment > time()) {
        as_schedule_single_action(
            $next_payment,
            'my_plugin_send_renewal_reminder',
            [['subscription_id' => $subscription_id]]
        );
    }
}, 10);

ベストプラクティス

  • すべての非同期サブスクリプションタスクに Action Scheduler(WooCommerce に同梱)を使用してください。決済に関わる重要な処理で WP cron に頼らないでください。
  • 必ず wcs_get_subscription() を null チェックとともに使用してください。特殊なケース(手動削除、インポート失敗など)では、サブスクリプションが削除されていたり、見つからなかったりする可能性があります。
  • 決済ゲートウェイのトークン処理をテストしてください。サブスクリプションの更新には保存済みの決済トークンが必要です。更新時にゲートウェイが登録済みのカードに正しく課金することをテストしてください。
  • 決済失敗時の督促処理を明示的に設定してください。デフォルトの再試行スケジュールは、失敗から 1 日後、4 日後、7 日後です。wcs_retry_rules フィルターでビジネスポリシーに合わせてカスタマイズしてください。
  • すべてのサブスクリプションのステータス変更を記録してください。監査やカスタマーサポートのために、ステータス変更履歴を注文メモまたは独自のテーブルに保存してください。
  • プラン切り替え時の日割り計算を考慮してください。同じ期間に対して顧客に二重請求しないでください。組み込みの日割り計算を使うか、独自のロジックを実装してください。
  • アップグレード/ダウングレードの特殊なケースをテストしてください。特に、トライアル中の場合や請求期間が変わる場合(月次から年次)に注意してください。
  • アクセス制御には wcs_user_has_subscription() を使用してください。ユーザーロールだけを確認するよりも確実です。

よくある問題

問題解決策
更新時の決済が処理されない決済ゲートウェイの機能に supports[] = subscription が含まれているか確認してください。ゲートウェイはサブスクリプションへの対応を明示的に宣言する必要があります。
決済成功後もサブスクリプションのステータスが「pending」のままwoocommerce_payment_complete アクションが実行される必要があります。決済ゲートウェイが成功時に $order->payment_complete() を呼び出しているか確認してください。
日割り計算の結果、請求額がマイナスになる日割り計算フィルターの戻り値に 0.00 の最低額を設定してください。負の金額は、ほとんどの決済処理でゲートウェイエラーの原因になります。
wcs_get_subscriptions_for_product が空を返すバリエーションの場合は、商品の親 ID を渡してください。WCS はサブスクリプション項目にバリエーション ID ではなく、親商品の ID を保存します。
トライアル終了後に更新時の課金が行われない_subscription_trial_length メタが 0 より大きい数値に設定されていること、および初回注文時に決済ゲートウェイのトークンが正しく保存されていることを確認してください。
管理画面でのステータス変更時にキャンセルメールが送信されるプログラムからキャンセルする前に woocommerce_subscription_status_cancelled の remove_action を追加し、メールを抑制する必要がある場合は、キャンセル後に再度追加してください。

Related Skills

  • @woocommerce-plugin-development
  • @woocommerce-rest-api
  • @subscription-billing
  • @stripe-integration
  • @woocommerce-blocks

コメント