ステップ毎の実装ガイド
この章では、認証処理を実行して、序章でダウンロードした簡単なショッピングサイトのプロジェクトに3DS2フローを組み込む方法を説明します。この章の説明は、同じ機能を既存の決済処理に追加する場合のガイドとしても使用できます。
このチュートリアルを通して、Intellij IDEAを使用します。
完成したコードについてさらに詳しく学習するには、/final
ディレクトリにあるGPaymentsのサンプルコードの、3DS2が組み込まれた最終的な加盟店決済ページにアクセスしてください。デモ用の3DSリクエスターのコードの概要は、最終的なデモコードの章を参照してください。
Info
本書の目的は、決済ページがJava以外の別のプログラミング言語で実装されていても、決済ページに3DSリクエスターを実装できるように、3DSリクエスターの基本的な流れを理解するのを助けることにあります。
処理 1: 認証の初期化¶
まず、認証を初期化する手順、すなわち/brw/init/{messageCategory}
を呼び出す手順を説明します。
messageCategory
はpa
(決済認証)またはnpa
(非決済認証)のいずれかです。
Tip
各APIのエンドポイントは認証APIというドキュメント内のAPIに対応する参照先にリンクしており、APIの使用方法に関する詳細情報にアクセスできます。
GPaymentsのサンプル・コードには、/initial
というディレクトリが含まれています。自分のIDEを使用してこのディレクトリを開きます(File > Open > Browse to initial folder > OKと操作します)。
また、認証処理の実装に役立つ3ds-web-adapter.js
も用意されています。
/final
プロジェクトから最初のプロジェクトの/resources/static/jsディレクトリに3ds-web-adapter.js
をコピーし貼り付けます。必要に応じjs
ディレクトリを作成します。このファイルの場所がよく分からない場合は、ディレクトリ構想を確認してください。
次に示す強調表示の行を追加し、3ds-web-adapter.js
ファイルをcheckout.html
ページにインポートします。
1 2 3 4 5 6 7 8 9 10 11 12 | .... <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> <script src="/js/3ds-web-adapter.js"></script> ..... |
アダプターを使用できるようcheckout.html
を編集します。checkout.html
のcheckout()
関数を確認してください。次のコードのようになっているはずです。この関数は、JQueryを使用してカード会員入力フィールドの値をHTML文書から取得し、新しい変数transId
、cardHolderInfo
、purchaseInfo
を初期化します。
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 | //checkout.html function checkout() { var transId = $('#transId').val(); // NOTE: Some attributes are set to default values for demo purpose var cardHolderInfo = {}; if($('#billAddrLine1').val()) { cardHolderInfo.billAddrLine1 = $('#billAddrLine1').val(); } .... if($('#cardExpiry').val()) { purchaseInfo.expiryDate = $('#expiryDate').val(); } //remove cardholder information class, checkout button and show spinner effect $("#checkoutButton").remove(); $("#cardholderInfoCard").remove(); $("#checkoutCard").removeClass("d-none"); //move to result screen 2 seconds after for demo purposes setTimeout(function () { window.location.href = "/auth/result" }, 2000); } |
checkout.html
のcheckout()
に、認証処理の開始時にthreeDSAuth()
メソッド(ステップ.1)を呼び出すための強調表示の行を追加します。
1 2 3 4 5 6 7 8 9 | function checkout() { ... $("#cardholderInfoCard").remove(); $("#checkoutCard").removeClass("d-none"); //move to result screen 2 seconds after setTimeout(function () { window.location.href = "/auth/result"}, 2000); threeDSAuth(transId, cardHolderInfo, purchaseInfo); } |
1 2 3 4 5 6 7 8 | function checkout() { ... $("#cardholderInfoCard").remove(); $("#checkoutCard").removeClass("d-none"); //setTimeout(function () { window.location.href = "/auth/result"}, 2000); threeDSAuth(transId, cardHolderInfo, purchaseInfo); } |
Note
次に示すコードは、3ds-web-adapter.js
で定義されているthreeDSAuth()
関数です。このコードから分かるように、threeDSAuth()
はJSON形式でページから/auth/init
にオブジェクトを送信し(行6)、たとえばthreeDSRequestorTransID
はtransId
により初期化されています。
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 | //3ds-web-adapter.js function threeDSAuth(transId, cardHolderInfo, purchaseInfo) { var postData = {...} .... console.log('init authentication'); $.ajax({ url: '/auth/init', type: 'POST', contentType: "application/json", data: JSON.stringify(postData), success: function (data) { console.log('init auth returns:', data); $('<iframe id="threeds-container" width="0" height="0" style="visibility: hidden;" src="' + data.threeDSServerCallbackUrl + '"></iframe>') .appendTo('.challengeContainer'); }, error: function () { alert('error'); }, dataType: 'json' }); } |
threeDSAuth()
を変更したり、cardholderInfo
、purchaseInfo
、transId
でなくユーザー独自のオブジェクトを転送できます。送信できるデータ要素の型は、最終的なコードの/dto/activeserverディレクトリの中のInitAuthRequestBRW
に示されているほか、APIドキュメントにも示されています。3ds-web-adapter.js
は/auth/init
へのPOSTリクエストを行いますので(ステップ.2)、このリクエストを処理するための新しいコントローラークラスが必要です。
クライアントサイドからのリクエストを処理するためには、/auth/init
リクエストを処理するコントローラーを作成する必要があります。/java/sample_requestorディレクトリ内に新しいコントローラークラスAuthController.java
を作成してください。さらに、/auth/init
リクエストを処理し認証APIエンドポイント/api/v1/{messageCategory}
を呼び出すinitAuth
メソッドをコピーし貼り付けます。
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 | public class AuthController { @PostMapping("/auth/init") public InitAuthResponseBRW initAuth(@RequestBody InitAuthRequestBRW request) { String initBrwUrl = THREE_DS_SERVER_URL + "/api/v1/auth/brw/init/{messageCategory}"; // 認証の初期化 by making POST request to /brw/init/{messageCategory} (ステップ. 3) RequestEntity<InitAuthRequestBRW> req = new RequestEntity<>(request, HttpMethod.POST, URI.create(initBrwUrl)); try { ResponseEntity<InitAuthResponseBRW> resp = restTemplate.exchange(req, InitAuthResponseBRW.class); InitAuthResponseBRW initRespBody = resp.getBody(); logger.info("initAuthResponseBRW {}", initRespBody); // set InitAuthResponseBRW for future use transactionInfo.setInitAuthResponseBRW(initRespBody); return initRespBody; } catch (HttpClientErrorException | HttpServerErrorException ex) { logger.error("initAuthReq failed, {}, {}", ex.getStatusCode(), ex.getResponseBodyAsString()); throw ex; } } } |
決済認証を初期化したい場合は{messageCategory}
をpa
に、非決済認証を初期化したい場合は{messageCategory}
をnpa
に置換します。この例ではpa
を使用しています。
1 2 3 4 5 6 7 | //AuthController.java String initBrwUrl = THREE_DS_SERVER_URL + "/api/v1/auth/brw/init/pa"; // 認証の初期化 by making POST request to /brw/init/{messageCategory} (ステップ. 3) InitAuthResponseBRW initAuthResponseBRW = restTemplate.postForObject(initBrwUrl, request, InitAuthResponseBRW.class); .... |
文字列THREE_DS_SERVER_URL
を、デフォルト設定で与えられるURLに置換します。THREE_DS_SERVER_URL
は、Settings > 3D Secure 2
の中にあるAUTH_API_URL
です。詳細は、ここを参照してください。
1 2 3 | //AuthController.java private final String THREE_DS_SERVER_URL = "https://api.as.testlab.3dsecure.cloud:7443"; .... |
文字列THREE_DS_REQUESTOR_URL
は、デフォルト設定で与えられるURLに置換するか、ホスト対象の3DSリクエスターのURLに更新します。
1 2 3 | //AuthController.java private final String THREE_DS_REQUESTOR_URL = "http://localhost:8082"; .... |
activeserver
ディレクトリを/java/sample_requestor/dto
ディレクトリにコピーし貼り付けます。このディレクトリには、API呼び出しを行うのに必要なすべてのクラスが含まれています。認証を初期化するために最も重要なのは、以下のクラスです。
InitAuthRequestBRW
-/brw/init/{messageCategory}
へのAPI呼び出しを行うのに必要なすべてのデータを保持しています(ステップ.3)。InitAuthResponseBRW
-/brw/init/{messageCategory}
へのAPI呼び出しに対する応答に関する情報を保持しています(ステップ.4)。
次に、AuthController.java
の中にあるfillInitAuthRequestBRW
メソッドについて説明します。このメソッドではInitAuthRequestBRW
にデモ用のデフォルトデータを挿入していますが、実際の状況では、データベースからのデータ、あるいはcheckout.html
から送信されたカード会員情報をロードするように、このメソッドの部分を入れ替えることができます。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | //AuthController.java /** * This method is to fill in the InitAuthRequestBRW with demo data, you need to fill the information from your database * @param initAuthRequestBRW * @param eventCallBackUrl */ private void fillInitAuthRequestBRW(InitAuthRequestBRW initAuthRequestBRW, String eventCallBackUrl) { initAuthRequestBRW.setAcctID("personal account"); // Fill AcctInfo with default data. AcctInfo acctInfo = new AcctInfo(); acctInfo.setChAccAgeInd("03"); acctInfo.setChAccChange("20160712"); acctInfo.setChAccChangeInd("04"); acctInfo.setChAccDate("20140328"); acctInfo.setChAccPwChange("20170328"); acctInfo.setChAccPwChangeInd("02"); acctInfo.setNbPurchaseAccount("11"); acctInfo.setPaymentAccAge("20160917"); acctInfo.setPaymentAccInd("02"); acctInfo.setProvisionAttemptsDay("3"); acctInfo.setShipAddressUsage("20160714"); acctInfo.setShipAddressUsageInd("02"); acctInfo.setShipNameIndicator("02"); acctInfo.setSuspiciousAccActivity("02"); acctInfo.setTxnActivityDay("1"); acctInfo.setTxnActivityYear("21"); initAuthRequestBRW.setAcctInfo(acctInfo); initAuthRequestBRW.setAcctType("03"); initAuthRequestBRW.setAuthenticationInd("01");//01 = Payment transaction // fills ThreeDSRequestorAuthenticationInfo ThreeDSRequestorAuthenticationInfo threeDSRequestorAuthenticationInfo = new ThreeDSRequestorAuthenticationInfo(); threeDSRequestorAuthenticationInfo.setThreeDSReqAuthData("login GP"); threeDSRequestorAuthenticationInfo.setThreeDSReqAuthMethod("02"); threeDSRequestorAuthenticationInfo.setThreeDSReqAuthTimestamp("201711071307"); initAuthRequestBRW.setAuthenticationInfo(threeDSRequestorAuthenticationInfo); // fills MerchantRiskIndicator, optional but strongly recommended for the accuracy of risk based authentication MerchantRiskIndicator merchantRiskIndicator = new MerchantRiskIndicator(); merchantRiskIndicator.setDeliveryEmailAddress("test@123.com"); merchantRiskIndicator.setDeliveryTimeframe("02"); merchantRiskIndicator.setGiftCardAmount("123"); merchantRiskIndicator.setGiftCardCount("02"); merchantRiskIndicator.setGiftCardCurr("840"); merchantRiskIndicator.setPreOrderDate("20170519"); merchantRiskIndicator.setPreOrderPurchaseInd("02"); merchantRiskIndicator.setReorderItemsInd("01"); merchantRiskIndicator.setShipIndicator("01"); initAuthRequestBRW.setMerchantRiskIndicator(merchantRiskIndicator); /** * Options for threeDSRequestorChallengeInd - Indicates whether a challenge is requested for this transaction. * Values accepted: * 01 = No preference * 02 = No challenge requested * 03 = Challenge requested: 3DSリクエスター Preference * 04 = Challenge requested: Mandate * 05–79 = Reserved for EMVCo future use (values invalid until defined by EMVCo) * 80-99 = Reserved for DS use */ initAuthRequestBRW.setChallengeInd("01"); initAuthRequestBRW.setEventCallbackUrl(eventCallBackUrl); //Set this to your url initAuthRequestBRW.setMerchantId("123456789012345"); initAuthRequestBRW.setPurchaseDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); initAuthRequestBRW.setPurchaseInstalData("24"); initAuthRequestBRW.setRecurringExpiry("20180131"); initAuthRequestBRW.setRecurringFrequency("6"); initAuthRequestBRW.setTransType("03"); } |
MerchantTransaction.java
、および対応するgetter
、setter
メソッドに、新しい変数initAuthResponseBRW
を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //MerchantTransaction.java import com.gpayments.requestor.requestor.dto.activeserver.InitAuthResponseBRW; public class MerchantTransaction { private InitAuthResponseBRW initAuthResponseBRW; ... //getter public InitAuthResponseBRW getInitAuthResponseBRW() { return initAuthResponseBRW; } //setter public void setInitAuthResponseBRW(InitAuthResponseBRW initAuthResponseBRW) { this.initAuthResponseBRW = initAuthResponseBRW; } |
ActiveServerへのAPI呼び出しを行うには、RESTTemplateにクライアント証明書を添付する必要があります。
GPaymentsから与えられたクライアント証明書(.p12ファイル)を/resources/certsディレクトリにコピーし貼り付けます。ディレクトリがない場合は/certs
ディレクトリを作成します。証明書ファイルをまだ受け取っていない場合は、お問合わせください。ActiveServerが社内ソフトウェアである場合は、管理者UIからクライアント証明書をダウンロードできます。クライアント証明書のダウンロードに関する詳細は、加盟店のセキュリティーを参照してください。
最終コードに含まれているcacerts.jks
トラストストアファイルを/resources/certsディレクトリにコピーし貼り付けます。証明書が必要なのは、相互SSL認証を可能にするために3DSリクエスターと3DSサーバーの相互認証が必要だからです。
final
パッケージに含まれている設定クラスRestClientConfig.java
を/java/sample_requestor/configディレクトリにコピーし貼り付けます。次のクラスは、クライアント証明書をHttpClientにロードする方法の例です。このクラスは、GPaymentsから与えられたトラストストアを使用してユーザーがActiveServerからダウンロードしたキーストアファイルをロードします。
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 31 32 | //RestClientConfig.java @Configuration public class RestClientConfig { private static final String KEYSTORE_PASSWORD = "123456"; private static final String CA_CERTS_FILE_NAME = "certs/cacerts.jks"; private static final String CLIENT_CERTS_FILE_NAME = "certs/client_certificate.p12"; @Bean public RestTemplate restTemplate() throws IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { SSLContext sslContext = SSLContextBuilder.create() .loadKeyMaterial( new ClassPathResource(CLIENT_CERTS_FILE_NAME).getURL(), KEYSTORE_PASSWORD.toCharArray(), KEYSTORE_PASSWORD.toCharArray()) .loadTrustMaterial( new ClassPathResource(CA_CERTS_FILE_NAME).getURL(), KEYSTORE_PASSWORD.toCharArray()) .build(); CloseableHttpClient client = HttpClients.custom() .setSSLContext(sslContext) .build(); HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(client); return new RestTemplate(httpRequestFactory); } } |
KEYSTORE_PASSWORD
を、デフォルト設定で与えられるパスワードに置換します。ActiveServerが社内ソフトウェアである場合、このパスワードは、管理者ダッシュボードの加盟店ページからクライアント証明書をダウンロードするときに指定したパスワードです。クライアント証明書のダウンロードに関する詳細は、加盟店のセキュリティーを参照してください。1 2 | //RestClientConfig.java private String KEYSTORE_PASSWORD = "123456"; |
Info
これは、クライアント証明書を使用してRESTFul APIリクエストを行う、Java
に固有な方法です。別の言語を使用して、3DSリクエスターが3DSサーバーと相互認証するためのサーバーサイドコードを実装する場合も、同様な手順に従う必要があります。
次のような依存関係をpom.xml
に追加し、SSLContextBuilder
クラスに必要なApache HttpClientを取得します。
1 2 3 4 5 6 7 8 | <dependencies> ... <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency> </dependencies> |
もう一度アプリを実行してhttp://localhost:8082からアクセスし、決済処理を実行します。/api/v1/auth/brw/init/{messageCategory}
に対応する正しい応答が得られ、次のようにログのthreeDSServerCallbackUrl
が有効になっているはずです。
1 2 3 4 | { "threeDSServerCallbackUrl":"https://admin.as.testlab.3dsecure.cloud/api/v1/auth/brw/callback?transId=cbc559c0-96bd-4078-be3c-ea0cc80686f0&t=ZIIgV2LqMak5ONQ1w3l3akfpZlMPftFlOE5Garat6lHiJ3T2Kq2vULuLhIQ7l5UKzpnNpztx", "threeDSServerTransID":"ade87add-b50c-42da-bbea-cdcbd85dcbf3" } |
3ds-notify
の部分で決済処理が停止しますが、これは、この部分がまだ実装されていないためであり、この部分については次の章で対応します。
Tip
変更内容がプロパゲートされるためには、プロジェクトを再実行する必要があります。CTRL+C
を使用して現在実行中のアプリを終了し、コマンドラインに次のように入力してプロジェクトを再実行します。
1 | mvn spring-boot:run |
3DSサーバーはコールバックURLを使用して、ACSは3DSメソッドを使用して、リスクに基づく認証に必要なブラウザー情報を収集します。
Note
/auth/init
からの応答が正常に得られると(ステップ.5)、3ds-web-adapter.js
は非表示のiframe
を決済ページに埋め込み(ステップ.6)、ACSと3DSサーバーはthreeDSServerCallbackUrl
を使用してブラウザー情報を収集できるようになります(ステップ.7)。
1 2 3 4 5 | //3ds-web-adapter.js success: function(data) { $('<iframe id="threeds-container" width="0" height="0" style="visibility: hidden;" src="'+ data.threeDSServerCallbackUrl+'"></iframe>') .appendTo('.challengeContainer'); }, |
処理 2: 認証の実行¶
前のステップで、3ds-web-adapter.js
は、コールバックsrc
がthreeDSServerCallbackUrl
にセットされたiframe
を挿入しています。このため、3DSサーバーはブラウザーへのコールバックを行って(ステップ.7)、必要なブラウザー情報を収集できます。
また、eventCallBackUrl
をTHREE_DS_REQUESTOR_URL/3ds-notify
にセットして/brw/init/{messageCategory}
を呼び出しています(line.2)。これにより、3DSサーバーは、ブラウザー情報の収集が完了したときに通知を行えます。
1 2 3 | //AuthController.java fillInitAuthRequestBRW(request, THREE_DS_REQUESTOR_URL + "/3ds-notify"); ..... |
したがって、このAPI呼び出しを受信するためのハンドラーメソッドを作成しておく必要があります。
以下のコードをMainController.java
に追加します。 このハンドラーメソッドは、パラメーターtransId
とcallbackType
、およびオプションのparam
を取り込みます。callbackType
は、3DSMethodFinished
または3DSMethodSkipped
のいずれかです。ハンドラーメソッドは文字列notify_3ds_events
を返します。Springbootに慣れていない方向けに説明すると、これは、/resources/templatesディレクトリから名前の一致するHTMLページを返すことを意味します。
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 | // MainController.java @PostMapping("/3ds-notify") public String notifyResult( @RequestParam("requestorTransId") String transId, @RequestParam("event") String callbackType, @RequestParam(name = "param", required = false) String param, Model model) { String callbackName; // check the callbackType and initialise callbackName if ("3DSMethodFinished".equals(callbackType)) { callbackName = "_on3DSMethodFinished"; } else if ("3DSMethodSkipped".equals(callbackType)) { callbackName = "_on3DSMethodSkipped"; } else { throw new IllegalArgumentException("invalid callback type"); } //Pass on the object to the page model.addAttribute("transId", transId); model.addAttribute("callbackName", callbackName); model.addAttribute("callbackParam", param); return "notify_3ds_events"; } |
/3ds-notify
への呼び出しにより返されたページであるcallbackFn
を呼び出すためのnotify_3ds_events.html
という新しいHTMLファイルを、/resources/templatesディレクトリ内に作成します。
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 31 32 33 | <!--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" data-th-value="${callbackName}"/> <input type="hidden" id="transId" name="transId" data-th-value="${transId}"/> <input type="hidden" id="param" name="param" data-th-value="${callbackParam}"/> </form> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> <script> //notify parent checkout page to proceed with rest procedures. var callbackFn = parent[$('#notifyCallback').val()]; //callbackFn is defined in 3ds-notify handler method if (typeof callbackFn === 'function') { callbackFn($('#transId').val(),$('#param').val()); } </script> </body> </html> |
callbackName
によって、3ds-web-adapter.js
で呼び出されるメソッドが異なることが分かります(ステップ.8)。 各メソッドについて以下に説明します。_onThreeDSMethodFinished
- 3DSメソッドが完了したことを通知し、ブラウザー情報の収集が完了したことを示す_doAuth()
を呼び出します。_onThreeDSMethodSkipped
- 3DSメソッドをスキップしたことを通知し、ACSによるブラウザー情報の収集をスキップしたことを示す_doAuth()
を呼び出します。
3ds-web-adapter
の_doAuth()
は、/auth
へのPOSTリクエストを行い認証を実行します(ステップ.9)。
このリクエストを処理する新しいハンドラーメソッドをAuthController.java
に追加します。 このメソッドは、threeDSRequestorTransID
とthreeDSServerTransID
を使用して/brw
へのPOST APIリクエストを行うはずです(ステップ.10)。続いて3DSサーバーは、AReqを作成、送信し、AResを受信するとこれを処理して3DS処理を開始します(ステップ.11)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //AuthController.java @PostMapping("/auth") public AuthResponseBRW auth(@RequestParam("id") String transId) { MerchantTransaction transaction = transMgr.findTransaction(transId); //create authentication request. AuthRequestBRW authRequest = new AuthRequestBRW(); authRequest.setThreeDSRequestorTransID(transaction.getId()); authRequest.setThreeDSServerTransID(transaction.getInitAuthResponseBRW().getThreeDSServerTransID()); String brwUrl = THREE_DS_SERVER_URL + "/api/v1/auth/brw"; AuthResponseBRW response = restTemplate.postForObject(brwUrl, authRequest, AuthResponseBRW.class); logger.info("authResponseBRW {}", response); return response; } |
アプリを再実行し、デフォルト値を使用して決済処理を実行すると、/api/v1/auth/brw
に対応する正しい応答が得られ(ステップ.12)、transStatusがY
にセットされているはずです。
1 2 3 4 5 6 | { "authenticationValue":"YXV0aCB2YWx1ZSB4eHh4eHh4eHg=", "eci":"06", "threeDSServerTransID":"52056514-7504-40c7-886f-2a0452d8edbd", "transStatus":"Y" } |
チャレンジシナリオをテストしたい場合は、ここのセクションを参照ください
/brw
へのAPI呼び出しに対する応答では、challengeUrl
がセットされ、transStatus
がC
にセットされます。
1 2 3 4 5 6 7 | { "acsChallengeMandated":"Y", "authenticationType":"01", "challengeUrl":"https://admin.as.testlab.3dsecure.cloud/api/v1/auth/brw/challenge/init?txid=78cd5068-96ea-48c4-b013-e6843fa8b2e4", "threeDSServerTransID":"c8a32fb7-9556-4242-896b-562ee8ca25df", "transStatus":"C", } |
チャレンジウィンドウで要求されるワンタイムパスコードは123456です。エラーが発生しますが、これはcallbackType
、AuthResultReady
の処理機能がまだ実装されていないためであり、この部分は次の章で実装します。
Note
transStatus
がC
の場合、3ds-web-adapter.js
はstartChallenge(url)
を呼び出し、src
がchallengeUrl
にセットされたiframe
をチャレンジウィンドウに挿入します(ステップ.14(C))。
1 2 3 4 5 6 7 8 9 | function startChallenge(url) { //remove the spinner $(".spinner").remove(); //create the iframe $('<iframe src="'+url+'" class="h-100 w-100 border-0" id="challengeWindow" name="challengeWindow"></iframe>') .appendTo('.challengeContainer'); } |
iframe
クラスがh-100
およびw-100
にセットされたことが分かります。これらはそれぞれ、height: 100%!important
およびwidth: 100%!important
というcss型を実装するためのブートストラップ・デフォルト・クラスです。これが必要な理由は、ACSから与えられた内容に応じてiframe
のサイズを調整する必要があるためです。チャレンジ画面は次のスクリーンショットのようになるはずです。
Info
実際の状況でACSが実行するのは、購入金額の確認だけで済むような簡単な処理ではなく、取得したカード会員情報に応じたリスクに基づく複雑な認証処理になります。同様に、認証メソッドはイシュアーにより決定され実行されます。
処理 3: 認証結果の取得¶
前のステップで説明したように、ここでカード会員に表示できるように認証結果の更新を要求する必要があります。フリクションレス・フローの場合も同様に更新を要求する必要があります(次の警告の項を参照)。
認証結果を別に取得しなければいけない理由
フリクションレス・フローの場合、利用可能な認証結果がステップ.12ですでに取得できているのに、なぜもう一度結果を要求する必要があるのか、不思議に思われるかもしれません。
これが必要なのは、ステップ.13で認証結果がページに転送されるためです。3ds-web-adapter
が結果を3DSリクエスターに送り返すことも可能ですが、データが安全でないと見なされます。サーバー側では常に、元の情報源である3DSサーバーから戻される結果をサーバー自身の機能により取得できなければなりません。このステップで結果の受信を要求する必要があるのは、このためです。
チャレンジ・フローの場合は、3DSメソッドのステータスの通知の場合と同様に、認証結果は3ds-notify
のエンドポイントを介して3DSサーバーから通知されます。
MainController.java
で"AuthResultReady" callbackType
を処理できるよう、次に示す強調表示の新しいelse if
ステートメントを追加します。
1 2 3 4 5 6 7 8 | //MainController.java if ("3DSMethodFinished".equals(callbackType)) { callbackName = "_on3DSMethodFinished"; } else if ("3DSMethodSkipped".equals(callbackType)) { callbackName = "_on3DSMethodSkipped"; } else if ("AuthResultReady".equals(callbackType)) { callbackName = "_onAuthResult"; } |
notify-3ds-events
から_onAuthResultReady
が呼び出され、ウィンドウを/auth/result?txid=+transId
にリダイレクトします。 認証結果を取得したい場合は、/brw/result
を呼び出して、ハンドラー/auth/result
の中で結果の受信を要求できます。
1 2 3 4 5 6 7 8 9 10 11 12 | @GetMapping("/auth/result") public String result( @RequestParam("txid") String transId, Model model) { MerchantTransaction transaction = transMgr.findTransaction(transId); String resultUrl = AuthController.THREE_DS_SERVER_URL + "/api/v1/auth/brw/result?threeDSServerTransID=" + transaction.getInitAuthResponseBRW().getThreeDSServerTransID(); AuthResponseBRW response = restTemplate.getForObject(resultUrl, AuthResponseBRW.class); model.addAttribute("result", response); return "result"; } |
result.html
の中の強調表示になっているコードをDelete or comment outします。1 2 3 4 5 6 7 8 9 10 11 12 | <div class="col-sm-8"> <div class="card"> <div class="card-header">Transaction successful</div> <div class="card-body"> <!--<h4 class="card-title text-success">Thank you for your purchase!</h4>--> <!--<p class="card-text">Want to continue shopping? Press "Back to Home".</p>--> <a href="/" class="btn btn-primary btn-block">Back to Home</a> </div> </div> </div> |
認証結果とその他の説明情報が表示されるよう、result.html
の代わりに、次に示す強調表示のコードを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <div class="col-sm-8"> <div class="card"> <div class="card-header">Transaction successful</div> <div class="card-body"> <h4 class="card-title text-success">3D secure authentication was successful</h4> <p class="card-text">You may move onto authorization using the result below or you can press "Back To Home" to restart authentication process.</p> <dl class="row"> <dt class="col-sm-3">Trans Status</dt> <dd class="col-sm-9" data-th-text="${result.transStatus}">Y</dd> <dt class="col-sm-3">ECI</dt> <dd class="col-sm-9" data-th-text="${result.eci}">eci value</dd> <dt class="col-sm-3">CAVV</dt> <dd class="col-sm-9" data-th-text="${result.authenticationValue}">cavv value</dd> <dt class="col-sm-3">Other Info</dt> <dd class="col-sm-9"></dd> </dl> <a href="/" class="btn btn-primary">Back To Home</a> </div> </div> </div> |
Success
お疲れ様でした。ActiveServerが組み込まれた加盟店決済ページの作成作業は、これで完了です。 アプリをもう一度実行してみてください。取引ステータス、ECI、CAVVが含まれた結果ページが正しく表示されるはずです。
Tip
通常、この処理の後は、取引を完了するための、取引ステータス、ECI、CAVVを使用した信用認証の実行に進みます。
最終的なデモコード¶
最終的なデモ用コードには、/final
ディレクトリにあるGPaymentsのサンプル・コードからアクセスできます。このコードは、追加設定を行わなくてもデフォルト設定のままでActiveServerを使用して動作しますので、操作は簡単で、このガイドに従ってアプリを実行するだけです。
コード解説¶
3DSリクエスターのデモ用コードには、以下のようにいくつかのエンドポイントとコンポーネントが実装されています。
/auth/init
- このエンドポイントは3ds-web-adapter.js
から呼び出されて認証初期化リクエストを受信し、3DS2認証処理を開始します。このエンドポイントは、3DSリクエスター内でActiveServerの認証初期化APIである/api/v1/auth/brw/init/{messageCategory}
を呼び出します。/auth
- このエンドポイントは3ds-web-adapter.js
から呼び出されて認証リクエストを受信し、ブラウザー情報の収集後に3DS2認証処理を実行し、オプションのその他の3DSメソッドを実行します。このエンドポイントは、3DSリクエスター内でActiveServerの認証APIである/api/v1/auth/brw
を呼び出します。/3ds-notify
- 3DSメソッドの実行ステータス、認証結果、チャレンジ結果についてActiveServerがiframe
を通して3DSリクエスターに通知できるようにします。3ds-web-adapter.js
- 3DSリクエスター処理のスキャフォールディング用に作成された簡単なJavaScriptライブラリ。com.gpayments.requestor.dto.activeserver
パッケージ内のクラス - ActiveServer API DTO(データ転送オブジェクト)。
加盟店のwebサイトに組み込まれた3DSリクエスターのデモ用コードは、加盟店サイトが決済ページに組み込むための3ds-web-adapter
を提供します。加盟店webサイトは、3ds-web-adapter.js
でthreeDSAuth()
を呼び出してカード会員情報をセットし、認証処理を開始します。また、加盟店webサイトは、3DSリクエスターが関連するコールバックURLと実行すべき3DSメソッドをセットできるようにするためのiframe
も提供する必要があります。
認証処理が終わると、3DSリクエスターからイベントコールバックURL /3ds-notify
を使用して認証結果が送信されます。この時点で、決済代行会社または加盟店は認証処理に進むことができます。
ライトボックス・チャレンジ画面¶
チュートリアルではチャレンジ画面がインラインになっていましたが、webサイトのデザインによってはポップアップ内にチャレンジウィンドウを表示した方が良いかも知れません。 UI自体はACSに用意されていますので、この機能の実装について心配する必要はありません。
checkout.html
を開き、チャレンジウィンドウを定義しているコード部分を見つけてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <!--checkout.html--> <div class="card d-none" id="checkoutCard"> <div class="challengeContainer border"> <div class="spinner row h-100 justify-content-center align-items-center"> <div class="col"> <div class="sk-fading-circle"> <div class="sk-circle1 sk-circle"></div> <div class="sk-circle2 sk-circle"></div> <div class="sk-circle3 sk-circle"></div> <div class="sk-circle4 sk-circle"></div> <div class="sk-circle5 sk-circle"></div> <div class="sk-circle6 sk-circle"></div> <div class="sk-circle7 sk-circle"></div> <div class="sk-circle8 sk-circle"></div> <div class="sk-circle9 sk-circle"></div> <div class="sk-circle10 sk-circle"></div> <div class="sk-circle11 sk-circle"></div> <div class="sk-circle12 sk-circle"></div> </div> <div class="text-center"><img class="w-25" src="images/visa-logo.png"/></div> </div> </div> </div> </div> |
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 | <!--checkout.html--> <!--<div class="card d-none" id="checkoutCard">--> <!--<div class="challengeContainer border">--> <div class="spinner row h-100 justify-content-center align-items-center"> <div class="col"> <div class="sk-fading-circle"> <div class="sk-circle1 sk-circle"></div> <div class="sk-circle2 sk-circle"></div> <div class="sk-circle3 sk-circle"></div> <div class="sk-circle4 sk-circle"></div> <div class="sk-circle5 sk-circle"></div> <div class="sk-circle6 sk-circle"></div> <div class="sk-circle7 sk-circle"></div> <div class="sk-circle8 sk-circle"></div> <div class="sk-circle9 sk-circle"></div> <div class="sk-circle10 sk-circle"></div> <div class="sk-circle11 sk-circle"></div> <div class="sk-circle12 sk-circle"></div> </div> <div class="text-center"><img class="w-25" src="images/visa-logo.png"/></div> </div> </div> </div> </div> <!--Cardholder information --> <div class="card" id="cardholderInfoCard"> |
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 31 32 33 | <!--checkout.html--> <!--<div class="card d-none" id="checkoutCard">--> <!--<div class="challengeContainer border">--> <div class="modal fade" id="authBox" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog h-100 d-flex flex-column justify-content-center my-0" role="document"> <div class="modal-content"> <div class="modal-body challengeContainer"> <div class="spinner row h-100 justify-content-center align-items-center"> <div class="col"> <div class="sk-fading-circle"> <div class="sk-circle1 sk-circle"></div> <div class="sk-circle2 sk-circle"></div> <div class="sk-circle3 sk-circle"></div> <div class="sk-circle4 sk-circle"></div> <div class="sk-circle5 sk-circle"></div> <div class="sk-circle6 sk-circle"></div> <div class="sk-circle7 sk-circle"></div> <div class="sk-circle8 sk-circle"></div> <div class="sk-circle9 sk-circle"></div> <div class="sk-circle10 sk-circle"></div> <div class="sk-circle11 sk-circle"></div> <div class="sk-circle12 sk-circle"></div> </div> <div class="text-center"><img class="w-25" src="images/visa-logo.png"/></div> </div> </div> </div> </div> </div> </div> <!--Cardholder information --> <div class="card" id="cardholderInfoCard"> |
checkout.html
の最後まで下にスクロールしてcheckout()
関数の中の次のコードを探します。1 2 3 4 5 6 7 8 9 10 11 12 13 | //checkout.html function checkout() { .... //remove cardholder information class, checkout button and show spinner effect $("#checkoutButton").remove(); $("#cardholderInfoCard").remove(); $("#checkoutCard").removeClass("d-none"); threeDSAuth(transId, cardHolderInfo, purchaseInfo); } |
1 2 3 4 5 6 7 8 9 10 11 12 | //checkout.html function checkout() { .... //remove cardholder information class, checkout button and show spinner effect $("#checkoutButton").remove(); //$("#cardholderInfoCard").remove(); //$("#checkoutCard").removeClass("d-none"); threeDSAuth(transId, cardHolderInfo, purchaseInfo); } |
これに代わるポップアップ・チャレンジウィンドウ用のモーダルボックスを表示するため、行11を追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //checkout.html function checkout() { .... //remove cardholder information class, checkout button and show spinner effect $("#checkoutButton").remove(); //$("#cardholderInfoCard").remove(); //$("#checkoutCard").removeClass("d-none"); $('#authBox').modal(); threeDSAuth(transId, cardHolderInfo, purchaseInfo); } |
デフォルト設定
最終的なデモ用コードでは、以下の値はデフォルト値に設定されています。この部分は、段階を追ったチュートリアルの実行中に参照できます。
- THREE_DS_SERVER_URL=https://api.as.testlab.3dsecure.cloud:7443
- THREE_DS_REQUESTOR_URL=http://localhost:8082
- KEYSTORE_PASSWORD=123456
Note
お問い合わせ¶
本書の内容についての質問は、弊社担当部門がお受けします。techsupport@gpayments.co.jpまでEメールにてお問い合わせください。