2026年6月1日

2026年6月1日

WooCommerceのカスタム決済ゲートウェイを実装する方法

はじめに

WooCommerceには標準でStripe・PayPalなどの決済手段が用意されていますが、日本国内の決済サービス(GMOペイメント・SBペイメント・PAY.JP等)や独自の決済フローを実装するには、カスタム決済ゲートウェイを作成する必要があります。本記事では WC_Payment_Gateway クラスを継承して完全な決済ゲートウェイを実装する方法を解説します。

症状・原因

  • 国内の決済サービスに対応した WooCommerce ゲートウェイが存在しない
  • 既存のプラグインでは社内システムとの連携ができない
  • 決済フロー(リダイレクト型・API型・後払い型)をカスタマイズしたい
  • Webhook / IPN コールバックの処理方法がわからない
  • テスト環境と本番環境で異なる認証情報を管理したい

解決手順

ステップ1:WC_Payment_Gateway を継承したクラスの基本構造を作成する

<?php
// my-payment-gateway/class-wc-my-payment-gateway.php

if ( ! defined( 'ABSPATH' ) ) {
    exit; // WordPress 外からの直接アクセスを防ぐ
}

/**
 * カスタム決済ゲートウェイクラス
 * WC_Payment_Gateway を継承して実装する
 */
class WC_My_Payment_Gateway extends WC_Payment_Gateway {

    /**
     * コンストラクタ:基本プロパティを設定する
     */
    public function __construct() {
        // ゲートウェイの識別子(一意の値を設定すること)
        $this->id = 'my_payment_gateway';

        // チェックアウトページに表示するアイコン
        $this->icon = plugin_dir_url( __FILE__ ) . 'assets/logo.png';

        // 決済フォームを直接表示するか(false = 外部にリダイレクト)
        $this->has_fields = true;

        // 管理画面に表示する名前と説明
        $this->method_title       = __( 'カスタム決済', 'my-payment-gateway' );
        $this->method_description = __( '独自の決済ゲートウェイを使用します。', 'my-payment-gateway' );

        // 対応する機能(refunds を追加すると返金処理が可能)
        $this->supports = [
            'products',
            'refunds',
        ];

        // 管理画面の設定フィールドを初期化する
        $this->init_form_fields();

        // 保存された設定を読み込む
        $this->init_settings();

        // 設定値をプロパティにセットする
        $this->title       = $this->get_option( 'title' );
        $this->description = $this->get_option( 'description' );
        $this->enabled     = $this->get_option( 'enabled' );
        $this->test_mode   = 'yes' === $this->get_option( 'test_mode' );
        $this->api_key     = $this->test_mode
            ? $this->get_option( 'test_api_key' )
            : $this->get_option( 'live_api_key' );

        // 設定保存のフックを登録する
        add_action(
            'woocommerce_update_options_payment_gateways_' . $this->id,
            [ $this, 'process_admin_options' ]
        );
    }
}

ステップ2:init_form_fields() でゲートウェイ設定フィールドを定義する

<?php
// WC_My_Payment_Gateway クラスのメソッドとして追加する

/**
 * 管理画面の設定フィールドを定義する
 * WooCommerce → 設定 → 支払い → カスタム決済 で表示される
 */
public function init_form_fields(): void {
    $this->form_fields = [
        // 決済手段の有効/無効
        'enabled' => [
            'title'   => __( '有効化', 'my-payment-gateway' ),
            'type'    => 'checkbox',
            'label'   => __( 'この決済手段を有効化する', 'my-payment-gateway' ),
            'default' => 'no',
        ],

        // チェックアウトページに表示するタイトル
        'title' => [
            'title'       => __( '表示名', 'my-payment-gateway' ),
            'type'        => 'text',
            'description' => __( 'チェックアウトページに表示される決済方法の名前。', 'my-payment-gateway' ),
            'default'     => __( 'クレジットカード決済', 'my-payment-gateway' ),
            'desc_tip'    => true,
        ],

        // 顧客向けの説明文
        'description' => [
            'title'   => __( '説明', 'my-payment-gateway' ),
            'type'    => 'textarea',
            'default' => __( 'クレジットカードで安全にお支払いいただけます。', 'my-payment-gateway' ),
        ],

        // テストモード切り替え
        'test_mode' => [
            'title'   => __( 'テストモード', 'my-payment-gateway' ),
            'type'    => 'checkbox',
            'label'   => __( 'テスト環境を使用する', 'my-payment-gateway' ),
            'default' => 'yes',
        ],

        // 本番用 API キー
        'live_api_key' => [
            'title'       => __( '本番 API キー', 'my-payment-gateway' ),
            'type'        => 'password',
            'description' => __( '決済サービスの本番用APIキーを入力してください。', 'my-payment-gateway' ),
            'default'     => '',
        ],

        // テスト用 API キー
        'test_api_key' => [
            'title'       => __( 'テスト API キー', 'my-payment-gateway' ),
            'type'        => 'password',
            'description' => __( '決済サービスのテスト用APIキーを入力してください。', 'my-payment-gateway' ),
            'default'     => '',
        ],

        // Webhook シークレット
        'webhook_secret' => [
            'title'       => __( 'Webhook シークレット', 'my-payment-gateway' ),
            'type'        => 'password',
            'description' => __( 'IPN/Webhook の署名検証に使用するシークレット。', 'my-payment-gateway' ),
            'default'     => '',
        ],
    ];
}

