Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

今回の記事では、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

y_toku
現 Rapyuta Robotics 前 Stripe の日本 でございます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした