今回の記事では、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 カード情報を安全な形で保存する流れ
まずはカード情報をトークン化し安全に取得し、カード決済までをどのような流れで行うかの全体像を説明します。
左上がブラウザ、右上が自身のサーバ、Stripe サーバが中央下です。
流れとしては、下記のようになります。
- Stripe.js & Elements(Checkout)を利用してブラウザから直接 Stripe へ決済(カード)情報を渡す
- Stripe のサーバからフロントエンドにトークンが返ってくる
- トークンを自身のサーバへ送る
- Charge / Customer のリクエストをサーバから送る(決済する / 決済情報を保存する)
- 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 キーでテストと本番環境の動作を分けます。
- 管理画面から API キーを取得: https://dashboard.stripe.com/account/apikeys
1.2 フォームの設置
次に、安全にカード情報を取得するためにフォームを作成していきます。Elements で用意されている UI コンポーネントを用いてフォームを作成します。
<label>
内もしくは、<label for ID属性値>
として、Element コンテナの ID と合わせて作成すると良いと思います。これをすることで、購入者がそれぞれのラベルをクリックすると Element が自動的にフォーカスを当てることができるようになります。
<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作成時に 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
イベントを受け取って、それぞれエラーを表示します。
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 に対応することができます。あまり意味が理解できないかもしれないので、全く同じページを各ブラウザで試してみると理解ができると思います。
上記デモサイトを Chrome、Safari、Microsoft Edge で開いてみると理解しやすいかと思います。フォームのテキスト自身は英語になっていますが、Elements のコンポーネントはローカライズされているので、ブラウザの優先言語が日本語であれば、自動で日本語になっていると思います。
フォームを自作する場合と流れは同じです
1.5.1 Elements をセットアップする
```html:paymentRequestButton
用のコンテナを作成する
公開キーをセットします。これで、どのアカウントからのリクエストか Stripe できます。
var stripe = Stripe('{{公開可能APIキー}});
1.5.2 PaymentRequest のインスタンスを作る
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()
を利用して、実際に利用可能なカードなどがあるかを同時に確かめます。
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
が必要となります。
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 決済する
# 自身のシークレット 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 の作成(保存)
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 を利用した非通過・非保持のトークン化から、決済まで一連の流れを解説してみました。
- 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