【Chargesから】Stripeで3Dセキュア2に対応するAPIへ移行した備忘【PaymentIntentsへ】

## アジェンダ
経済産業省のウェブサイトによると、3Dセキュア2認証の導入が義務化されており、事業者は2025年3月末までに対応する必要があるらしい。

EC取引の増加に伴い、不正利用や詐欺が増加しているため、より強固なセキュリティが求められているとのこと。

> _[経済産業省: 「クレジットカード・セキュリティガイドライン」が改訂されました](https://www.meti.go.jp/press/2023/03/20240315002/20240315002.html
)_

運用している業務システムの決済は、Stripeを導入しており、義務化に向けて本対応が求められた。

**え、既存のStripeの実装はどう改修したらええの？
ってか、3Dセキュアって何、1とか2とかなに？
認証画面ってどうやって出すの、プロダクト側で新規実装しなきゃあかん？**

…という、最初の一歩からつまずきやすかったため、対応した基本的な内容を備忘録としてまとめます。

## 3Dセキュアってなに？
クレジットカード決済する際、各ECサイトの画面からカード会社の認証画面にリダイレクトした事はないだろうか。

それです。

https://docs.stripe.com/payments/3d-secure/authentication-flow?locale=ja-JP&platform=web#when-to-use-3d-secure

### 3Dセキュア1
- モバイル決済に対応していない。
- 認証画面へのリダイレクトが発生し、各カード会社のページで認証操作を行う必要がある。
    - このリダイレクトにより、ECサイトでは **「カゴ落ち（決済前に離脱すること）」** のリスクが増加。
- ユーザーはあらかじめ設定した固定パスワードを入力する必要があり、セキュリティが低い上に操作性も悪かった。

### 3Dセキュア2（EMV 3Dセキュア）
- リダイレクトを必要とせず、webViewなどのインラインフローで認証が可能。
- 認証内容は動的認証（ワンタイムパスワード、生体認証など）を採用。
    - 認証フローのスムーズさにより、ユーザー離脱を軽減。
    - 動的な認証により、セキュリティが大幅に向上。

## ざっくりアプローチ
Stripeでの大まかな流れは下記。
1. `サーバー`
    - 3Dセキュア2に対応したAPIで決済を作成。
    - 決済に3Dセキュア認証が要求される場合、クライアント側で認証フローを実行する必要がある。
1. `クライアント`
    - StripeのSDKを使用し、3Dセキュアのチャレンジフローを表示。ユーザーに認証操作を行ってもらう。
1. `サーバー`
    - 3Dセキュア認証が成功した場合、決済成功の後続処理を実行。失敗した場合は決済を中断。
    - 3Dセキュア認証が不要な場合、クライアント側の処理をスキップして後続処理を実行。
1. Success!!

## 移行方法
### 課題
既存の決済システムは`chargesAPI`で実装されていた。このAPIは3Dセキュア1に対応しているものの、今回対応が求められる3Dセキュア2には対応していない。
そのため、`chargesAPI`を利用している場合は、`paymentIntentsAPI`への移行が必要となる。

https://docs.stripe.com/payments/charges-api

https://docs.stripe.com/payments/payment-intents/migration

両APIの簡単な違いは下記。

#### chargesAPI
`chargesAPI`は、その名の通り支払い専用のAPIで、使用方法は非常にシンプル。メソッドを使ってリクエストを送信するだけで、Stripe上で決済処理が実行される。

```.php
\Stripe\Charge::create([
    'amount' => 5000,
    'currency' => 'usd',
    'customer' => $customerId,
    // その他パラメータ
]);
```

返金処理も可能で、従来の決済システムにおいては`chargesAPI`で十分対応できていた。しかし、3Dセキュア2認証には対応していないため、現在の要件には不適合。残念。

https://docs.stripe.com/api/charges/create

#### paymentIntentsAPI
`chargesAPI`がレガシー技術とされる一方、`paymentIntentsAPI`はStripeの最新機能を利用できるAPIである。3Dセキュア2認証にも対応しており、使い方は`chargesAPI`と大きく変わらない。

`paymentIntentsAPI`では、生成されたpaymentIntentが各決済を管理するオブジェクトとなる。

```.php
\Stripe\PaymentIntent::create([
    'amount' => 5000,
    'currency' => 'usd',
    'payment_method' => $paymentMethodId,
    'off_session' => false,
    // その他パラメータ
]);
```

paymentIntentは、3Dセキュア認証が必要な場合、`clientSecret`というパラメータを持ち、さらに決済処理の状態を示すstatusも保持する。
これらの情報はレスポンスに含まれているため、後続の処理を条件分岐で実装できる。

また、管理画面上でも該当の取引について、3Dセキュア認証待ちであることを示すステータスが確認可能である。

```.json
{
    "client_secret": "pi_3tBLwu2tPa_secret_YicjGHhZloH",
    "next_action": "use_stripe_sdk",
    "status": "requires_action",
}
```

https://docs.stripe.com/payments/payment-intents

https://docs.stripe.com/api/payment_intents/create

### 対応
#### 実装
課題を踏まえ、以下の方針で対応した。

> - バックエンドでは、既存の`chargesAPI`をすべて`paymentIntentsAPI`へ移行。
> - 既存の決済失敗フローを基に、3Dセキュアが要求される場合のみ別の分岐を実装し、デグレードリスクを回避。
> - クライアント側ではStripeのSDKを導入し、3Dセキュア要求に応じたレスポンスを受け取った際に、認証画面（3Dセキュアチャレンジフロー）を表示できるように実装。

3Dセキュアの認証画面（チャレンジフロー）については、プロダクト側で新たに実装する必要は無かった（ありがたい🙌）。

`clientSecret`をStripeの認証関連メソッドに渡すだけで、認証画面が自動的に表示される仕組みである。

なお、公式ドキュメントには、ウェブ、iOS、Android、React Nativeにおける対応方法が簡潔に記載されている。

https://docs.stripe.com/payments/3d-secure/authentication-flow?locale=ja-JP&platform=web#when-to-use-3d-secure

#### その他
決済は比較的複雑で重要な機能のため、入念なテストを行った。
Stripeでは、通常の決済用テストカードに加えて、3Dセキュア認証に対応したテストカードも複数用意されている。これらを活用して認証フローを検証した。

https://docs.stripe.com/testing#regulatory-cards

## 終わりに
StripeのAPIリファレンスは非常に充実しており、実装を進めやすいと感じた。
しかし、特にchargesAPIからpaymentIntentsAPIへの移行手順や、3Dセキュアチャレンジの具体的な実装方法については、情報が少なく苦労した。。。

案外、同じような条件で実装された記事が出回っていなかった。
とはいえ、まだまだ入門レベルではあるが、Stripeに少し強くなった気がする。

あと、アドベントカレンダー初投稿で少し気持ちが上がってる。
ハッピークリスマス🎄