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