ステップ3:process_payment() で支払い処理を実装する

<?php
// WC_My_Payment_Gateway クラスのメソッドとして追加する

/**
 * 支払い処理
 * チェックアウトの「注文する」ボタン押下時に呼び出される
 *
 * @param int $order_id 注文ID
 * @return array 処理結果(result と redirect を含む)
 */
public function process_payment( $order_id ): array {
    $order = wc_get_order( $order_id );

    if ( ! $order ) {
        wc_add_notice( __( '注文が見つかりません。', 'my-payment-gateway' ), 'error' );
        return [ 'result' => 'fail' ];
    }

    try {
        // 決済 API を呼び出す
        $response = $this->call_payment_api( [
            'amount'      => (int) ( $order->get_total() * 100 ), // 金額(銭単位)
            'currency'    => strtolower( get_woocommerce_currency() ),
            'order_id'    => $order_id,
            'customer'    => [
                'name'  => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
                'email' => $order->get_billing_email(),
            ],
            'return_url'  => $this->get_return_url( $order ),
            'cancel_url'  => wc_get_checkout_url(),
            'webhook_url' => WC()->api_request_url( 'my_payment_gateway' ),
        ] );

        if ( isset( $response['redirect_url'] ) ) {
            // リダイレクト型:決済ページへ転送する
            return [
                'result'   => 'success',
                'redirect' => $response['redirect_url'],
            ];
        }

        if ( isset( $response['transaction_id'] ) ) {
            // API 型:即時決済が完了した場合
            $order->payment_complete( $response['transaction_id'] );
            $order->add_order_note(
                sprintf(
                    __( '決済完了。トランザクションID: %s', 'my-payment-gateway' ),
                    $response['transaction_id']
                )
            );

            // カートをクリアする
            WC()->cart->empty_cart();

            return [
                'result'   => 'success',
                'redirect' => $this->get_return_url( $order ),
            ];
        }

        throw new \Exception( __( '決済サービスからの応答が不正です。', 'my-payment-gateway' ) );

    } catch ( \Exception $e ) {
        wc_add_notice( $e->getMessage(), 'error' );
        $order->update_status(
            'failed',
            sprintf( __( '決済エラー: %s', 'my-payment-gateway' ), $e->getMessage() )
        );
        return [ 'result' => 'fail' ];
    }
}

/**
 * 決済 API を呼び出す内部メソッド
 */
private function call_payment_api( array $params ): array {
    $api_endpoint = $this->test_mode
        ? 'https://api-sandbox.payment-service.example.com/v1/charges'
        : 'https://api.payment-service.example.com/v1/charges';

    $response = wp_remote_post( $api_endpoint, [
        'timeout' => 30,
        'headers' => [
            'Authorization' => 'Bearer ' . $this->api_key,
            'Content-Type'  => 'application/json',
        ],
        'body' => wp_json_encode( $params ),
    ] );

    if ( is_wp_error( $response ) ) {
        throw new \Exception( $response->get_error_message() );
    }

    $body = json_decode( wp_remote_retrieve_body( $response ), true );

    if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
        throw new \Exception( $body['error']['message'] ?? __( 'APIエラー', 'my-payment-gateway' ) );
    }

    return $body;
}

ステップ4:payment_complete() と注文ステータス更新を処理する

<?php
// WC_My_Payment_Gateway クラスのメソッドとして追加する

/**
 * 決済完了後の処理
 * リダイレクト型の場合、決済サービスからのリターンURLで呼び出される
 *
 * @param WC_Order $order 注文オブジェクト
 * @param string   $transaction_id トランザクションID
 */
public function complete_order_payment( WC_Order $order, string $transaction_id ): void {
    // 二重処理を防ぐ(既に処理済みの場合はスキップ)
    if ( $order->is_paid() ) {
        return;
    }

    // 決済完了として注文を処理する
    // → 注文ステータスを「processing」に変更
    // → 在庫の減算
    // → 顧客への確認メール送信
    $order->payment_complete( $transaction_id );

    // トランザクションIDをメモに記録する
    $order->add_order_note(
        sprintf(
            __( '決済が完了しました。トランザクションID: %s', 'my-payment-gateway' ),
            esc_html( $transaction_id )
        )
    );

    // カスタムメタデータを保存する
    $order->update_meta_data( '_my_gateway_transaction_id', $transaction_id );
    $order->update_meta_data( '_my_gateway_test_mode', $this->test_mode ? 'yes' : 'no' );
    $order->save();
}

