PHPとStripeで決済処理を実装してみる

:::note warn
本記事は古くなっております。
日本語の[公式ドキュメント](https://stripe.com/docs/development/quickstart?lang=php)が用意されておりますのでそちらを参照ください
:::

PHPでStripeの決済処理のサンプルを実装してみました。
実際に動かせるコンテナ(docker)もありますので参考にしてみてください。

# Stripeとは

API型の決済サービスです。
APIベースで決済処理を記述できます。
会員登録するとテスト用のAPIキーが発行されるので簡単に動作確認が出来ます。

# Stripeの大まかな機能＆サービス

* Payments
  * 支払い機能
* Billing
  * 定期的な支払い機能
* Connect
  * 支払いを別の利用者に送金する機能
* Sigma
  * SQLを用いてトランザクションデータからカスタムレポートを作成する機能
* Atlas
  * アメリカでインターネットビジネスを始める起業家を支援するサービス
* Radar
  * 支払い履歴から不正な支払を検出する機能
* Issuing
  * 物理カード、仮想カードを発行するサービス
* Terminal
  * 物理カードリーダを経由して支払いを行う機能（2019年8月現在　日本では利用できない）

今回は**支払い機能（Payments）**に絞って触っていきます。
その他の機能の詳細については公式ドキュメントを参照してください。
[Documentation \| Stripe](https://stripe.com/docs)


# Laravel Cashierについて

[Laravel Cashier](https://laravel.com/docs/7.x/billing)はStripeのサブスクリプション（定期購読）をLaravelから利用するのをサポートするライブラリです。
本記事では使用しませんが念の為、紹介だけ。
[個人ブログ](http://t-kuni.sub.jp/2020/05/28/laravel-cashier%e3%81%a7%e3%82%b5%e3%83%96%e3%82%b9%e3%82%af%e3%83%aa%e3%83%97%e3%82%b7%e3%83%a7%e3%83%b3%ef%bc%88%e5%ae%9a%e6%9c%9f%e8%b3%bc%e8%aa%ad%ef%bc%89%e3%82%92%e3%82%84%e3%81%a3%e3%81%a6/)の方に触ってみた記録があるので興味がある方は参考にどうぞ。
ざっくりどんな事ができるのか把握できるかと思います。

# 支払い機能（Payments）について

ウェブ、モバイルでのクレジットカード支払いをサポートしています。
更にいくつかの機能に分かれます。

* Checkout
  * Stripeが用意する決済フォームを経由して決済する方法です。
  * あらかじめ、Stripeのダッシュボードで商品と価格を登録しておいて、発行されるリンクを設置するだけで決済することもできます。
* Card Payment
  * 自サイトに**クレジットカード情報入力欄(Stripe Elements)**を設置して、ユーザにクレジットカード情報を入力して貰って決済します。
  * カード情報を保存しておき、任意のタイミングで決済する事も出来ます。
  
## Stripe Elementsについて

Stripeが提供している、クレジットカード情報を入力するUIコンポーネントです。
web, iOS, Androidをサポートしています。

以下の画像の様な**クレジットカード情報入力欄**を構築できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71575/9510d451-f2f6-9d4f-b149-e4ee1d59450f.png)


# 動かして試せる環境を用意しました

DockerでWebアプリケーションを起動して実際に動かせるサンプルを作りました。
ソースコードを書き換えてみる事も出来ます。
HTTPSに対応したサーバを用意しなければいけないので自前でやろうとするとそこそこ面倒かと思います。
詳しい起動方法はReadmeを参照してください。
[GitHub - t-kuni/stripe-php-sample](https://github.com/t-kuni/stripe-php-sample)

### フォルダ構成について

srcフォルダにサンプルソースコードが格納されています。
目的別にフォルダを分けており、以下の様になっています。

* checkout
  * Stripeが用意する決済フォーム(Checkout)を経由して決済する
* charge
  * 自サイトに設置したクレジットカード情報入力欄を用いて決済(Charge)する
* create-customer
  * 顧客情報(Customer)を作成する
* customers
  * 顧客(Customer)の一覧を表示する
* charge-by-customer
  * 顧客IDを元に決済(Charge)する
* card-list
  * クレジットカードの一覧を表示する
* add-card
  * クレジットカードを追加する

※括弧書きの英名はStripeの用語に対応しています

### テスト用のクレジットカード

テスト用のクレジットカード情報は以下の公式ドキュメントに記載があります。
[Test card numbers and tokens](https://stripe.com/docs/testing#cards)

カードブランド毎に番号が割り振られています。
有効期限とCVCは何でも良い様です。

# 決済処理のパターン

大まかに以下の３パターンあります。

* [決済フォーム(Checkout)で決済する](#決済フォームcheckoutで決済する)
* [自サイトにクレジットカード情報入力欄を設置して決済(Charge)する](#自サイトにクレジットカード情報入力欄を設置して決済chargeする)
* [クレジットカード情報を保存して後で決済(Charge)する
](#クレジットカード情報を保存して後で決済chargeする
)

# 決済フォーム(Checkout)で決済する

## 概要

CheckoutはStripeが用意する決済フォームにリダイレクトするだけで決済ができる機能です。
「月次のバッチ処理で決済を行う」といったケースには対応できませんが、
シンプルなECサイトなどで活用すれば工数を掛けずに決済処理が実装できるかと思います。

（以下の画像の様な決済フォームが表示されます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71575/459ef16b-c75b-1e42-b27e-1a0d87454e21.png)

なお、Stripeのダッシュボードであらかじめ商品名や金額などを登録しておけば、リンクを張るだけで決済できる方法もありますが、ここではあえてPHPを使う方法を使っています。

大まかな流れは以下の様になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71575/d29bfefe-3750-8a50-9372-9d354c543fcd.png)

## 処理内容の解説

以下のサンプルコードに沿って解説していきます
[/src/checkout/index.php](https://github.com/t-kuni/stripe-php-sample/blob/master/src/checkout/index.php)

### 1. Stripeライブラリの初期化（サーバサイド）

Stripeから提供される**シークレットキー**と**パブリックキー**を設定してライブラリを初期化します。
シークレットキーとパブリックキーはStripeのダッシュボードから取得できます。

[/src/checkout/index.php#L4-L11](https://github.com/t-kuni/stripe-php-sample/blob/master/src/checkout/index.php#L4-L11)

```php
$dotenv = Dotenv\Dotenv::create('../');
$dotenv->load();

$secretKey = getenv('STRIPE_SECRET_KEY');
$publicKey = getenv('STRIPE_PUBLIC_KEY');

\Stripe\Stripe::setApiKey($secretKey);
```


なお、それぞれのキーについてはハードコードするのを避けるため、[phpdotenv](https://github.com/vlucas/phpdotenv)を使って`.env`ファイルから読み込む様にしています。

PHPのStripeライブラリの導入は[コチラ](https://github.com/stripe/stripe-php)を参考にしてください。

### 2. 支払いフォームを構築するリクエストをStripe APIに送信する

商品名や商品画像、価格、数量を指定してフォームを構築するリクエストをStripe APIに送信します。
処理は同期的に実行され、通信している事を意識せず利用する事ができます。
処理に失敗した場合は例外`Stripe\Error\InvalidRequest`が発生します。
返り値の`$session`は手順4で参照するので保持しておきます。

[/src/checkout/index.php#L13-L26](https://github.com/t-kuni/stripe-php-sample/blob/master/src/checkout/index.php#L13-L26)

```php
$session = \Stripe\Checkout\Session::create([
    'payment_method_types' => ['card'],
    'line_items'           => [[
        'name'        => 'T-shirt',                           // 商品名
        'description' => 'Comfortable cotton t-shirt',        // 説明
        'images'      => ['https://example.com/t-shirt.png'], // 画像URL
        'amount'      => 500,                                 // 金額
        'currency'    => 'jpy',                               // 単位
        'quantity'    => 1,                                   // 数量
    ]],
    'success_url'          => 'https://example.com/checkout/success.php', // 成功時リダイレクトURL
    'cancel_url'           => 'https://example.com/checkout/cancel.php',  // 失敗時リダイレクトURL
]);
```


### 3. Stripeライブラリの初期化（フロントエンド）

StripeのJavaScriptライブラリの読み込みと初期設定を行います。
手順1で読み込んだパブリックキーをここで使用しています。

```php
<script src="https://js.stripe.com/v3/"></script>

<script>
    const publicKey = '<?= $publicKey ?>';

    var stripe = Stripe(publicKey);
```

### 4. 決済ボタンが押下されたら決済画面にリダイレクトする処理

決済ボタンが押下されると`redirectToCheckout`を呼び出して決済画面に遷移させます。
この時に手順2で取得した`$session->id`を引数に渡します。

[/src/checkout/index.php#L39-L48](https://github.com/t-kuni/stripe-php-sample/blob/master/src/checkout/index.php#L39-L48)

```php
function onClick() {
    stripe.redirectToCheckout({
        sessionId: '<?= $session->id ?>'
    }).then(function (result) {
        // If `redirectToCheckout` fails due to a browser or network
        // error, display the localized error message to your customer
        // using `result.error.message`.
    });
}
```



# 自サイトにクレジットカード情報入力欄を設置して決済(Charge)する

## 概要

自サイトにクレジットカード情報入力欄を設置して決済を行う方法です。
クレジットカード情報はStripe APIを通してクレジットカードトークンに置き換えられるので
クレジットカード情報そのものを自サーバ上で扱う必要はありません。
UIとしてStripe Elementsを使用します。

大まかな流れは以下の様になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71575/4fa8d86e-358d-9a1a-063d-4da032d32922.png)


## 処理内容の解説

以下のサンプルコードに沿って解説していきます。
[/src/charge/index.php](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge/index.php)
[/src/charge/action.php](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge/action.php)

なお、Checkoutと重複する内容は解説を省きます。

### 1. クレジットカード情報入力欄の表示

formを用意しておいて、stripeのjsライブラリで**クレジットカード情報入力欄**を構築します。

[/src/charge/index.php#L18-L31](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge/index.php#L18-L31)

```php
<form action="/charge/action.php" method="post" id="payment-form" style="width: 400px;">
    <div class="form-row">
        <label for="card-element">クレジットカード情報</label>

        <div id="card-element">
            <!-- ここにクレジットカード情報入力欄が挿入される -->
        </div>

        <!-- ここにエラーメッセージが表示される -->
        <div id="card-errors" role="alert"></div>
    </div>

    <button>決済実行</button>
</form>
```

[/src/charge/index.php#L39-L49](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge/index.php#L39-L49)

```js
// スタイルのカスタマイズ
var style = {
    base: {
        fontSize: '16px',
        color: "#32325d",
    }
};

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

以下の様なフォームが表示されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71575/a65602f8-002c-d5fa-9608-f6c78884447f.png)


### 2. クレジットカード情報入力欄のsubmit時

submit時のリスナーを登録します。
入力されたクレジットカード情報を`createToken`に渡して、**クレジットカードトークン**を取得し、取得したクレジットカードトークンを自サーバにポストします。

```js
// submit時のリスナー
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
    event.preventDefault();

    // Stripeサーバにクレジットカード情報を送信してクレジットカードトークンを取得する
    stripe.createToken(card).then(function(result) {
        if (result.error) {
            var errorElement = document.getElementById('card-errors');
            errorElement.textContent = result.error.message;
        } else {
            // クレジットカードトークンを自サーバにsubmitする
            stripeTokenHandler(result.token);
        }
    });
});

function stripeTokenHandler(token) {
    var form = document.getElementById('payment-form');
    var hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('type', 'hidden');
    hiddenInput.setAttribute('name', 'stripeToken');
    hiddenInput.setAttribute('value', token.id);
    form.appendChild(hiddenInput);
    form.submit();
}
```

### 3. 決済処理

クレジットカードトークンを元に決済処理を行います。
`Charge::create`メソッドを使います。

[stripe-php-sample/action.php at master · t-kuni/stripe-php-sample · GitHub](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge/action.php#L16-L23)

```php
$token  = $_POST['stripeToken'];

$charge = \Stripe\Charge::create([
    'amount'      => 999,              // 金額
    'currency'    => 'jpy',            // 単位
    'description' => 'Example charge', // 名目
    'source'      => $token,           // クレジットカードトークン
]);
```

なお、クレジットカードトークンは一度しか使用できないことと、数分で失効してしまう点に注意してください。
ユーザがクレジットカード情報を入力した後、時間を開けて決済したい場合や
複数回決済する必要がある場合は、次の顧客情報(Customer)を作成します。

# クレジットカード情報を保存して後で決済(Charge)する

## 概要

クレジットカードトークンを元に顧客情報(Customer)を生成する事ができます。
顧客情報に含まれる顧客IDをDB等に保存しておく事で、任意のタイミングで決済を行う事ができます。

大まかな流れは以下の様になります。
なお、クレジットカード情報からクレジットカードトークンを取得するまでは前章と同じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71575/dc87b1f3-f9f1-1426-4415-4d4b3f5b6dfa.png)



## 処理内容の解説

以下のサンプルコードに従って解説します。

顧客情報の作成
[/src/create-customer/index.php](https://github.com/t-kuni/stripe-php-sample/blob/master/src/create-customer/index.php)
[/src/create-customer/action.php](https://github.com/t-kuni/stripe-php-sample/blob/master/src/create-customer/action.php)
顧客IDを元に決済
[/src/charge-by-customer/index.php](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge-by-customer/index.php)
[/src/charge-by-customer/action.php](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge-by-customer/action.php)

### 1. 顧客ID取得処理

クレジットカードトークンを`Customer::create`メソッドに渡す事で顧客情報(Customer)が作成できます。

[/src/create-customer/action.php#L16-L20](https://github.com/t-kuni/stripe-php-sample/blob/master/src/create-customer/action.php#L16-L20)

```php
$token = $_POST['stripeToken'];
$email = $_POST['email'];
$name  = $_POST['name'];
$customer = \Stripe\Customer::create([
    'source' => $token, // クレジットカードトークン
    'email'  => $email, // メールアドレス
    'name'   => $name,  // 顧客の名前
]);
```

`$customer->id`で顧客IDにアクセスできるので、これをDBなどに保存します。

### 2. 顧客IDを元に決済する

手順1で作成した顧客IDを用いて決済をします。
`Charge::create`メソッドを使います。

[/src/charge-by-customer/action.php#L15-L19](https://github.com/t-kuni/stripe-php-sample/blob/master/src/charge-by-customer/action.php#L15-L19)

```php
$charge = \Stripe\Charge::create([
    'amount'   => $amount,     // 金額
    'currency' => 'jpy',       // 単位
    'customer' => $customerId, // 顧客ID
]);
```

以上が決済の主な流れです。

# クレジットカードの一覧を表示する

１つの顧客情報(Customer)には、複数のクレジットカードを登録する事ができます。
顧客情報に紐づいているクレジットカードの一覧を取得するには`Customer::allSources`メソッドを実行します。

[/src/card-list/action.php#L14-L20](https://github.com/t-kuni/stripe-php-sample/blob/master/src/card-list/action.php#L14-L20)

```php
$cards = \Stripe\Customer::allSources(
    $customerId, // 顧客ID
    [
        'limit'  => 3,      // 最大件数
        'object' => 'card', // リソース種別
    ]
);
```

レスポンスの形式については公式APIリファレンスが分かりやすいです。
https://stripe.com/docs/api/cards/list

# クレジットカードを追加する

顧客情報(Customer)に、クレジットカードを登録します。
`Customer::createSource`メソッドを実行します。
あらかじめクレジットカードトークンを取得しておく必要があります。

[/src/add-card/action.php#L15-L20](https://github.com/t-kuni/stripe-php-sample/blob/master/src/add-card/action.php#L15-L20)

```php
$card = \Stripe\Customer::createSource(
    $customerId, // 顧客ID
    [
        'source' => $token, // クレジットカードトークン
    ]
);
```



# 所感

以上、PHPでStripeを用いた決済処理を実装する方法の解説でした。
Stripeの使い方のイメージが沸きましたでしょうか？

私個人の感想ですが、さすがデベロッパファーストを謳っているだけあって、簡単に実装できました。
公式ドキュメントも各言語のサンプルがあってわかりやすくて良かったです。
なんならローカルにHTTPSに対応したwebサーバを立てる方が大変だった可能性も・・・。

それでは、よきPHPライフを！

# 参考情報

公式ドキュメント
https://stripe.com/docs

公式APIリファレンス
https://stripe.com/docs/api