LoginSignup
60
76

More than 3 years have passed since last update.

【PHP】stripe API で定額課金を実装する

Last updated at Posted at 2020-01-16

はじめに stripeとは

stripeは、クレジットカードなどの決算処理を代行してくれるサービスです。
シンプルな実装で決済処理を実現でき、カード情報を自社サーバで持たなくても良いなどの特徴があります。

大まかな流れとして、

  1. サービスの請求モデルを作成
  2. フロントサイドでカード情報を取得
  3. バックエンドで顧客登録、決済処理

と進んでいきます。

サービスの請求モデルを作成する

1. stripeに登録する

1-1.はじめに、stripeの公式ページにて、今すぐ始めるをクリック。

スクリーンショット 2020-01-15 20.24.58.png

1-2メールアドレス、名前、パスワードを入力登録。

なお、テスト用に登録する際には、メールアドレスの存在確認は求められません。
この時点で、登録が完了いたしました。
スクリーンショット 2020-01-15 20.27.48.png

1-3.ダッシュボードが表示される

登録が完了したら、いくつかの質問がされるので答えていきます。
質問が完了したら、次のようなダッシュボードが表示されます。
スクリーンショット 2020-01-15 20.34.07.png

2.テストAPIキーを取得する

ダッシュボードが表示されたら、まずはAPIキーを取得してみましょう。
今回はテスト用のAPIキーを取得します。
ダッシュボードのテストAPIキーの取得をクリックしてください。スクリーンショット 2020-01-15 20.39.48.png

公開可能キーシークレットキーの2つが表示されています。
スクリーンショット 2020-01-15 20.43.27.png

公開可能キー

公開可能キーは、不特定多数に表示されてもかまわないキーです。
JavaScriptなど、フロントサイドで使用されるキーです。

シークレットキー

一方シークレットキーは秘密にしなければなりません。
PHPなど、サーバーサイドで使用されます。

3.商品(Product)を登録する

3-1.Billing->商品をクリック

はじめに、提供するサービスである、商品(Product)を登録します。
画面左側のBillingをクリックしてから、商品が表示されるのでもう一度クリックしてください。

スクリーンショット 2020-01-15 21.24.24.png

テスト商品を追加をクリックします。
スクリーンショット 2020-01-15 21.31.18.png

3-2.商品を作成

商品名を入力して、商品を作成をクリックします。
スクリーンショット 2020-01-15 21.33.31.png

なお、ここでは商品の価格などの情報は入力しません。
商品の配下に、プラン(Plan)を作成して個別に料金体系(価格、通貨、支払い間隔など)を設定します。
一つの商品に対して、複数のプランが設定できます。
例えば、有料会員という製品には以下のプランが紐付けられていると考えられます。

  • 有料会員
    • ブロンズ会員 月額500円
    • シルバー会員 月額1,000円
    • ゴールド会員 月額3,000円

4.プランを作成する

4-1.プランの作成

それでは実際にプランを作成してみます。
商品を作成したら、そのままプランを作成するページへ遷移します。
スクリーンショット 2020-01-15 21.41.54.png
スクリーンショット 2020-01-15 21.43.12.png

必要な項目を入力して、料金プランを追加をクリックすると、プランが追加されます。

4-2.プランIDの取得

後ほど会員をプランに追加する際に、プランIDが必要になるのでここで取得します。
prod_から始まる商品IDではないことに注意してください
先程作成したプランクリックして詳細を表示します。
スクリーンショット 2020-01-16 0.17.41.png

plan_から始まる英数字がプランIDです。
スクリーンショット 2020-01-15 22.25.39.png

コードを実装する。

5.ライブラリをインストールする

いよいよコードの実装にはいります。
はじめに、Composerを利用して、PHPのライブラリを入手します。

Composerのインストール

composer require stripe/stripe-php

6.フロントサイドの実装(JavaScript)

6-1.カード情報を入手する

カード情報を入手するための入力フォームを、JavaScritpで実装します。
カード情報といっても、実際に得られるのはAPI通信によって得られたトークンの情報です。

index.html
<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>
<!-- stripAPIを読み込みます -->
<script src="https://js.stripe.com/v3/"></script>
<script>
  // 公開可能なAPIキーです。
  const stripe = Stripe('pk_test_aGucPIXrlDAHQuflip6RqErD00CqmOnlKK');
  // 入力フォームを生成します。スタイルを指定することもできます。
  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>

下記画像のように、カード番号・有効期限・確認番号・郵便番号を入力するフォームが生成されました。
スクリーンショット 2020-01-16 11.09.10.png

