背景
- 2025/4/1より3Dセキュアが義務化されました
- Stripeが3DS対応を全てを吸収してくれる訳ではなく、一部、加盟店側で実装の修正が必要
- 加盟店
- アクワイアラー(中間業者=Stripe)を通じて、カードブランド(VISA、Mastercardなど)の決済ネットワークに加盟し、カード決済サービスを受ける事業者(サービス提供者)
- 加盟店
- 実装の際、
Stripe.jsのconfirmCardPayment()の内部動作を調べたのでメモ- 内部動作を調べた事で3DSの理解が深まりました!
ドキュメント、参考ブログ
- Stripeのドキュメント (※私は、以下のドキュメントが全く読解できず、非常に苦戦しました...(結局、コードを書いて理解した))
- 参考になったブログ
結論
- Stripe.js を通じて、Stripeがカード発行会社(イシュアー)のサーバと連携して認証をおこなっている
前提①: 3Dセキュア (英語: 3-D Secure) とは?
- ネットでクレジットカード決済するときの本人認証
- 3つのドメインで連携して本人認証を行う仕組み
要するに...
- クレジットカードに記載されている情報(カード番号、氏名、署名コード)だけで決済できてしまうと、カードを見られてしまったら不正利用されてしまうので、「クレジットカードに記載されていない情報(=TOTPなど)」での本人確認も行う事で、不正利用をブロックする、という仕組み
- 典型例
- ネットでクレジットカードで決済したときに、スマホに認証コードが飛んでくる
3つのドメイン
流れ
3DS 1.0vs3DS 2.01.0※旧方式- 予め登録したパスワードで、決済の度に毎回必ず認証
2.0- TOTP等に対応、リスクベースで認証
- リスクが低い場合は認証がスキップされる
- TOTP等に対応、リスクベースで認証
前提②: Stripe.jsのconfirmCardPayment()を利用して3DS認証画面を表示する方法
- 決済時に3DS認証が発生すると、
payment_intent.statusがrequires_actionになる- この際、Stirpeのeventとしては
payment_intent.requires_actioneventが発生する (※1) payment_intent.client_secretでクライアントシークレットが返される
- この際、Stirpeのeventとしては
1.のクライアントシークレットを、Stripe.jsのconfirmCardPayment()の第一引数に渡す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()の内部動作の調査方法
- ドキュメントから把握
- https://docs.stripe.com/js/payment_intents/confirm_card_payment
- 内部的な動作に関しては記述がなさそう
- https://docs.stripe.com/js/payment_intents/confirm_card_payment
- コードから把握
- https://js.stripe.com/v3 の
confirmCardPaymentメソッドを分析- ※@stripe/stripe-jsを利用すると
<header></header>内に以下のscriptが読み込まれる<script src="https://js.stripe.com/v3"></script>
- => 理屈的にはソースコードから分析可能そうだが、minify されてるので現実的にはツラそう
- ※@stripe/stripe-jsを利用すると
- https://js.stripe.com/v3 の
- Stripe.jsによるリクエスト内容から把握
- => この方法で推論する事にした
Stripe.jsによる3DS認証関連のリクエスト内容
POST https://api.stripe.com/v1/payment_intents/:id/confirmPOST https://api.stripe.com/v1/3ds2/authenticateStripe.jsがiframe内にacsURLを読み込み事で、ifreme内に「3DS認証用の画面」を表示- この画面で、ユーザーが TOTP などを入力し、認証を通す
iframe内にacsURLが読み込みこまれている様子
Stripe.jsがiframe内にacsURLを読み込んでいる様子
POST https://api.stripe.com/v1/3ds2/challenge_complete- このリクエストで、イシュアーの認証結果(=この場合は認証OK)を、Stripeのサーバー側に送信 (してるんだと思う)
GET https://api.stripe.com/v1/payment_intents/:id?is_stripe_sdk=false&client_secret=:シークレット- 3DS認証完了後のStripe PaymentIntens オブジェクトを取得
- これが
Stripe.jsのconfirmCardPayment()のPromiseとして返される
- これが
- 3DS認証完了後のStripe PaymentIntens オブジェクトを取得