勉強したことのメモ

Webエンジニア / プログラマが勉強したことのメモ。

PHPとStripeで定額課金(サブスクリプション)実装する方法

   2024/01/16  PHP

PHPとStripeで1ヶ月ごとに500円でサイト内のサービスを使い放題、といったようなサブスクリプション機能を実装したかった。以下に決済処理及び解約処理ページの実装方法をメモ。

 

Stripeの導入

Stripeのアカウント作成やライブラリのインストールは過去記事を参照。

 

商品(コース)の作成

Stripeのダッシュボードにログインし「商品」→「商品を追加」→「商品を保存」の順に進み、商品(コース)を作成する。今回はテストのため、1日ごとに500円の決済が発生する商品を作成した。

尚、商品を作成すると「price_」から始まるAPI IDと「prod_」から始まるIDが発行され、今回必要なのはAPI IDになる。

 

決済処理

決済ページ(HTML)

<!DOCTYPE html>
<html lang="ja">
<head>
</head>
<body>
    <form id="form_payment" action="add.php" method="post">
        Name:<input id="name" type="text" name="name">
        Email:<input id="email" type="text" name="email">
        <!-- ここのdivタブがカード入力フォームに置き換わります。 -->
        <div id="card-element" class="MyCardElement"></div>
        <!-- ここにエラーメッセージが表示されます。 -->
        <div id="card-errors" role="alert"></div>
        <button id="button">Submit</button>
    </form>

    <script src="https://js.stripe.com/v3/"></script>
    <script>
    const stripe = Stripe('公開可能なAPIキー');

    // 入力フォームを生成します。スタイルを指定することもできます。
    const elements = stripe.elements();
    const cardElement = elements.create("card");

    // 先程のdivタブにマウントします。
    cardElement.mount("#card-element");

    // クレジットカード番号や有効期限の入力に合わせてエラーメッセージを出力します。
    cardElement.addEventListener('change', ({error}) => {
        const displayError = document.getElementById('card-errors');
        if (error) {
            displayError.textContent = error.message;
        } else {
            displayError.textContent = '';
        }
    });

    const submit = document.getElementById('button');
    const name = document.getElementById('name');
    const email = document.getElementById('email');

    // 登録ボタンがクリックされたら、API通信をおこなう
    submit.addEventListener('click', async(e) => {
        e.preventDefault();
        const {paymentMethod, error} = await stripe.createPaymentMethod({
            type: 'card',
            card: cardElement,
            billing_details: {
                // 顧客名emailアドレスはなくてもOK
                name: name.value,
                email: email.value,
            },
        });
        // 通信エラー時
        if (error) {
            console.error(error)
        } else {
            // 成功したらトークンが返されるので、hiddenに埋め込む
            const form = document.getElementById('form_payment');
            const hiddenToken = document.createElement('input');
            hiddenToken.setAttribute('type', 'hidden');
            hiddenToken.setAttribute('value', paymentMethod.id);
            hiddenToken.setAttribute('name', 'token');
            form.appendChild(hiddenToken);
            form.submit();
        }
    });
</script>
</body>
</html>

商品名や料金説明等は適宜追記する。

決済処理ページ(PHP)

<?php
require './vendor/autoload.php';
\Stripe\Stripe::setApiKey('シークレットキー');

if($_SERVER['REQUEST_METHOD'] == 'POST') {

    $name = $_POST['name'];
    $email = $_POST['email'];
    $token = $_POST['token'];

    // 顧客情報を登録
    $customer = \Stripe\Customer::create([
        'payment_method' => $token, // 登録する支払い方法
        'name' => $name,
        'email' => $email,
        'invoice_settings' => [
            'default_payment_method' => $token, // デフォルトで使用する支払い方法。必須。
        ],
    ]);

    // 顧客をプランに登録する
    $subscription = \Stripe\Subscription::create([
        // 先程登録した顧客情報のID
        'customer' => $customer->id,
        'items' => [
            [
              'plan' => 'API ID',
            ],
        ],
    ]);
    // subsctiprionIDは解約時に必要のため、DBに保存しておく
    $sub_id = $subscription->id;

}