これらの項目はすべて必須項目です。
もし、郵便番号の入力をなくしたいなら、入力フォーム生成時にhidePostalCodetrueにします。

    const cardElement = elements.create('card', {hidePostalCode: true});

テスト用として、4242 4242 4242 4242の番号が用意されているので、そちらを利用します。
その他の値はなんでもかまわないようです。

7.サーバーサイド(PHP)

7-1.顧客情報を登録する。

フロントサイドから受け取ったトークンをもとに、顧客情報を登録します。

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

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, // デフォルトで使用する支払い方法。必須。
        ],
    ]);

7-2.顧客をプランに登録する

さらに、先程登録した顧客情報をもとに、プランへの登録処理を行います。

    // 顧客をプランに登録する
    $subscription = \Stripe\Subscription::create([
        // 先程登録した顧客情報のID
        'customer' => $customer->id,
        'items' => [
            [
              // 4-2で取得したプランID
              'plan' => 'plan_GYHnkoAjA7PFtG',
            ],
        ],
        'trial_end' => strtotime('+3 month'),
        // トライアル(無料)期間。UNIX秒で指定する。
    ]);
    // subsctiprionIDは、解約時に必要
    $sub_id = $subscription->id; // sub_GYJXuOff81PbZy


    // DBを準備
    try {
        $db = new PDO('mysql:dbname=php;host=localhost', 'root', '');
    } catch (PDOException $e) {
        print "Coudn't connet to the database: " . $e->getMessage();
        exit();
    }

    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // 顧客情報を登録
    try {
        $stmt = $db->prepare('INSERT INTO Users (name, email, sub_id)
                                VAlUES (?,?,?)');
        $stmt->execute($name, $email, $sub_id);

    } catch (PDOException $e) {
        print "Coudn't connet to the database: " . $e->getMessage();
        exit();
    }
}

trial_endを指定すると、トライアル(無料期間)を定めることができます。
音楽ストリーミングサイトによくある初回3ヶ月無料!みたいなやつです。

UNIX秒で指定するので、strtotimeを使用すればある程度簡単に指定することができます。

プラン登録時の返却値のSubscriptionオブジェクトのIDは、解約時に必要になります。
なので、顧客情報とともにDBに登録します。

8.登録を確認する

ダッシュボードに戻り、左側の顧客をクリックすると、先程登録した顧客情報が表示されているかと思います。
スクリーンショット 2020-01-15 23.34.23.png

無事顧客が登録されていましたね。
さらに、詳細情報をみると顧客の定期支払を確認することができます。
スクリーンショット 2020-01-15 23.33.40.png

トライアル期間を設定したので、請求書をみると初回の支払いは0円で、次回請求日は3ヶ月後に設定されていることが確認できます。(今回登録した日付は01/15)
スクリーンショット 2020-01-15 23.34.58.png

9.プランを解約する

最後に、顧客が有料会員プランを解約する流れを説明します。

<?php
// 契約を解除するためにログインしている想定
session_start();
require 'vendor/autoload.php';

$name = $_SESSION['name'];
// DBを準備
try {
    $db = new PDO('mysql:dbname=php;host=localhost', 'root', '');
} catch (PDOException $e) {
    print "Coudn't connet to the database: " . $e->getMessage();
    exit();
}

// ログインユーザーからsub_idを取得
$stmt = $db->prepare('SELECT sub_id FROM Users WHERE name = ?');
$stmt->execute($name);
$row = $stmt->fetch('assoc');
if ($row) {
  $sub_id = $row->sub_id;
} else {
  exit();
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// シークレットキーをセットする
\Stripe\Stripe::setApiKey(YOUR_API_KEY);

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

上記の例のようにcancel_at_period_endtrueにしてプランをSubscription::updateで更新することで、解約後も有効期限内まではプランが有効になります。

もし、解約後はすぐにプランを終了したいときには、以下のようなコードを書きます。

// 解約後はすぐにプランが終了する。
$subscription = \Stripe\Subscription::retrieve('sub_49ty4767H20z6a');
$subscription->cancel();

8-2.解約を確認

ダッシュボードを確認すると、先程の会員が有効期限である04/15にプランがキャンセルされることが確認できました。
スクリーンショット 2020-01-16 0.06.57.png

おわりに

定額課金の決済の簡単な流れを説明しました。
他にも期間中のアップグレード、ダウングレードや途中解約に対する比例配分、更新時にカードが利用できなかった場合など複雑な状況にも対応できます。

ぜひ、ドキュメントも参照してみてください。

参考サイト

公式ドキュメント
API リファレンス
Stripe Billing 101
Stripe.js & Elements を利用して決済フローを理解する

60
76
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
60
76