フロントエンド実装
このセクションでは、サンプルコードを使用して加盟店アプリケーションのためのフロントエンドの実装方法を示します。
サンプルコードパッケージ内の以下のファイルはフロントエンド内の3DS2認証に必須です。必要な場合はディレクトリツリーで詳細を確認してください。
process.html
- すべての認証シーケンスを実装しています。3ds-web-adapter.js
- 3DS2データをフロントエンドからバックエンドへ渡し、コールバックURLの為に必要なiframe
を生成する3DSクライアントの重要なコンポーネントです。notify_3ds_events.html
- ActiveServerの為に使用されるコールバックページで、認証において(ステップ 7とステップ 18(C))を開始する際に必要になります。このページはチェックアウト処理に認証イベントを渡します。
以下は認証処理と認証シーケンスに基づいたフロントエンド実装の詳細です。
処理1: 認証の初期化¶
認証を初期化する為に、フロントエンドが必要な事:
ユーザーがチェックアウトページ内のチェックアウトを続けるボタンをクリックすると、ブラウザは必要なデータをセッションストレージに保存しprocess.html
ページに移動します。
1 2 3 4 5 6 7 8 9 10 11 | //checkout.html function checkout() { var sessionData = {}; sessionData.channel = "brw"; sessionData.messageCategory = "pa"; sessionData.authData = genAuthData(); sessionData.backButtonType = "toShop"; sessionStorage.setItem("sessionData", JSON.stringify(sessionData)); window.location.href = "/process"; } |
注目
sessionData
が含むもの:
channel
:brw
、3ri
、またはenrol
。上記の例では、ブラウザベースの認証を実行するためにbrw
チャンネルを使用しました。messageCategory
:pa
-決済認証またはnpa
-非決済認証のいずれか。上記の例では、pa
を使用しました。authData
:JSON形式のすべての必要なデータ。データ構造については、APIドキュメントを参照してください。backButtonType
:プロセスページのback
ボタンタイプを示します。上記の例では戻る
ボタンをショップに戻る
として作成します。
注意点:ページ間通信にsessionStorage
を使用するのは、デモ用です。 3DSリクエスターの実際の実装では、既存のチェックアウトプロセスと統合するために、ページ間でパラメーターを転送するための最適なアプローチを選択しましょう。
process.html
ページ内で、sessionData
受信し3DS2処理を開始します。まず、認証を初期化する為の情報を3ds-web-adapterに送信します (ステップ 1) 。
1 2 3 4 5 6 7 8 9 10 | //process.html var sessionData = JSON.parse(sessionStorage.getItem("sessionData")); ... switch (sessionData.channel) { case "brw": var container = $('#iframeDiv'); brw(sessionData.authData, container, _callbackFn, sessionData.messageCategory, sessionData.options); break; ... |
情報
3ds-web-adapter内のbrw()
関数は以下のパラメータを含みます:
authData
:JSON形式のすべての必要なデータ。データ構造については、APIドキュメントを参照してください。container
:複数のiframe
を生成する3ds-web-adapterの事前定義されたコンテナー。callbackFn
:認証結果を処理するコールバック関数。messageCategory
:pa
-決済認証またはnpa
-非決済認証。options
: optional parameters for 3DS Requestor. For example, the 3DS Requestor can choose to cancel the challenge by settingoptions.cancelChallenge=true
.
次に3ds-web-adapter内のbrw()
メソッドから3DSリクエスターバックエンドに認証を初期化する為の情報を送信します(ステップ 2) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //3ds-web-adapter.js function brw(authData, container, callbackFn, messageCategory, options) { _callbackFn = callbackFn; iframeContainer = container; if (options) { _options = options; } //iframeのためにランダムな番号を作成する iframeId = String(Math.floor(100000 + Math.random() * 900000)); //認証を初期化するための3DSリクエスターのURL var initAuthUrl; if (messageCategory) { if (messageCategory === "pa" || messageCategory === "npa") { initAuthUrl = "/auth/init/" + messageCategory; } else { _onError({"Error": "Invalid messageCategory"}); } } else { initAuthUrl = "/auth/init/" + "pa"; } console.log('init authentication', authData); // /auth/init/{messageCategory}に認証を初期化するためにデータを送信する(ステップ 2) doPost(initAuthUrl, authData, _onInitAuthSuccess, _onError); } |
brw()
関数はバックエンドへ/api/v1/auth/init/{messageCategory} POSTリクエストを送信します。APIメッセージオブジェクトはJSON形式で送信されます。バックエンドがどのようにこのリクエストを処理するかを確認するにはこちらを参照してください。
3ds-web-adapterは成功した応答を処理する為に_onInitAuthSuccess()
関数を使用します(ステップ 5) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | //3ds-web-adapter.js function _onInitAuthSuccess(data) { console.log('init auth returns:', data); if (data.threeDSServerCallbackUrl) { serverTransId = data.threeDSServerTransID; $('<iframe id="' + "3ds_" + iframeId + '" width="0" height="0" style="visibility: hidden;" src="' + data.threeDSServerCallbackUrl + '"></iframe>') .appendTo(iframeContainer); } else { _onError(data); } if (data.monUrl) { // 任意でモニタリング用iframeを追加する $('<iframe id="' + "mon_" + iframeId + '" width="0" height="0" style="visibility: hidden;" src="' + data.monUrl + '"></iframe>') .appendTo(iframeContainer); } } |
3ds-web-adapterは2つの隠しiframe
をチェックアウトページに挿入します (ステップ 6)。1つ目のiframe
は(ステップ 7) 用で、threeDSServerCallbackUrl
を使用してブラウザの情報がACSとActiveServerにより収集出来るようにします。
2つ目はオプションのモニタリングiframe
で、ブラウザ情報を収集する間もしくは3DSメソッドの処理中に何かしらのエラーが発生した時に、InitAuthTimedOut
イベントをActiveServer受信できるようになります。このイベントのタイムアウトは15秒です。
処理 2: 認証の実行¶
認証を実行するためにはフロントエンドはブラウザ情報の収集、3DSメソッドデータの収集(収集が可能な場合のみ)を終える必要があります。これらの2つの処理が完了した後、notify_3ds_events.html
は3ds-web-adapter
に認証を続けるよう通知します。この処理内で、何らかの理由によりデータ収集が失敗したか、もしくは完了出来なかった場合、InitAuth処理内でセットアップした別のモニタリングiframe
によってInitAuthTimedOut
のイベントが3ds-web-adapter
に通知され、認証処理は終了されます。
以下は認証を実行するためのステップです。
notify_3ds_events.html
を実装もしくは提供されたものを再利用し、データ収集についてのイベントを受信します。_on3DSMethodSkipped
もしくは_on3DSMethodFinished
イベントが通知された後、認証の実行
メッセージを3DSリクエスターに送信します (ステップ 8とステップ 9) 。- 認証結果をフリクションレスフローもしくはチャレンジフローで処理します。(ステップ 13に続き、ステップ 14(F)またはステップ 14(C)) 。
notify_3ds_events.html
は認証処理を開始するのに使用されます (ステップ 8) 。 3DSリクエスターはnotify_3ds_events.html
にtransId
、callbackName
とオプションのparam
変数を提供します。バックエンド実装を確認するにはこちらを参照してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <!--notify_3ds_events.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>3DSecure 2.0 Authentication</title> </head> <body> <form> <input type="hidden" id="notifyCallback" name="notifyCallback" value={{callbackName}}> <input type="hidden" id="transId" name="transId" value={{transId}}> <input type="hidden" id="param" name="param" value={{callbackParam}}> </form> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script> //チェックアウトページに通知を送り、以降の処理を実行する。 var callbackFn = parent[$('#notifyCallback').val()]; //callbackFnは3ds-notifyハンドラーメソッドにより定義されています。 if (typeof callbackFn === 'function') { callbackFn($('#transId').val(), $('#param').val()); } </script> </body> </html> |
callbackName
に応じて3ds-web-adapter内の異なるメソッドを呼んでいる事が分かります(ステップ 8) 。callbackName
の値は_onThreeDSMethodFinished
、_onThreeDSMethodSkipped
、_onAuthResult
、もしくは_onInitAuthTimedOut
です。それぞれのメソッドの説明は以下になります:
イベント | 説明 |
---|---|
_onThreeDSMethodFinished | 3DSメソッドがACSにより終了し、_doAuth() を呼び出す時である事を通知します |
_onThreeDSMethodSkipped | 3DSメソッドが(利用出来ない、もしくは別の理由により)スキップされ、_doAuth() を呼び出す時である事を通知します3DSメソッドがスキップされたか否かに関わらず、3DSサーバーのブラウザ情報収集は3DSメソッドよりも前に実行される事に注意してください。 |
_onAuthResult | このイベントは認証結果がActiveServerから取得可能になったことを通知します。フリクションレスフローとチャレンジフローで使用されます (ステップ 17(F) と 19(C)) 。 |
_onInitAuthTimedOut | 3DSメソッド中もしくはブラウザ情報収集中にエラーが発生した事を通知します。デモ内ではこのイベントが発生した場合、認証処理は終了されます。 |
ここステップ 8でActiveServerにより返されるcallbackName
は_onThreeDSMethodFinished
もしくは _onThreeDSMethodSkipped
のどちらかです。3ds-web-adapterはこれらのイベントを受信すると、POSTリクエストを送信し認証を実行する為に _doAuth()
を呼び出します (ステップ 9) 。
そしてバックエンドはAPI /api/v1/auth/brwの呼び出しを作成します。バックエンドがどのようにそのリクエストを処理するか確認するにはこちらを参照してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 | //3ds-web-adapter.js function _doAuth(transId) { console.log('Do Authentication for transId', transId); //最初に3DSメソッド用のiframeを取り除く $("#3ds_" + iframeId).remove(); var authData = {}; authData.threeDSRequestorTransID = transId; authData.threeDSServerTransID = serverTransId; doPost("/auth", authData, _onDoAuthSuccess, _onError); } |
3ds-web-adapterは成功した応答を処理する為に_onDoAuthSuccess()
関数を使用します(ステップ 13) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //3ds-web-adapter.js function _onDoAuthSuccess(data) { console.log('auth returns:', data); if (data.transStatus) { if (data.transStatus === "C") { // 3Dリクエスターはチャレンジフローに移行するか否かを選択できます。 if (_options.cancelChallenge) { if (_options.cancelReason) { var sendData = {}; sendData.threeDSServerTransID = serverTransId; sendData.status = _options.cancelReason; doPost("/auth/challenge/status", sendData, _onCancelSuccess, _onCancelError) } else { var returnData = _cancelMessage(); _callbackFn("onAuthResult", returnData); } } else { data.challengeUrl ? startChallenge(data.challengeUrl) : _onError( {"Error": "Invalid Challenge Callback Url"}); } } else { _callbackFn("onAuthResult", data); } } else { _onError(data); } } |
返されたtransStatus
に基づいて異なるフローを実行します。
注目
transStatusはY
、C
、N
、U
、A
とです。
Y
: 認証 / 口座確認に成功C
: チャレンジが必要N
: 未認証 / 口座未確認U
: 認証 / 口座確認を実行できなかったA
: 処理の試行が実施されたR
: 未認証 / 口座未確認または取引拒否
詳細はAPIドキュメントを参照してください。
フリクションレス認証結果¶
もしtransStatus
がC
ではない場合(つまりフリクションレスの場合)、3ds-web-adapterはフリクションレスフローへ移行し(ステップ 14(F)) 、_callbackFn("onAuthResult", data)
を呼び結果を表示します (11行目)。
1 2 3 4 5 6 7 8 9 | //process.html function _callbackFn(type, data) { switch (type) { case "onAuthResult": // "Show results in separate page"を表示する。 $("#sepButton").removeClass("d-none"); showResult(data); break; |
showResult(data)
関数はprocess.html
ページ内に結果を表示します:
フリクションレス認証処理はこの時点で終了になります。そして加盟店チェックアウト処理は、認証結果情報を使用して通常のオーソリ処理に移動できます。
情報
認証結果は別のページにも表示されます。これは処理 3で説明されます。
チャレンジ処理の続行¶
transStatus
がC
の場合、ACSがチャレンジを要求したことを示します。 3DS2認証を継続してチャレンジプロセスに進むか、チャレンジが不要な場合は認証プロセスを終了するかを決定するのは、3DSリクエスター次第です。このデモでは、 options.cancelChallenge
パラメーターを使用して、チャレンジフローに関する3DSリクエスターの意思決定を示します。この機能は、BRWテストページで概説されています。
備考
デモの目的で、キャンセルチャレンジプロセスはフロントエンドのjavascript(
3ds-web-adapter)
で実装されています。セキュリティ上の理由から、このプロセスは本番環境ではバックエンドで実装する必要がある場合があります。
3ds-web-adapterは、options.cancelChallenge
パラメーターをチェックします。 options.cancelChallenge = true
の場合、3ds-web-adapterはチャレンジをキャンセルします。オプションで、指定されたキャンセルの理由に応じて、 options.cancelReason
を設定してActiveServerに送信することもできます。指定された理由は、3ds-web-adapterによってバックエンドから/auth/challenge/status
に送信されます。 バックエンドがこのリクエストを処理する方法を確認するには、こちらを参照してください。
チャレンジ結果のキャンセル画面は、次のスクリーンショットのようになります。
options.cancelChallenge = true
が存在しない場合(または値がfalse
に設定されている場合)、3ds-web-adapterはstartChallenge()
を呼び出し、チャレンジウィンドウにsrc
をchallengeUrl
に設定したiframe
を挿入します(ステップ. 14(C))
1 2 3 4 5 6 7 8 9 10 11 | //3ds-web-adapter.js function startChallenge(url) { _callbackFn("onChallengeStart"); // チャレンジ用iframeを生成する $('<iframe src="' + url + '" width="100%" height="100%" style="border:0" id="' + "cha_" + iframeId + '"></iframe>') .appendTo(iframeContainer); } |
注目
チャレンジシナリオを試したい場合は、こちらのガイドに従ってください。
iframe
のclass
属性がwidth="100%"
とheight="100%"
に設定される事が分かります。これはiframe
がACSに提供される内容に従ってサイズ変更をする為に必要です。チャレンジスクリーンは以下のスクリーンショットに似た表示になるはずです。
そしてカード会員はワンタイムパスワードのような認証データを入力してチャレンジフォームを送信する事が出来ます。ACSは取引とカード会員を認証してチャレンジ結果を返します。
チャレンジフローが終了した後、 ActiveServerは3DSリクエスターバックエンド内の/3ds-notify
エントリーポイントをiframe
を通して呼び出し、_onAuthResult
が呼ばれます。3DSリクエスターは以前 (ステップ 19(C))と同様にnotify_3ds_events.html
ページをレンダリングします。3ds-web-adapterは認証結果を取得しそれをprocess.html
ページに表示する為に_onAuthResult()
関数を呼び出します。
情報
本番環境ではACSはカード会員から得られた情報から複雑なリスクベースの認証を実行します。同様に、認証メソッド(例えばワンタイムパスワード、生体認証など)がカード会員のイシュアーにより決定され、実行されます。
処理3: 認証結果の取得¶
チャレンジ処理が終了した後 (もしくはフリクションレス処理結果を別ページに表示する必要がある場合)、オーソリ処理に必要な最終認証結果を取得するために、認証結果を要求する必要があります。
なぜ別の認証結果要求が必要なのか?
処理 2の後で利用可能な認証の結果を既に持っているのに、なぜ結果をもう一度要求する必要があるのか不思議に思うかもしれません。
これは、ステップ. 13で認証結果がページに転送されているためです。認証結果ページは、チェックアウトページとは別のページとして表示されるのが一般的です。この場合、3ds-web-adapterは結果を3DSリクエスターまたは結果ページに転送できますが、認証結果はクライアント側コードによって再転送されるため、推奨されるデータフローではありません。一般的に安全ではないと見なされます。
3DSリクエスターのサーバー側には、常に元のソース(ActiveServer)から結果を取得する独自のメカニズムが必要です。3DSリクエスターのクライアント側が認証結果を提供するのではなく、3DSリクエスターのサーバー側が認証結果を取得し結果ページを提供することでデータがより安全であることが保証できます。したがって、このステップで認証結果を再要求する必要があります。
このデモでは、process.html
内の処理 2の最後のShow results in separate page
を選択して別のページに結果を表示する事も出来ます。
認証結果を取得し別ページに表示する為に、フロントエンドが必要な事は:
認証結果の取得
メッセージを3DSリクエスターに送信する(ステップ 15(F)もしくはステップ 20(C)) 。- 画面に結果を表示する (ステップ 17(F) もしくは ステップ 22(C)) 。
まず、process.html
ページ内でserverTransId
をsessionStorage
内に保存し、result.html
ページに移動します。
1 2 3 4 5 6 7 8 9 | //process.html function openResultInNewWindow() { if (serverTransId) { var url = '/result'; sessionStorage.setItem("serverTransId", serverTransId); window.open(url, 'newwindow', 'height=800,width=1000'); } } |
result.html
ページ内で、認証結果を取得する為に 3ds-web-adapter内のresult()
メソッドを呼び出します。result()
メソッドは認証結果の取得
メッセージをバックエンドへ送信します。バックエンドはこのリクエストを受信し、/api/v1/auth/brw/resultメッセージをActiveServerに送信します。バックエンドがどのようにこのリクエストを処理するか確認するには、こちらを 参照してください。コールバック関数showData()
はページに結果を表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //result.html var serverTransId = sessionStorage.getItem("serverTransId"); // 認証結果を要求する。(Step 15(F)またはStep 20(C)) result(serverTransId, showData); // 認証結果を別ページに表示する (Step 17(F)またはStep 22(C)) function showData(type, data) { var toShow = "<dl class='row'>"; Object.keys(data).forEach(function (k) { toShow = toShow + "<dt class='col-sm-4'>" + k + "</dt>" + "<dd class='col-sm-8'>" + data[k] + "</dd>"; }); toShow += "</dl>"; $('#showResult').empty().append(toShow); $("#resultCard").removeClass("d-none"); } |
新しい結果画面は以下のスクリーンショットのようになります:
成功
この文書ではフロントエンド統合について説明しました。認証が完了した後、チェックアウト処理は取引ステータス、ECIとCAVVを使用してオーソリ処理を続け、取引を完了出来ます。
次のチャプター
次へボタンを選択し3DSリクエスターの バックエンド実装について確認できます。