はじめに
前回の記事では、PAY.JP の 3Dセキュア認証をサブウィンドウ型で実装した際、
テスト環境では即時認証のため気づけなかった「待ち時間」 が原因で、
本番環境にて画面が進まなくなるケースがあったことを紹介しました。
最終的には、
3Dセキュアの完了API(tds_finish)を最大5分間リトライする実装 に改善することで、
認証コードの受信に時間がかかった場合でも、決済が正しく完了するようになりました。
しかし、この改善を進めていく中で、
- “サブウィンドウ型” の構造そのものが、今回のような問題を生みやすいのでは?
- そもそも最近の PAY.JP では iframe 型が推奨 されている
という気づきもあり、より安定した3Dセキュア認証のために
ワークフロー自体の見直し を行うことを決断しました。
この記事では、その背景と、iframe型への移行過程、実装後の改善点についてまとめます。
サブウィンドウ型を使い続ける上での懸念点
前回の記事で改善したのは、
「3Dセキュア認証の待ち時間に対応できなかった」という 実装側の問題 です。
ただし調査を進める中で、サブウィンドウ型には次のような
構造的に避けがたい課題 があることも見えてきました。
1. 認証画面が別ウィンドウのため、操作状況の把握がしづらい
サブウィンドウ型では、
- 親画面(決済ページ)
- 別ウィンドウ(3Dセキュア認証)
の2つが同時に存在します。
そのため、ユーザーがどの画面を見ているか、
そしてどのタイミングで戻ってくるかをこちらで制御しづらい構造です。
2. ポップアップブロックの影響を受ける
PC・スマホ問わず、
「別ウィンドウを自動で開く」動作はポップアップとして扱われることがあります。
特にスマホではこの挙動が不安定で、
- 別タブが開かない
- 別タブを閉じても元の画面に戻れない
といったケースも実際に発生します。
3. トラブル発生時の切り分けが難しい
「決済が上手くいかない」といった抽象的な問い合わせに対して、
- 端末やブラウザ依存
- OS設定
- ポップアップブロック
- ブラウザの挙動
など、原因がユーザー環境に依存しやすく、
障害と環境依存の切り分けが非常に難しい という問題があります。
前回記事の調査中も、この点がネックとなり、
正しい原因の特定に余計な時間を要しました。
iframe型が登場し、PAY.JPでも推奨されていた
修正対応中に気づきましたが、PAY.JP公式ドキュメントでは、
iframe型が推奨方法として案内されている ことが分かりました。
サブウィンドウ型の説明を見てみると、
下記に列挙するような環境ではポップアップに対して警告やブロックが働くため、3Dセキュアが行えない場合がございます。
とくに、LINEやXなどのSNSアプリ内ブラウザからアクセスした場合、サブウィンドウを開けない環境では利用できないことにご注意ください。
それを踏まえた上でご利用いただくか、別のワークフローでの実装をご検討ください。
(PAY.JP ガイドラインより)
iframe型の提供前はこちらが主流なワークフローでしたが、後述の注意事項にも記載の通り、サブウィンドウ型を導入するよりもiframe型を組み込んだ方が多くの環境で動作可能となるためそちらの使用を先にご検討ください。
(PAY.JP ガイドラインより)
とされており、 iframe 型なら、
- 別ウィンドウを開かない
- ページ内で認証が完結する
- 戻り先の制御が明確
- ポップアップブロックの影響を受けない
など、今回の懸念点を解消してくれる仕組みになっています。
自社サイトにPAY.JPを導入した当初は、まだ iframe 型が提供されておらず、
サブウィンドウ型を採用していました。
iframe型へ移行する決断
前回の改善で、サブウィンドウ型でも利用はできる状態になりました。
しかし、
- ポップアップ系挙動の不安定さ
- 問い合わせ発生時の切り分けの難しさ
- 最新の推奨事項として iframe 型が存在する
これらを踏まえ、
このタイミングで3Dセキュアの方式を iframe 型へ変更する方が正しい
と判断しました。
実装方法
サブウィンドウ型から iframe 型へ移行するにあたり、
PAY.JPが提供する openThreeDSecureIframe() を呼び出すように修正しました。
従来はサブウィンドウ型用に openThreeDSecurePopup() を実行していましたが、
iframe 対応後は PAY.JP の SDK が提供する方法にそのまま置き換えるだけで実装可能 でした。
変更内容としては、3Dセキュア認証の実行処理を次のように書き換えています。
export const runThreeDSecure = (payjpInstance, resourceId) => {
if (!resourceId) {
return Promise.reject(new Error('3Dセキュアを開始できませんでした。'));
}
// iframe型で3Dセキュア認証を実行
return payjpInstance.openThreeDSecureIframe(resourceId);
};
ページ側では、従来通り「認証が必要な場合に3Dセキュアを実行」する流れは変わらず、
呼び出し部分だけを iframe 用メソッドに置き換えています。
if (responseData.requires_three_d_secure && responseData.charge_id) {
try {
await runThreeDSecure(compState.payjp, responseData.charge_id);
} catch (error) {
compState.isProcessing = false;
compState.payjpError = error.message || '3Dセキュア認証を完了できませんでした。';
}
}
サブウィンドウ型のために用意していた
- popup の生成
- タイムアウト監視
- window.open の挙動確認
といったコードは、iframe 移行後はすべて不要となり、
実装が大幅にシンプルになりました。
移行後に改善されたポイント
実際に iframe 型へ移行し、社内テスト・実カード決済を行ったところ、
以下の点が大きく改善されました。
1. 認証がページ内で完結し、UXが向上
認証画面がページ内に埋め込まれるため、
「別ウィンドウに飛ばされる」という処理がなくなり、
操作の流れが自然になりました。
2. ポップアップブロックの影響を完全に回避
iframe はポップアップ扱いではないため、
スマホでも安定して認証画面が表示されるようになります。
3. 戻り先が明確で、実装もシンプルに
サブウィンドウ型では、
- 親画面の状態
- 別ウィンドウの戻り方
などを考える必要がありましたが、
iframe 型では 常に同一ページ上で完結 するため、
制御をしやすく、コードも簡潔になります。
まとめ
今回、PAY.JPの3Dセキュア認証の構造を改めて見直し、
サブウィンドウ型から iframe 型へ移行したことで、以下の改善が得られました。
- ポップアップ依存の不安定要因を排除
- 認証フローのUX向上
- 実装側の制御が明確化
- 問い合わせ時の切り分けが容易
- 本番環境での安定稼働につながった
また、切り替えよう!と決めてからは実装の簡単さに驚かされました。
こんなことなら、iframe 型が提供開始されていることにもっと早く気づきたかった……と悔いるほどです。
3Dセキュア以外にも、仕組み上どうしても待ち時間が発生するような処理は多く存在するので、
安定稼働していても定期的に各サービスのドキュメントを見直し、
ユーザー体験を向上できるよう継続的に改善を行ってかなければ、と認識を改める経験になりました。