/**
 * 返金処理の実装
 *
 * @param int    $order_id 注文ID
 * @param float  $amount   返金金額(null の場合は全額)
 * @param string $reason   返金理由
 * @return bool|WP_Error
 */
public function process_refund( $order_id, $amount = null, $reason = '' ) {
    $order          = wc_get_order( $order_id );
    $transaction_id = $order->get_meta( '_my_gateway_transaction_id' );

    if ( ! $transaction_id ) {
        return new WP_Error( 'refund_error', __( 'トランザクションIDが見つかりません。', 'my-payment-gateway' ) );
    }

    try {
        $response = $this->call_payment_api( [
            'action'         => 'refund',
            'transaction_id' => $transaction_id,
            'amount'         => (int) ( $amount * 100 ),
            'reason'         => $reason,
        ] );

        $order->add_order_note(
            sprintf(
                __( '返金完了: ¥%s(理由: %s)', 'my-payment-gateway' ),
                number_format( $amount ),
                $reason
            )
        );

        return true;

    } catch ( \Exception $e ) {
        return new WP_Error( 'refund_error', $e->getMessage() );
    }
}

ステップ5:woocommerce_payment_gateways フィルターで登録し Webhook を処理する

<?php
// プラグインのメインファイル(my-payment-gateway.php)に追記する

/**
 * WooCommerce にゲートウェイを登録する
 */
add_filter( 'woocommerce_payment_gateways', 'register_my_payment_gateway' );

function register_my_payment_gateway( array $gateways ): array {
    $gateways[] = 'WC_My_Payment_Gateway';
    return $gateways;
}

/**
 * プラグイン有効化時にクラスを読み込む
 */
add_action( 'plugins_loaded', 'init_my_payment_gateway_class' );

function init_my_payment_gateway_class(): void {
    if ( ! class_exists( 'WC_Payment_Gateway' ) ) {
        return; // WooCommerce が有効でない場合はスキップ
    }
    require_once plugin_dir_path( __FILE__ ) . 'class-wc-my-payment-gateway.php';
}

/**
 * Webhook / IPN コールバックを処理する
 * URL: https://example.com/?wc-api=my_payment_gateway
 */
add_action( 'woocommerce_api_my_payment_gateway', 'handle_my_payment_webhook' );

function handle_my_payment_webhook(): void {
    $gateway = new WC_My_Payment_Gateway();

    // リクエストボディを取得する
    $payload = file_get_contents( 'php://input' );
    $data    = json_decode( $payload, true );

    // 署名を検証する(改ざん防止)
    $signature        = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
    $expected_sig     = hash_hmac( 'sha256', $payload, $gateway->get_option( 'webhook_secret' ) );

    if ( ! hash_equals( $expected_sig, $signature ) ) {
        http_response_code( 401 );
        exit( 'Invalid signature' );
    }

    // イベントタイプに応じて処理する
    switch ( $data['event'] ?? '' ) {
        case 'payment.completed':
            $order_id       = (int) ( $data['metadata']['order_id'] ?? 0 );
            $transaction_id = $data['transaction_id'] ?? '';
            $order          = wc_get_order( $order_id );

            if ( $order ) {
                $gateway->complete_order_payment( $order, $transaction_id );
            }
            break;

        case 'payment.failed':
            $order_id = (int) ( $data['metadata']['order_id'] ?? 0 );
            $order    = wc_get_order( $order_id );

            if ( $order ) {
                $order->update_status(
                    'failed',
                    __( 'Webhook: 決済が失敗しました。', 'my-payment-gateway' )
                );
            }
            break;

        case 'refund.completed':
            // 返金完了の処理
            break;
    }

    http_response_code( 200 );
    echo 'OK';
    exit;
}

注意事項

  • PCI DSS 準拠: カード番号を自サーバーに送信・保存してはいけません。トークナイゼーションを使用し、決済サービス側でカード情報を処理してください。
  • Webhook の署名検証: 署名検証なしで Webhook を処理すると、偽の決済完了通知を送り込む攻撃が可能になります。必ず hash_equals を使って検証してください。
  • 冪等性: Webhook が複数回送信された場合でも、注文の二重処理が起きないよう $order->is_paid() などで処理済みチェックを入れてください。

まとめ

WooCommerceのカスタム決済ゲートウェイは「クラス定義→設定フィールド→支払い処理→注文ステータス更新→Webhook処理」の5ステップで実装できます。セキュリティ(署名検証・冪等性)と WooCommerce のライフサイクルに従った実装が成功の鍵です。関連記事:WooCommerceにカスタム商品タイプを作成する方法

お気軽にご相談ください

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