4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

EC-CUBE×Stripeで疑似サブスク機能「Stripeの実装部分」

Posted at

noteにEC-CUBE×Stripeで疑似サブスクリプションの記事を書きました。
その技術的な部分、Stripeの実装をここに記す!!

EC-CUBE側は結構なカスタムを行っているので、この部分は省きます。

Stirpeで利用した機能

・The Setup Intents API(docs
・Set up future payments(docs

・SetupIntents(docs API
・PaymentMethods(docs API
・PaymentIntents(docs API
・Customers(docs API

EC-CUBEに実装した大まかな流れ

1.決済フロー「ご注文手続き」でSetupIntentsの作成、payment_method_idを取得
2.決済フロー「checkout」でCustomer作成、PaymentMethodにCustomerをアタッチ
3.決済フロー「checkout」でCustomerId、PaymentMethodIdを保存

SetupIntents, PaymentMethods, Customers

SetupIntentsは顧客のカード情報の認証情報を設定して保存できます。
すぐに支払いをせずにPaymentIntentsを利用して後から支払いできます。

カード情報の収集とSetupIntentsの作成

SetupIntentsを作成し、戻ってきた情報からpayment_methodのIDをcheckout時に保存します。

下記はEC-CUBEの決済フロー(ご注文手続き)に実装しています。
EC-CUBEに関わる部分は邪魔なので削除しています。

カード情報入力フォーム
<div class="ec-orderPayment">
    <div class="ec-rectHeading">
        <h2>クレジットカード情報</h2>
        <div id="card-element" ></div>
        <div id="card-errors" role="alert"></div>
    </div>
</div>
<script src="https://js.stripe.com/v3/"></script>
<script>
    const publicKey = '{{ publicKey }}';

    let stripe = Stripe(publicKey,{betas: ['payment_intent_beta_3']});
    let elements = stripe.elements();

    // スタイルのカスタマイズ
    let style = {
        base: {
            color: '#32325d',
            fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
            fontSmoothing: 'antialiased',
            fontSize: '16px',
            '::placeholder': {
                color: '#aab7c4'
            }
        },
        invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
        }
    };

    // クレジットカード情報入力欄の構築
    let card = elements.create('card', {style: style, hidePostalCode: true});
    card.mount('#card-element');

    // 入力変更時のリスナー
    // バリデーションメッセージを表示する
    card.addEventListener('change', function(event) {
        let displayError = document.getElementById('card-errors');
        if (event.error) {
            displayError.textContent = event.error.message;
        } else {
            displayError.textContent = '';
        }
    });

    // submit時のリスナー
    let setupIntent;
    let form = document.getElementById('form-id');//用意したFORMのIDを設定してください。
    form.addEventListener('submit', async function(event) {
        event.preventDefault();

        if($.isEmptyObject(setupIntent)){
            await getSetupIntent();
        }

        if(setupIntent !== undefined){
            stripe.confirmCardSetup(setupIntent.client_secret, {
                payment_method: {
                    card: card
                }
            }).then(function(result) {
                if (result.error) {
                    stripeDisplayErrorMessage(result.error.message);
                } else {
                    // The PaymentMethod was successfully set up
                    stripePaymentMethodHandler(result.setupIntent.payment_method);
                }
            });
        }
    });

    function stripePaymentMethodHandler(payment_method) {
        let hiddenInputToken = document.createElement('input');
        hiddenInputToken.setAttribute('type', 'hidden');
        hiddenInputToken.setAttribute('name', 'stripe_payment_method_id');
        hiddenInputToken.setAttribute('value', payment_method);
        form.appendChild(hiddenInputToken);

        // Submit the form
        form.submit();
    }

    let getSetupIntent = function() {
        return fetch('{{ setupIntent作成PHPのURL }}', {
            method: "post",
            headers: {
                "Content-Type": "application/json",
            }
        }).then(function(response) {
            return response.json();
        }).then(function(setupIntentObj) {
            if (setupIntentObj.error) {
                stripeDisplayErrorMessage(setupIntentObj.error.message);
            } else {
                setupIntent = setupIntentObj;
            }
        });
    };

    function stripeDisplayErrorMessage(message) {
        let displayError = document.getElementById('card-errors');
        displayError.textContent = message;
    }
</script>
setupIntent作成PHP
use Stripe\Stripe;
use Stripe\SetupIntent;

public function setup_intent()
{
    Stripe::setApiKey('{{ secretKey }}');

    $setupIntent = SetupIntent::create([
        'payment_method_types' => ['card'],
    ]);

    return $setupIntent;//jsonに変換してください。
}

Customerの作成とPaymentMethodにCustomerをアタッチ、ID保存

checkoutはEC-CUBEの決済プラグインを参考に作成しています。
https://github.com/EC-CUBE/sample-payment-plugin

checkout
use Stripe\Customer;
use Stripe\PaymentMethod;
use Stripe\Stripe;

public function checkout()
{
    Stripe::setApiKey('{{ secretKey }}');

    //Customer作成
    $StripeCustomer = Customer::create([
        'name' => 'name',
        'email' => 'email',
    ]);

    //CustomerをpaymentMethodへアタッチ
    $payment_method = PaymentMethod::retrieve('{{ paymentMethodId }}');
    $payment_method->attach(['customer' => $StripeCustomer->id]);

    //それぞれのIDを保存(略)
}

PaymentIntents

CustomerId, paymentMethodIdを利用してPaymentIntentsで決済を行います。
この部分はEC-CUBEのcommandを作成してcronで定期的に実行しています。

決済
use Stripe\Stripe;
use Stripe\StripeClient;

$stripe = new StripeClient('{{ secretKey }}');

$createPaymentIntent = $stripe->paymentIntents->create([
    'amount'   => 1000,
    'currency' => 'jpy',
    'customer' => {{ customer_id }},
    'payment_method' => {{ payment_method_id }},
    'off_session' => true,
    'confirm' => true,
]);

以上!!

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?