Stripe.js & Elements を利用して決済フローを理解する

今回の記事では、Stripe.js & Elements を利用して、カード情報を自社サーバにヒットさせることなく安全に取得し（トークン化し）、決済するまでの一連の流れを解説していきます。

ブラウザ側でカード情報を取得し、バックエンドで決済処理をするというのが大まかな流れです。

# そもそも Stripe.js と Stripe Elements とは何か
- Stripe.js は、決済フローを実現するための JavaScript のライブラリで、カード情報をトークン化して安全に処理する
- Elements は、プリビルトのデザインコンポーネントで、入力時の動作、Placeholder の自動翻訳機能を兼ね備えたコンポーネント。デザインをカスタマイズするためにも利用する

これらを利用することで、自社のサーバにカード情報を通すことなく、クレジットカード決済ができます。いわゆる、自社サーバにて**非通過**、**非保持**な処理が可能となります。

Stripe.js & Elements はデザインを自分のサイトに合わせられるよう、カスタマイズができるよう作られています。埋め込み型のため、他のページに飛ぶことなく、自身のサイトに自然に溶け込むことが可能になります。

一方で、Stripe Checkout というデザインの一部のみ変更可能で、Stripe が用意したデザインのフォームもあります。Checkout も同じようにトークンを利用するので、**非通過**、**非保持**な処理が可能となります。HTML だけで安全なフォームを作成することができます。こちらの詳細は過去に書きましたので、こちらをご覧ください: https://qiita.com/y_toku/items/7bdb534f9143ecdadf8a

# トークンを利用して決済をする or カード情報を安全な形で保存する流れ
まずはカード情報をトークン化し安全に取得し、カード決済までをどのような流れで行うかの全体像を説明します。

![alt](https://stripe.com/img/docs/recipes/switching/charge-workflow.png)


左上がブラウザ、右上が自身のサーバ、Stripe サーバが中央下です。
流れとしては、下記のようになります。

1. Stripe.js & Elements（Checkout）を利用してブラウザから直接 Stripe へ決済（カード）情報を渡す
2. Stripe のサーバからフロントエンドにトークンが返ってくる
3. トークンを自身のサーバへ送る
4. Charge / Customer のリクエストをサーバから送る（決済する / 決済情報を保存する）
5. Stripe からのレスポンスを受ける

これが大まかな流れです。

# 必要な知識
Stripe.js & Elements を利用するには、基本的なレベルの下記の知識が必要です。

- HTML
- CSS
- JavaScript

# 1. Stripe.js & Elements を利用してカード情報をトークン化し自身のサーバへ送る
まずはじめに、カード情報を安全に取得しトークン化するために、Stripe.js & Elements をセットアップしていきます。

## 1.1 セットアップ

```html:セットアップ
<script src="https://js.stripe.com/v3/"></script>
```

直接ロードします。**これを各ページに配置することで、Stripe の不正検知ツール [Radar](https://stripe.com/jp/radar) の精度が上がります。**

```js:インスタンス作成
var stripe = Stripe('{{公開可能APIキー}}');
var elements = stripe.elements();
```

{{公開可能APIキー}}に公開キーを入れ、Elements のインスタンスを作成します。テスト・本番用の API キーでテストと本番環境の動作を分けます。

- 管理画面から API キーを取得: https://dashboard.stripe.com/account/apikeys

## 1.2 フォームの設置
次に、安全にカード情報を取得するためにフォームを作成していきます。Elements で用意されている UI コンポーネントを用いてフォームを作成します。

`<label>`内もしくは、`<label for ID属性値>`として、Element コンテナの ID と合わせて作成すると良いと思います。これをすることで、購入者がそれぞれのラベルをクリックすると Element が自動的にフォーカスを当てることができるようになります。

```html:フォームサンプル（1行にカード番号、CVCなどを表示させる）
<form action="/charge" method="post" id="payment-form">
  <div class="form-row">
    <label for="card-element">
      クレジット・デビットカード番号
    </label>
    <div id="card-element">
      <!-- Stripe Element がここに入ります。 -->
    </div>

    <!-- Element のエラーを入れます。 -->
    <div id="card-errors" role="alert"></div>
  </div>

  <button>お支払い</button>
</form>
```

フォームをロードしたら、Element のインスタンスを作成しマウントします。

```js:Elementのマウントまで
// Element作成時に options から スタイルを調整できます.
var style = {
  base: {
    // ここでStyleの調整をします。
    fontSize: '16px',
    color: "#32325d",
  }
};

// card Element のインスタンスを作成
var card = elements.create('card', {style: style});

// マウント
card.mount('#card-element');
```

`card` Element は一行にカード情報の必要な項目を包含しているので、フォームをシンプルにすることができます。ローカライズがされているので、日本発行のカードが入力されれば郵便番号は出ません。しかし、米国発行のカードを入力すると郵便番号フィールドが出ます。また、ブラウザの設定言語を拾うので日本語設定だと日本語が、英語設定だと英語で表示されていると思います。

もし、有効期限やセキュリティコードは分けて表示したいという場合は、フォームを作成する際のコンテナを分け、Element の`type`で調整します。

- スタイルの`options`調整: Options: https://stripe.com/docs/stripe-js/reference#stripe-elements
- Element の `type`: https://stripe.com/docs/stripe-js/reference#element-types
- サンプルはこちら: https://stripe.github.io/elements-examples/
- テストカードはこちら: https://stripe.com/docs/testing#international-cards

そして最後に項目入力時のエラー処理をします。`change`イベントを受け取って、それぞれエラーを表示します。

```js:EventListner
card.addEventListener('change', function(event) {
  var displayError = document.getElementById('card-errors');
  if (event.error) {
    displayError.textContent = event.error.message;
  } else {
    displayError.textContent = '';
  }
});
```
### 補足: Elements でよくある問い合わせのうちできないこと
- 有効期限年月をプルダウンにはできません
- mm/yy を"年月"にもできません

逆に、これ以外のことはだいたいカスタマイズ可能かなと思います。

なお、Checkout をネイティブアプリ側に実装するとエラーになるというお問い合わせもたまにいただきますが、ネイティブアプリで利用する場合は、iOS/Android SDK を使うか、Stripe.js & Elements を利用します。ネイティブアプリ内での画面遷移が許されないためです。

- iOS: https://stripe.com/docs/mobile/ios
- Android: https://stripe.com/docs/mobile/android

## 1.3 トークンを作成します
Elements で収集された情報をトークンに変換します。

```js:トークン作成
//トークン作成もしくはエラー表示

var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
  event.preventDefault();

  stripe.createToken(card).then(function(result) {
    if (result.error) {
      // エラー表示.
      var errorElement = document.getElementById('card-errors');
      errorElement.textContent = result.error.message;
    } else {
      // トークンをサーバに送信
      stripeTokenHandler(result.token);
    }
  });
});
```

`stripe.createToken` は2つの結果にわかれます
- `result.token`: 成功です: https://stripe.com/docs/api#tokens
- `result.error`: エラーです。クライアントサイドのバリデーションエラーも含みます: https://stripe.com/docs/api#errors

## 1.4 トークンをバックエンドへ渡す

```js:トークンやその他の情報をサーバへ送信
function stripeTokenHandler(token) {
  // 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);

  // Submit します
  form.submit();
}
```

これでカードフォームの作成から、トークン化、そして決済やカード情報を保存するためのプロセスが終わりました。

ここから実際に決済をするためには、2.1 または 2.2 をご覧ください。

## 1.5 Payment Request Button を利用して Google Pay / Apple Pay / Microsoft Pay を Web 上で実装する 

製品の一部で、`paymentRequestButton`という一つ Element があります。これを利用すると一つのソースコードで Apple Pay、Google Pay、Microsoft Pay に対応することができます。あまり意味が理解できないかもしれないので、全く同じページを各ブラウザで試してみると理解ができると思います。

https://stripe-payments-demo.appspot.com/

上記デモサイトを Chrome、Safari、Microsoft Edge で開いてみると理解しやすいかと思います。フォームのテキスト自身は英語になっていますが、Elements のコンポーネントはローカライズされているので、ブラウザの優先言語が日本語であれば、自動で日本語になっていると思います。

フォームを自作する場合と流れは同じです

### 1.5.1 Elements をセットアップする


```html:`paymentRequestButton`用のコンテナを作成する
<script src="https://js.stripe.com/v3/"></script>
<div id="payment-request-button">
  <!-- Stripe Element がここに挿入されます. -->
</div>
```

公開キーをセットします。これで、どのアカウントからのリクエストか Stripe できます。

```js:公開キーの設定
var stripe = Stripe('{{公開可能APIキー}});
```


### 1.5.2 PaymentRequest のインスタンスを作る
`stripe.paymentRequest`のインスタンを作成します。

```js:stripe.paymentRequest
var paymentRequest = stripe.paymentRequest({
  country: 'JP',
  currency: 'jpy',
  total: {
    label: '合計（デモ）',
    amount: 1000,
  },
  requestPayerName: true,
  requestPayerEmail: true,
});
```
`requestPayerName`、`requestPayerEmail`は必須ではないのですがおすすめです。

### 1.5.3 paymentRequestButton Element を作成しマウントする
`paymentRequestButton` Element を作成します。`canMakePayment()`を利用して、実際に利用可能なカードなどがあるかを同時に確かめます。

```js:paymentRequestButton
var elements = stripe.elements();
var prButton = elements.create('paymentRequestButton', {
  paymentRequest: paymentRequest,
});

// Payment Request APIが使えるかをチェックする.
paymentRequest.canMakePayment().then(function(result) {
  if (result) {
    prButton.mount('#payment-request-button');
  } else {
    document.getElementById('payment-request-button').style.display = 'none';
  }
});
```

### 1.5.4 トークンを送信し支払い後の処理をする

```js:トークンをサーバへ送る処理とその後の処理
paymentRequest.on('token', function(ev) {
  // ここでトークンをサーバへ送ります。決済の処理はサーバサイド
  fetch('/charges', {
    method: 'POST',
    body: JSON.stringify({token: ev.token.id}),
    headers: {'content-type': 'application/json'},
  })
  .then(function(response) {
    if (response.ok) {
      // ブラウザ側へ決済が成功したかを伝え、ブラウザ閉じる
      ev.complete('success');
    } else {
      // 決済が失敗した場合は、再度決済画面を表示するかエラーを表示する
      ev.complete('fail');
    }
  });
});
```

---
#### 配送住所が必要な場合
`paymentRequest`を作成するときに、`requestShipping: true `として渡します。そして、どのような情報を取得したいか指定します。下記サンプルです。

```js:配送住所を取得する
var paymentRequest = stripe.paymentRequest({
  country: 'JP',
  currency: 'jpy',
  total: {
    label: 'デモ（合計）',
    amount: 1000,
  },

  requestShipping: true,
  // `shippingOptions` はオプショナルです:
  shippingOptions: [
    // このリスト内のオプションがインターフェイスに表示されます
    {
      id: 'free-shipping',
      label: '配送料無料',
      detail: '通常5営業日以内にお届けいたします。',
      amount: 0,
    },
  ],
});
```

次に、`shippingaddresschange`イベントを聞き、お客さまが配送住所を選択したかを判別します。この時点では正確な`shippingOptions`が必要となります。

```js:shippingaddresschangeイベント
paymentRequest.on('shippingaddresschange', function(ev) {
  if (ev.shippingAddress.country !== 'US') {
    ev.updateWith({status: 'invalid_shipping_address'});
  } else {
    // 配送オプションを取得するためサーバサイドにリクエストします
    fetch('/calculateShipping', {
      data: JSON.stringify({
        shippingAddress: ev.shippingAddress
      })
    }).then(function(response) {
      return response.json();
    }).then(function(result) {
      ev.updateWith({
        status: 'success',
        shippingOptions: result.supportedShippingOptions,
      });
    });
  }
});
```

##### その他の必要事項
- HTTPS 必須
- カード情報をブラウザやウォレット側に登録しておくことが必須
- Apple Pay はドメインの登録が必要: https://stripe.com/docs/stripe-js/elements/payment-request-button#verifying-your-domain-with-apple-pay
- サポート環境に対応しているか確認: https://stripe.com/docs/stripe-js/elements/payment-request-button#testing


# 2. Charge(決済する）もしくは Customer（顧客）情報としてカード情報を保存する
上記 1 で得られたトークンを今度はバックエンドのサーバーで処理します。


## 2.1 決済する

```rb:ChargeCreation
# 自身のシークレット API キーを指定: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "{{シークレットキー}}" 

# トークンを取得
token = params[:stripeToken]

charge = Stripe::Charge.create({
    amount: 1000,
    currency: 'jpy',
    description: 'サンプル決済',
    source: token,
})
```

このように token を `source`に指定して、Charge の API をリクエストすると、1000 円の決済が、Elements 経由で取得したカード情報に対して行われます。

補足
- ドキュメント: https://stripe.com/docs/charges
- オーソリ（与信）とキャプチャ（売上確定）について: https://support.stripe.com/questions/jp-does-stripe-support-authorize-and-capture

## 2.2 Customer 情報として保存し、後日もしくは繰り返し決済する
毎回カード情報を得て、都度決済を行う場合は、上記の方法で良いのですが、同じお客様に毎回カード情報を入力してもらわないようにしたいという場合には、カード情報を customer として保存します。また、Subscription（Billing）で利用する場合は、customer を利用します。Subscription の製品関する記事はこちら: https://qiita.com/y_toku/items/235b5e7ee00792edcbbf

```rb:Customerの作成とCharge
# Customer の作成（保存）
customer = Stripe::Customer.create({
    source: token,
    email: '{{お客様のメアドを指定}}',
})

# Customer に対して charge します:
charge = Stripe::Charge.create({
    amount: 1000,
    currency: 'jpy',
    customer: customer.id, # Customer id を指定する
})

# Customer ID を保存しておき、その後また Charge する際に指定する

charge = Stripe::Charge.create({
    amount: 2000,
    currency: 'jpy',
    customer: customer_id, # 保存しておいた Customer ID を利用する
})
```

より詳しくは[こちら](https://stripe.com/docs/saving-cards)をご覧ください。

以上です。

今回は、Stripe.js & Elements を利用した非通過・非保持のトークン化から、決済まで一連の流れを解説してみました。

![alt](https://stripe.com/img/docs/recipes/switching/charge-workflow.png)


- Stripe.js Elements のドキュメント: https://stripe.com/docs/stripe-js
- Charge（決済）のドキュメント: https://stripe.com/docs/charges
- カード情報を安全な形で保存する際のドキュメント: https://stripe.com/docs/saving-cards


何かご質問があればお気軽にサポートへ日本語でメールをお願いします。
日本語でできるだけ早くサポートチームが回答します: https://support.stripe.com/contact