以下の内容はhttps://dev-dub.hatenablog.com/entry/2025/04/27/102428より取得しました。


Stripe: 【3Dセキュア2.0対応】Stripe.jsのconfirmCardPayment()の内部動作を調べました

背景

  • 2025/4/1より3Dセキュアが義務化されました
  • Stripeが3DS対応を全てを吸収してくれる訳ではなく、一部、加盟店側で実装の修正が必要
    • 加盟店
      • アクワイアラー(中間業者=Stripe)を通じて、カードブランド(VISA、Mastercardなど)の決済ネットワークに加盟し、カード決済サービスを受ける事業者(サービス提供者)
  • 実装の際、Stripe.jsconfirmCardPayment() の内部動作を調べたのでメモ
    • 内部動作を調べた事で3DSの理解が深まりました!

ドキュメント、参考ブログ

結論

  • Stripe.js を通じて、Stripeがカード発行会社(イシュアー)のサーバと連携して認証をおこなっている

前提①: 3Dセキュア (英語: 3-D Secure) とは?

  • ネットでクレジットカード決済するときの本人認証
    • 3つのドメインで連携して本人認証を行う仕組み
  • 要するに...

    • クレジットカードに記載されている情報(カード番号、氏名、署名コード)だけで決済できてしまうと、カードを見られてしまったら不正利用されてしまうので、「クレジットカードに記載されていない情報(=TOTPなど)」での本人確認も行う事で、不正利用をブロックする、という仕組み
    • 典型例
      • ネットでクレジットカードで決済したときに、スマホに認証コードが飛んでくる
  • 3つのドメイン

    • アクワイアラー ※加盟店支援者=カードブランドからライセンスを取得し、加盟店の開拓、審査、管理をする機関
      • 決済代行サービス会社
        • Stripe、など
    • カード発行会社 (イシュアー)
    • カードブランド (スキーム)
      • VISA、Mastercard、JCB、など
  • 流れ

    • 利用確認
      • Stripe(アクワイアラー) → カードブランド(スキーム) → カード発行会社(イシュアー) → 購入者
        • カード発行会社 が 購入者 に本人確認画面を表示する
    • 認証
      • 購入者 → カード発行会社(イシュアー) → カードブランド(スキーム) → Stripe(アクワイアラー)
        • 「利用確認」と逆の順序で認証結果が伝搬される
  • 3DS 1.0 vs 3DS 2.0

    • 1.0 ※旧方式
      • 予め登録したパスワードで、決済の度に毎回必ず認証
    • 2.0
      • TOTP等に対応、リスクベースで認証
        • リスクが低い場合は認証がスキップされる

前提②: Stripe.jsのconfirmCardPayment()を利用して3DS認証画面を表示する方法

  1. 決済時に3DS認証が発生すると、payment_intent.statusrequires_action になる
    • この際、Stirpeのeventとしては payment_intent.requires_action eventが発生する (※1)
    • payment_intent.client_secret でクライアントシークレットが返される
  2. 1. のクライアントシークレットを、Stripe.jsのconfirmCardPayment() の第一引数に渡す
    • これだけで Stire.jp が iframe 内に「3DS認証用のモーダル」を表示してくれる
    • 購入者は↑のモーダルに3DS認証に必要なTOTPなどを入力し認証する
  3. confirmCardPayment() から Promise で 3DS認証後の paymentIntent が返される
    • paymentIntent.status等を参照すれば、3DS認証に成功したがどうかを識別できる

※1 payment_intent.requires_action イベントのイベントデータ

{
  "object": {
    "id": "pi_***",
    "object": "payment_intent",
    //...
    "client_secret": "pi_***_secret_***",
    //...
    "status": "requires_action",
    //...
  }
}

実装イメージ

import { loadStripe } from '@stripe/stripe-js'

// ...

// 3Dセキュア認証が必要な場合
if (currentPaymentIntent?.status === 'requires_action') {
  const clientSecret = currentPaymentIntent.client_secret
  const stripeApiPublicKey = process.env.NEXT_PUBLIC_STRIPE_API_PUBLIC_KEY

  const stripe = await loadStripe(stripeApiPublicKey)

  // モーダルで3DS認証画面を表示
  const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment(clientSecret)

  // 3DS認証が成功した場合
  if (paymentIntent?.status === 'succeeded') {
    //...
  } else {
    // 3DS認証に失敗した場合
    //...
  }

Stripe.jsのconfirmCardPayment()の内部動作の調査方法

Stripe.jsによる3DS認証関連のリクエスト内容

  1. POST https://api.stripe.com/v1/payment_intents/:id/confirm

    • レスポンスで status=requires_actionnext_action.type = use_stripe_sdk と返っているので、Stripe SDK での 3DS認証が必要であると判定される
    • next_action.use_stripe_sdk.directory_server_name=visa となっているので、カードブランド (スキーム) が VISA である事をがわかる
  2. POST https://api.stripe.com/v1/3ds2/authenticate

    • レスポンスで acsURL=https://testmode-acs.stripe.com/3d_secure_2_test/acct_***/threeds2_***/challenge 返っているので、StripeがイシュアーACSサーバーと連携する事が読み取れる
    • ※本番環境ではこのURLがイシュアー(=カード発行会社)のドメインになるハズ
  3. Stripe.jsiframe 内に acsURL を読み込み事で、ifreme内に「3DS認証用の画面」を表示

    • この画面で、ユーザーが TOTP などを入力し、認証を通す
    • iframe 内に acsURL が読み込みこまれている様子
      • Stripe.jsがiframe内にacsURLを読み込んでいる様子
  4. POST https://api.stripe.com/v1/3ds2/challenge_complete

    • このリクエストで、イシュアーの認証結果(=この場合は認証OK)を、Stripeのサーバー側に送信 (してるんだと思う)
  5. GET https://api.stripe.com/v1/payment_intents/:id?is_stripe_sdk=false&client_secret=:シークレット

    • 3DS認証完了後のStripe PaymentIntens オブジェクトを取得
      • これが Stripe.jsconfirmCardPayment()Promise として返される



以上の内容はhttps://dev-dub.hatenablog.com/entry/2025/04/27/102428より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14