stripe
決済
トークン
非通過
非保持

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

左上がブラウザ、右上が自身のサーバ、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 セットアップ

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

直接ロードします。これを各ページに配置することで、Stripe の不正検知ツール Radar の精度が上がります。

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

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

1.2 フォームの設置

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

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

フォームサンプル(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 のインスタンスを作成しマウントします。

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で調整します。

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

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 を利用します。ネイティブアプリ内での画面遷移が許されないためです。

1.3 トークンを作成します

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

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

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 トークンをバックエンドへ渡す

トークンやその他の情報をサーバへ送信
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 をセットアップする

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

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

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

1.5.2 PaymentRequest のインスタンスを作る

stripe.paymentRequestのインスタンを作成します。

stripe.paymentRequest
var paymentRequest = stripe.paymentRequest({
  country: 'JP',
  currency: 'jpy',
  total: {
    label: '合計(デモ)',
    amount: 1000,
  },
  requestPayerName: true,
  requestPayerEmail: true,
});

requestPayerNamerequestPayerEmailは必須ではないのですがおすすめです。

1.5.3 paymentRequestButton Element を作成しマウントする

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

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 トークンを送信し支払い後の処理をする

トークンをサーバへ送る処理とその後の処理
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として渡します。そして、どのような情報を取得したいか指定します。下記サンプルです。

配送住所を取得する
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が必要となります。

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,
      });
    });
  }
});
その他の必要事項

2. Charge(決済する)もしくは Customer(顧客)情報としてカード情報を保存する

上記 1 で得られたトークンを今度はバックエンドのサーバーで処理します。

2.1 決済する

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

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 を利用する
})

より詳しくはこちらをご覧ください。

以上です。

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

alt

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