giftee engineer blog

サーバーサイドでStripe APIを用いた3Dセキュア決済の実装について

2021-12-27

サーバーサイドでStripe APIを用いて3Dセキュア決済を実装する方法について説明します。

thumbnail

はじめに

こんにちは。giftee Loyalty Platformの開発を行っている安達です。先日決済代行サービスStripeを用いた3Dセキュアの実装をする機会があり、その際の実装の知見をシェアしたいと思います。

3Dセキュアの実装方法についてはStripeの公式ドキュメントに詳しいので、基本的にはこちらやAPIドキュメントを参照して欲しいのですが、Stripeに用意されている3Dセキュアを導入するための様々なアプローチのうち、サーバーサイドでStripe APIを用いる実装について、全体像や注意点が具体的に分かるように書きたいと思うので、参考になれば幸いです。

3Dセキュア導入のメリットとデメリット

3Dセキュアとは、クレジットカード決済時に通常のクレジットカード情報の入力に加えて、クレジットカード会社のログイン画面を挟むことによって、よりセキュアな決済手段を提供できるサービスのことです。3Dセキュアを導入するメリットとしては、不正利用の防止に加えて、「ライアビリティシフト」という仕組みにより、3Dセキュアで認証された決済については、クレジットカードユーザーから不審請求の申請が行われた場合の返金責任が、原則として販売者からクレジットカード会社に移行するため、チャージバックによる損失の発生を防げることがあげられます。逆にデメリットとしては、3Dセキュア認証画面を経由することによるコンバージョン率の低下や、3Dセキュア利用のオプション料金の発生があげられるでしょう。

3Dセキュア決済処理(オーソリ・キャプチャ分離あり)のフロー図

サーバーサイドでStripe APIを用いた3Dセキュアの実装について、ユーザーの商品購入操作から決済完了までの処理の全体像を図で示したいと思います。

Stripe 3Dセキュアフロー図

オーソリとキャプチャの分離について

オプション指定をしない場合には3Dセキュア認証が確認されたらただちに決済が確定する仕様になっているのですが、実際のユースケースでは3Dセキュア認証を確認してから商品データ作成等の処理を行い、その処理の完了を確認してから決済を確定したいということがあるかと思います。そういった要件については、クレジットカードの与信枠の確保(オーソリ)と確定(キャプチャ)を分離するようにAPIのオプションを指定することで実現できます。Stripeでのオーソリとキャプチャの分離についてはこちらのドキュメントに解説されています。

3Dセキュア対応カードだがユーザーが3Dセキュア対応の設定を行っていない場合の「みなし認証」について

3Dセキュア対応カードだが、ユーザーが3Dセキュアに必要な設定を行っていない場合には、認証情報の入力なしで、認証情報の入力に成功した場合と同じ挙動になります(みなし認証)。Stripeにはこのみなし認証を判定して処理をコントロールする機能は用意されていませんが、この場合についてもライアビリティシフトの対象となるので、みなし認証を許容することに特にリスクはないと思われます。

Rubyでの実装例

以下では、Stripe公式のstripe-ruby gemを用いた各ステップの実装例を紹介します。

3Dセキュアオプションを指定してオーソリ確認する

payment_intent = Stripe::PaymentIntent.create(
  {
    amount: @stripe_invoice['amount_due'],
    currency: 'jpy',
    confirm: true, # Stripe::PaymentIntent.create後にStripe::PaymentIntent.confirm(オーソリ確認)まで自動で実行する
    capture_method: 'manual', # オーソリ確認後にキャプチャーを自動的に行わない
    payment_method_options: {
      card: {
        request_three_d_secure: 'any' # 3Dセキュアを必ずリクエストする
      }
    },
    return_url: 'http://example.com/three-d-secure/callback' # 3Dセキュア認証完了後にリダイレクトされるURL
  }
)
payment_intent['next_action']['redirect_to_url']['url'] # 3Dセキュア対応カードの場合、ここに入っているURLにユーザーをリダイレクトするとクレジットカード会社の認証ページが表示される

通常Stripeは「Radarルール」と呼ばれる独自の機械学習アルゴリズムによって必要と判断した場合にのみ3Dセキュアをリクエストするのですが、 request_three_d_secure オプションを any で指定することによって、Radarルールによらず必ず3Dセキュアをリクエストするようにできます。

クレジットカード会社の3Dセキュア認証画面にユーザーをリダイレクトする

3Dセキュア対応カードの場合、オーソリ確認の返り値のPaymentIntentのnext_action.redirect_to_url.url プロパティにあるURLにユーザーをリダイレクトするとクレジットカード会社の認証画面が表示されます。

3Dセキュア非対応カードの場合は next_actionnil になるので、3Dセキュア非対応の場合の処理を実装することも可能です。オーソリとキャプチャを分離していない場合、3Dセキュア非対応カードについてはそのまま決済が完了する仕様になっていることには注意が必要です。3Dセキュア非対応カードによる決済を許容したくない場合には、オーソリとキャプチャを分離するオプションを指定する必要があります。

3Dセキュア認証完了後に支払いをキャプチャする

3Dセキュアの認証が完了すると、オーソリ確認時に指定したリダイレクトURLにStripe側で payment_intentpayment_intent_client_secret のクエリパラメータが付与された形でリダイレクトされます。この payment_intent パラメータには認証が完了した PaymentIntentのIDが指定されていて、このIDで支払いをキャプチャできます。

payment_intent_id = params[:payment_intent] # payment_intentパラメータを取得
Stripe::PaymentIntent.capture(payment_intent_id)

これで3Dセキュア認証を経た支払いを確定できました。

派生的な要件を実現するテクニック

以上、必須の部分に絞ったシンプルな形での実装例を紹介しましたが、実際の実装で用いた応用的な手法についていくつか紹介したいと思います。

metadataの活用

PaymentIntentなどのStripeのオブジェクトには、作成時に metadata というプロパティにユーザーが任意に指定したキーと値のセットを保存することができます。

この仕組みを利用することで、例えばアプリ側の商品のDBレコードのIDを保存してリダイレクト後に利用するというような処理を、CookieやRedisを使わなくても実装することができます。

Invoiceでのオーソリ・キャプチャの分離

3DセキュアのリクエストはPaymentIntentまたはSetupIntentオブジェクトを介して行われるようにAPIが設計されているようなのですが、実際にはInvoiceオブジェクトを使いたいケースがあるかと思います。

InvoiceにはPaymentIntentが紐づいているのですが、Invoiceに紐づいているPaymentIntentではオーソリとキャプチャを分離することができない仕様になっているようです。私の今回の実装でもInvoiceを扱う必要があり、この仕様への対応に苦慮したのですが、こちらのブログに解説されていた手法(Invoiceの情報をコピーしたPaymentIntentを作成する)を参考にさせていただき、擬似的にではありますがInvoiceを用いた処理を実現できました。

おわりに

以上、サーバーサイドでStripe APIを用いた3Dセキュアの実装について説明しました。

Stripeには3Dセキュアの実装ひとつとっても多様なアプローチとオプションが用意されているので、実際には個別の要件と各機能の性質を照らし合わせて望ましいアプローチを選択する必要があるかと思います。この記事がその一助になれば幸いです。