Stripe でカスタムフォームを表示してカード決済(単品購入&定期購入)

##やりたいこと
・ 単品/定期購入をセレクトボックスで選択
・ クレカ情報を入力
・ Stripeで決済

##環境
・PHP
・Javascript

##参考
・[Stripe Elements公式](https://stripe.com/docs/stripe-js/elements/quickstart)

##1. Stripe Elements読み込み##
```html:index.html
<script src="https://js.stripe.com/v3/"></script>
```
公式ドキュメントではStripeによる不正検知の精度を上げるため
サイト内すべてのファイルで読み込むことを推奨しています。

##2. フォームを表示##
```html:index.html
<form action="charge.php" method="post" id="payment-form">
    <ul>
        <li>
            <select id="product" name="product" class="product">
            <option value="定期購入">定期購入</option>
            <option value="単品購入">単品購入</option>
            </select>
        </li>        
        <li><input type="email" name="email" class="email" placeholder="Email"></li>
        <li><input type="text" name="name" class="name" placeholder="名前"></li>  
                
        <!-- Stripe Elements -->
        <li><div id="card-number" class="stripe-input"></div></li>
        <li><div id="card-expiry" class="stripe-input"></div></li>
        <li><div id="card-cvc" class="stripe-input"></div></li>
        <li><div id="stripe_err" role="alert"></div></li>
                
        <li><button type="submit"> ご購入手続き </button></li>
    </ul>
</form>
```
簡単なフォームと最低限のcssを追加しました。
Stripe Elementsの部分にカード情報入力フォームを表示することになります。
あとからcssで見た目の調整をするためそれぞれのdivにclass="stripe-input"を指定しています。
![スクリーンショット 2019-05-27 13.46.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/347435/4ed819c7-8c52-4aee-e9b4-4ee73b3c23ad.png)
この状態で表示してもStripeElementsは表示されません。
##3. フォームにStripeElementsをマウント##
```html:index.html(JS)
<script type="text/javascript">
    // StripeのAPIキーを読み込み
    var stripe = Stripe('pk_test_XXXXXXXX');

    // Elementsインスタンスを作成
    var elements = stripe.elements();

    // Elementsのスタイルを指定（ここはお好みで）
    var style = {
        base: {
        color: '#32325d',
        fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
        fontSize: '13px'
    },
    invalid: { //値が不正のときに文字色を変える
        color: '#fa755a',
        iconColor: '#fa755a'
    }
    };

    // フォームのidを指定してElementsをマウント
    const cardNumber = elements.create('cardNumber', {style: style,placeholder: 'カード番号 1111 1111 1111 1111'});
    cardNumber.mount('#card-number');
    const cardExpiry = elements.create('cardExpiry',{style: style,placeholder: '有効期限　MM/YY'});
    cardExpiry.mount('#card-expiry');
    const cardCvc = elements.create('cardCvc', {style: style,placeholder: 'セキュリティ番号'});
    cardCvc.mount('#card-cvc');

    // カード番号のリアルタイムバリデーション
    cardNumber.addEventListener('change', function(event) {
        var displayError = document.getElementById('stripe_err');
        if (event.error) {
            displayError.textContent = event.error.message;
        } else {
            displayError.textContent = '';
        }
    });

    // Submitボタンでエラーをチェック
    var form = document.getElementById('payment-form');
    form.addEventListener('submit', function(event) {
        event.preventDefault();
        stripe.createToken(cardNumber).then(function(result) {
            if (result.error) {
                var errorElement = document.getElementById('stripe_err');
                errorElement.textContent = result.error.message;
            } else {
                // Send the token to your server.
                stripeTokenHandler(result.token);
            }
        });
    });

    // フォーム送信処理 (stripeTokenをhiddenで送信)
    function stripeTokenHandler(token) {
        // Insert the token ID into the form so it gets submitted to the server
        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);

        // Submit the form
       form.submit();
    }   
</script>
```

![スクリーンショット 2019-05-28 11.09.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/347435/941c213c-6850-6a95-28d7-6d5f9b4960c6.png)

StripeElementsを表示できました。
このままだとフォームの枠が無い状態なので、cssを追加します。

```css
.stripe-input {
    width: 200px;
    padding:8px;
    margin-bottom:5px;
    border: 1px solid #BBBBBB;
    border-radius:5px;
}
```
![スクリーンショット 2019-05-28 11.05.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/347435/5747f0b1-d7cb-7799-9f27-2e120986ca7a.png)

このように、他のフォームとスタイルを合わせて表示させることができました！

##4. 決済処理##
フォームから実行するphpを作成します。
[Stripeをインストール](https://github.com/stripe/stripe-php)しておきます。PHPならcomposerがおすすめ。
ルートディレクトリで `composer require stripe/stripe-php` だけでOKです。
また、定期購入決済を利用する場合はプランをダッシュボードで作っておく必要があります。

```php:charge.php
require 'vendor/autoload.php';

// シークレットキーを設定
\Stripe\Stripe::setApiKey("sk_test_XXXXXXXXXX");

// トークンとプロダクト(定期/単品どちらか)を変数に格納
$token = $_POST['stripeToken'];
$product = $_POST['product'];

if($product == '単品購入'){
    
    //単品購入なら単品チャージ
    try {
        $responce = \Stripe\Charge::create(array(
        "amount" => 1000, //価格
        "currency" => "jpy",
        "source" => $token,
        "description" => "単品購入",
        ));
    }catch (\Stripe\Error\Card $e) {
        // 決済できなかったときの処理
        console.log($e);
        die('決済が完了しませんでした');
    }
    
}else{
    
    //顧客情報を作成
    try{
        $customer = \Stripe\Customer::create(array(
        'email' => $_POST['email'],
        'source' => $token,
        'metadata' =>['Name' =>$_POST['name']]
        ));
        
    }catch (\Stripe\Error\Card $e) {
        console.log($e);
        die('顧客情報の登録が完了しませんでした');
    }
    
    //定期購入登録
    try{
        $responce = \Stripe\Subscription::create([
        'customer' => $customer['id'],
        'items' => [['plan' => 'plan_XXXXXX']],//予め作成した定期購入プランのIDを指定します
        ]);
        
    }catch (\Stripe\Error\Card $e) {
        console.log($e);
        die('定期購入の登録が完了しませんでした');
    }   
}
```
エラー処理などは割愛して簡略化していますが、決済処理はたったこれだけでOKです。
実装がシンプルでデザインの自由度も高く、初心者でもとっつきやすいです。