決済日時、メールアドレス、「sub_」から始まるsubsctiprionIDをMySQLなどに適宜格納しておく。特にsubsctiprionIDは必須。

 

解約処理

解約処理ページ(PHP)

<?php
require './vendor/autoload.php';

\Stripe\Stripe::setApiKey('シークレットキー');

//subsctiprionID
$sub_id = 'sub_xxxxxxxxxxxx';

// 解約したあとも有効期限までは、プランに入会したまま
\Stripe\Subscription::update(
  $sub_id,
  [
    'cancel_at_period_end' => true,
  ]
);

 

別の実装方法

決済までであればもっと簡単な実装方法もある。Stripeのダッシュボードにログインし「設定」→「Payments内にあるCheckoutの設定」→「Checkout クライアント専用組み込みを有効」→「許可」→「ドメインを入力して保存」と進む。

さらに「商品」→「料金体系の…となっているメニュー部分」→「checkoutのコードスニペットを取得」→「成功時のURLとキャンセル時のURLを入力して保存」とする。最後のURLを入力するダイアログでJavaScriptのタグが表示されるのでそのタグを決済ページに貼り付けるだけで良い。

ただ、この方法だとsubsctiprionIDの取得方法が分からなかった。そうすると解約もどうすればいいのか分からない。「テスト用クレカ&実在するメアド」で登録テストしてみたものの確認メールのようなものは届かなかった。

 

その他

正常に定額決済に登録されているか確認する方法

Stripeのダッシュボードにログインし「顧客」メニューを開くと登録されているユーザーが一覧表示される。

またユーザー部分をクリックすると詳細が表示され、どのコースに登録されているか等が確認できる。

できれば「商品」→「詳細」を開いた際にも該当商品に登録しているユーザーが見られればありがたいが、そのような項目は見受けられなかった。

subsctiprionIDの確認方法

Stripeのダッシュボードにログインし「顧客」→「適当なユーザーを選択」→「支払い部分を選択」するとページ下部に「イベントとログ」という表示欄があり、イベントデータの中に「sub_」から始まるsubsctiprionIDが確認できる。

ただ、イベントデータはデフォルトでは省略されており「○行すべてを表示する」ボタンをクリックしないと確認できなかったので注意する。

 

所感

定額決済だと単発決済とは違って解約が発生する為、subsctiprionIDは必須として入会日や更新日などもデータベースに残しておく必要がありそうでまあ色々面倒くさそう。また、例えばだが「今月入会した場合は○%割引」「解約しても更新予定日までは使用可能」等、色々柔軟に対応できるみたいだがこれも実装するのは大変そうである。できれば単発決済のみが良いなぁとは思うものの、恐らくは需要がある決済方法だと考えられるため最低限の部分は今回メモした方法で乗り切りたいところ。

 

参考サイト

https://qiita.com/azukiazusa/items/584d69a373214769880c

 - PHP

  関連記事

PHPで多次元連想配列のキーを指定してソート

やりたかった事は、 ・多次元配列があってcodeというキーでソートしたい という ...

MySQLとPHPの「image-comparator」ライブラリを使用して類似画像検索を実装する方法

先日PHPで画像を比較して類似度を算出する「image-comparator」ラ ...

画像をアップロードすると複数サムネイルを生成する方法

フォームで画像をアップロードすると、予め定めておいた大中小のサイズでサムネイル画 ...

PHPとGoogle Authenticatorの組み合わせで2段階認証を実装する方法

2段階認証と言えばSMS送信のケースが多く、その次に通常のメール送信というケース ...

時間と数字のフォーマット

教わったのでメモ。 ・時間の整形 strtotime() 例) $ymd = ' ...