バックエンド実装(v2)
この章では、サンプルコードを使用して、加盟店サイトのバックエンドへの実装方法について説明します。
バックエンドには3DSリクエスターを実装する必要があります。3DSリクエスターは3ds-web-adapterから情報を受信しActiveServerへリクエストを送信します。またActiveServerからの認証結果も受信し、結果を3ds-web-adapterへ転送します。
3DSリクエスターのデモのコードは、バックエンドの実装を以下のサーバーサイドの言語付きで提供します:
Java - JavaのバージョンはSpringbootフレームワークを使用して実装されています。Springbootの詳細はhttps://spring.io/projects/spring-bootを参照してください。
C# - C#のバージョンはASP.netを使用して実装されています。
PHP - PHPのバージョンはcURL(Client URL Library)とPHP7.2を使用して実装されています。
Go - GoのバージョンはGo 1.12とGoモジュールサポートを使用して実装されます。すべての依存性は
go.mod
ファイルに記載されています。
なぜバックエンドの実装が必要なのか?
EMVCo's 3Dセキュア2.0の仕様で定義されているように、3DSサーバーと3DSリクエスターの環境が分かれている時、これらの2つのコンポーネント間の通信は相互認証されていなければなりません。
[Req 300] もし3DSリクエスターと3DSサーバーが別のコンポーネントである場合は、コンポーネント間で転送されるデータが、支払いシステムのセキュリティ要件を満たすレベルで保護され両方のサーバーで相互認証されている事を確保してください。
ActiveServerと認証プロセスを行う前に、3DSリクエスターはActiveServer と相互TLS接続を確立する必要があります。クライアント証明書を所持し3DSリクエスターでそれが設定されている事を確認してください。そうでない場合は、導入ページにある クライアント証明書の取得と3DSリクエスター詳細の設定 の説明に従ってください。
HTTPクライアントの為のTLS設定の導入は下記にあります:
次に、認証プロセスと認証シーケンスに基づいたバックエンド実装の詳細について記述します。
Java - TLS構成とクライアント証明書のロード方法は、クラス
RestClientConfig
にあります。C# - TLS構成とクライアント証明書のロード方法は、クラス
RestClientHelper.cs
にあります。PHP - TLS構成とクライアント証明書のロード方法は、ファイル
RestClientConfig.php
にあります。Go - TLS構成とクライアント証明書のロード方法は、ファイル
https.go
にあります。
次に、認証プロセスおよび認証シーケンスに基づいたバックエンド実装の詳細を説明します。
重要
以下のサンプルコードは開発用ガイドのための例であるため、商用環境で十分にテストされたものではありません。そのため商用環境向けにご利用いただく場合は、事前にレビューおよび修正の上、お客様の環境上適切であるかご確認ください。
処理 1: 認証の初期化¶
認証を初期化するためには、3DSリクエスターが以下のように動作する必要があります。
認証の初期化
リクエストを3DS-web-adapter(Step. 2)から受信する。ActiveServerにリクエストを送信する (Step. 3)。
ActiveServer(Step. 4)から応答を受信する。
受信したデータを3DS-web-adapter (Step. 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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | //AuthControllerV2.java @PostMapping("/v2/auth/init") @ResponseBody public Message initAuth( @RequestParam(value = "trans-type", required = false) String transType, @RequestBody Message request, HttpSession session) { return authServiceV2.initAuth(transType, request, session); } //AuthServiceV2.java public Message initAuth(String transType, Message request, HttpSession session) { // リクエスターのトランザクションIDを生成する。 String transId = UUID.randomUUID().toString(); request.put(THREE_DS_REQUESTOR_TRANS_ID, transId); // イベントコールバックURLを「リクエスターURL + /3ds-notify」に設定する。 String callBackUrl = config.getBaseUrl() + "/3ds-notify"; request.put("eventCallbackUrl", callBackUrl); // ActiveServerに認証を初期化するためのデータを送信する。(ステップ 3) // ActiveServerから応答を受け取る (ステップ 4) Message response = sendInitAuthRequest(transType, request, session); response.put(THREE_DS_REQUESTOR_TRANS_ID, transId); logger.info("initAuthResponseBRW: \n{}", response); return response; } private Message sendInitAuthRequest(String transType, Message request, HttpSession session) { ... // 認証を初期化するためのActiveServerのURL String initAuthUrl = config.getAsAuthUrl() + "/api/v2/auth/brw/init"; // 「initAuthUrl」にパラメーター「trans-type=prod」を追加して本番環境DSを使用します。指定しなかった場合はTestLabs DSを使用します。 // 例:「initAuthUrl」をhttps://api.as.testlab.3dsecure.cloud:7443/api/v1/auth/brw/init?trans-type=prodに変更することで本番環境のDSを使用します。 if ("prod".equals(transType)) { initAuthUrl = initAuthUrl + "?trans-type=prod"; } Message response = sendRequest(initAuthUrl, request, HttpMethod.POST); ... return response; } |
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 | //AuthV1Controller.cs /// <summary> /// 認証の初期化要求を3DS-web-adapterから受信する。(ステップ 2) /// ActiveServerに認証を初期化するためのデータを送信する。 /// </summary> /// <param name="request"></param> /// <returns></returns> [HttpPost, Route("v2/auth/init")] { return authServiceV2.initAuth(transType, request); } //AuthServiceV2.cs public Message initAuth(string transType, Message request) { //Generate requestor trans ID string transId = Guid.NewGuid().ToString(); request[THREE_DS_REQUESTOR_TRANS_ID] = transId; // イベントコールバックURLを「リクエスターURL + /3ds-notify」に設定する。 string callBackUrl = Config.BaseUrl + "/3ds-notify"; request["eventCallbackUrl"] = callBackUrl; //Send data to ActiveServer to Initialise authentication (Step 3) //Get the response data from ActiveServer (Step 4) Message response = sendInitAuthRequest(transType, request); response[THREE_DS_REQUESTOR_TRANS_ID] = transId; logger.Info(string.Format("initAuthResponseBRW: \n{0}", response)); return response; } /** * Send data to ActiveServer to Initialise authentication and get the response data from ActiveServer. * * @param transType:「trans-type=prod」を追加して本番環境DSを使用します。指定しなかった場合はTestLabs DSを使用します。 * @param request: init auth request * @return init auth response */ private Message sendInitAuthRequest(String transType, Message request) { setSessionAttribute(INIT_AUTH_REQUEST, request); //認証を初期化するためのActiveServerのURL string initAuthUrl = Config.AsAuthUrl + "/api/v2/auth/brw/init"; // 「initAuthUrl」にパラメーター「trans-type=prod」を追加して本番環境DSを使用します。指定しなかった場合はTestLabs DSを使用します。 // 例:「initAuthUrl」をhttps://api.as.testlab.3dsecure.cloud:7443/api/v1/auth/brw/init?trans-type=prodに変更することで本番環境のDSを使用します。 if ("prod".Equals(transType)) initAuthUrl = initAuthUrl + "?trans-type=prod"; logger.Info(string.Format("initAuthRequest on url: {0}, body: \n{1}", initAuthUrl, request)); Message response = (Message)RestClientHelper.PostForObject(initAuthUrl, request, typeof(Message)); if (response != null) setSessionAttribute(INIT_AUTH_RESPONSE, response); else throw new ArgumentException("Invalid init auth response"); return response; } |
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 | <?php //AuthControllerV2.php /** * 認証の初期化要求を3DS-web-adapterから受信する。 (ステップ 2) * ActiveServerに認証を初期化するためのデータを送信する。 */ public function initAuth() { $requestData = Utils::_getJsonData(); // リクエスターのトランザクションIDを生成する。 $requestData->threeDSRequestorTransID = Utils::_getUUId(); // イベントコールバックURLを「リクエスターURL + /3ds-notify」に設定する。 $requestData->eventCallbackUrl = $this->config->getBaseUrl() . "/3ds-notify"; $transType = $_GET["trans-type"]; $responseBody = $this->authService->sendInitAuthRequest($transType, $requestData); // 3ds-web-adapterにデータを返却する。 (ステップ 5) Utils::_returnJson($responseBody); } public function sendInitAuthRequest($transType, $requestData) { $_SESSION[SessionKeys::INIT_AUTH_REQUEST] = $requestData; //認証を初期化するためのActiveServerのURL // 「initAuthUrl」にパラメーター「trans-type=prod」を追加して本番環境DSを使用します。指定しなかった場合はTestLabs DSを使用します。 // 例:「initAuthUrl」をhttps://api.as.testlab.3dsecure.cloud:7443/api/v1/auth/brw/init?trans-type=prodに変更することで本番環境のDSを使用します。 // 詳細はhttps://docs.activeserver.cloudをご参照ください。 $initAuthUrl = "/api/v2/auth/brw/init"; if (!empty($transType) && $transType == "prod") { $initAuthUrl = $initAuthUrl . "?trans-type=prod"; } // ActiveServerに認証を初期化するためのデータを送信する。(ステップ 3) // ActiveServerから応答を受け取る。(ステップ 4) $response = $this->restTemplate->post($initAuthUrl, $requestData); $responseBody = json_decode($response->getBody()); if ($responseBody != null) { $_SESSION[SessionKeys::INIT_AUTH_RESPONSE] = $responseBody; return $response->getBody(); } else { echo "invalid init auth response"; exit; } } |
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 | //api-v2.go //AuthController routers v2 func authControllerV2(r *gin.Engine, config *Config, httpClient *http.Client, fp *mustache.FileProvider) { r.POST("/v2/auth/init", func(c *gin.Context) { var message Message err := c.ShouldBindJSON(&message) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // リクエスターのトランザクションIDを生成する。 message[ThreeDSRequestorTransId] = uuid.New() // イベントコールバックURLを「リクエスターURL + /3ds-notify」に設定する。 message["eventCallbackUrl"] = config.GPayments.BaseUrl + "/3ds-notify" // 「initAuthUrl」にパラメーター「trans-type=prod」を追加して本番環境DSを使用します。指定しなかった場合はTestLabs DSを使用します。 // 例:「initAuthUrl」をhttps://api.as.testlab.3dsecure.cloud:7443/api/v1/auth/brw/init?trans-type=prodに変更することで本番環境のDSを使用します。 // 詳細はhttps://docs.activeserver.cloudをご参照ください。 callASAPI(asSession{ message: message, url: appendTransTypeIfNecessary("/api/v2/auth/brw/init", c), context: c, httpClient: httpClient, config: config, rHandler: func(resp []byte, contentType string, context *gin.Context) error { msg, err := parseMap(resp) msg[ThreeDSRequestorTransId] = message[ThreeDSRequestorTransId] if err != nil { return err } log.Printf("init auth response: %v\n", msg) // レスポンスをセッションに保持する。 session := sessions.Default(c) session.Set(InitAuthRequest, message) session.Set(InitAuthResponse, msg) err = session.Save() if err != nil { return err } // データを返却する。 responseBytes, err := json.Marshal(msg) if err != nil { return err } context.Data(http.StatusOK, contentType, responseBytes) return nil }}) }) ... } |
注目
eventCallbackUrl
を{baseUrl}/3ds-notify
に設定しています。このURLを設定することでActiveServerからブラウザー情報の収集が完了した時通知を受ける事ができます。(ステップ. 7)。 baseUrl
はデモリクエスター構成にて設定されています。
initAuth
リクエストが{ActiveServer auth url}/api/v2/auth/brw/init/{messageCategory}
のURLに送信されます。initAuth
リクエストのデータ構造をご覧になりたい場合は認証APIドキュメントを参照してください。
デフォルトではGPayments TestLabsに認証リクエストは送信されます
重要:デフォルトでは、上記のURLはテスト目的で認証リクエストをGPayments TestLabsに送信します。本番環境に移行する時に、APIリクエストを国際ブランドのディレクトリサーバーに送信するには、このAPI URLにtrans-typeクエリパラメータを追加する必要があります。詳細な、説明はAPIドキュメントを参照してください。
認証APIマスタークライアント証明書用のHTTPヘッダー¶
認証APIマスタークライアント証明書を使用してビジネス管理者ユーザーとして加盟店に代わって認証を行う場合、バックエンドは、HTTPヘッダーにAS-Merchant-Token
フィールドを追加する必要があります。このフィールドは加盟店プロファイルから取得できるマーチャントトークンを設定します。
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 | //注目: sendRequest()がActiveServerにリクエストを送信します。 //これがgroupAuthの場合、リクエストにはAS-Merchant-Tokenのフィールドを持つHTTPヘッダーが含まれている必要があります。 private Message sendRequest(String url, Message request, HttpMethod method) { HttpEntity<Message> req; HttpHeaders headers = null; if (config.isGroupAuth()) { //the certificate is for groupAuth, work out the header. headers = new HttpHeaders(); headers.add("AS-Merchant-Token", config.getMerchantToken()); } switch (method) { case POST: req = new HttpEntity<>(request, headers); return restTemplate.postForObject(url, req, Message.class); case GET: if (headers == null) { return restTemplate.getForObject(url, Message.class); } else { req = new HttpEntity<>(headers); return restTemplate.exchange(url, HttpMethod.GET, req, Message.class).getBody(); } default: return null; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //注目: PostForObject()がActiveServerにリクエストを送信します。 //これがgroupAuthの場合、リクエストにはAS-Merchant-Tokenのフィールドを持つHTTPヘッダーが含まれている必要があります。 //RestClientHelper.cs public static object PostForObject(string url, object request, Type responseType) { HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(url); req.Method = "POST"; req.ClientCertificates.Add(GetClientCertificate()); //the certificate is for groupAuth, work out the header if (Config.GroupAuth) req.Headers.Add("AS-Merchant-Token", Config.MerchantToken); req.ContentType = "application/json;charset=utf-8"; string strRequest = JsonConvert.SerializeObject(request, Formatting.Indented, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); byte[] postData = System.Text.Encoding.UTF8.GetBytes(strRequest); req.ContentLength = postData.Length; using (Stream streamOut = req.GetRequestStream()) streamOut.Write(postData, 0, postData.Length); string result = null; using (StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream())) result = streamIn.ReadToEnd(); return JsonConvert.DeserializeObject(result, responseType); } |
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 | <?php //注目: post()がActiveServerにリクエストを送信します。 //これがgroupAuthの場合、リクエストにはAS-Merchant-Tokenのフィールドを持つHTTPヘッダーが含まれている必要があります。 //RestClientConfig.php public function __construct(Config $config) { ... if ($config->isGroupAuth()) { $this->headers = array('AS-Merchant-Token' => $config->getMerchantToken()); } } function post($request_uri, $post_data) { $response = $this->client->request( "POST", $request_uri, ['json' => $post_data, 'headers' => $this->headers]); return $response; } function get($request_uri) { $response = $this->client->request("GET", $request_uri, ['headers' => $this->headers]); return $response; } |
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 | //注目: callAsAPI()がActiveServerにリクエストを送信します。 //これがgroupAuthの場合、リクエストにはAS-Merchant-Tokenのフィールドを持つHTTPヘッダーが含まれている必要があります。 func callASAPI(session asSession) { //generate the url, if the url starts with http, use it as is otherwise prefix with the base URL var url string if strings.HasPrefix(strings.ToLower(session.url), "http") { url = session.url } else { url = session.config.GPayments.AsAuthUrl + session.url } if session.message == nil { log.Println("Send GET request to 3DS Server, url: " + url) r, err = http.NewRequest("GET", url, nil) if err != nil { session.context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } else { var data []byte data, err = json.Marshal(session.message) if err != nil { session.context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } log.Printf("Send POST request to 3DS Server, url: %s, body: %v\n", url, session.message) r, err = http.NewRequest("POST", url, bytes.NewBuffer(data)) if err != nil { session.context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } r.Header.Set("Content-Type", "application/json;charset=utf-8") } if session.config.GPayments.GroupAuth { r.Header.Set("AS-Merchant-Token", session.config.GPayments.MerchantToken) } response, err := session.httpClient.Do(r) if err != nil { session.context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer response.Body.Close() contentType := response.Header.Get("Content-Type") responseBody, err := ioutil.ReadAll(response.Body) if err != nil { session.context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } log.Printf("Received response with content type: %s, content: %s\n", contentType, string(responseBody)) if session.rHandler != nil { log.Printf("Received response with content type: %s, content: %s\n", contentType, string(responseBody)) if session.rHandler != nil { //process the response by the responseHandler. the handle returns the data as well. if err = session.rHandler(responseBody, contentType, session.context); err != nil { session.context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } else { //if no response handler provided, return the data by default. session.context.Data(http.StatusOK, contentType, responseBody) } } |
処理2: 認証の実行¶
認証を実行する為に3DSリクエスターが必要な事は:
- ステップ 7の後、ActiveServerからの
/3ds-notify
メッセージを処理する。 - 3DS-web-adapter からの
認証の実行
リクエストを処理する (ステップ 9とステップ 10) 。 - 認証結果を受信し、それを3DS-web-adapterへ返答する (ステップ 12とステップ 13) 。
ブラウザ情報の収集 (ステップ7)が完了した時、ActiveServer はあなたがeventCallBackUrl
に設定したhttp://localhost:8082/3ds-notify
に通知を行います。3DSリクエスターはこの通知を処理し必要なパラメータをnotify-3ds-events.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 | //MainController.java @PostMapping("/3ds-notify") public String notifyResult( @RequestParam("requestorTransId") String transId, @RequestParam("event") String callbackType, @RequestParam(name = "param", required = false) String param, @RequestParam(name = "threeDSSessionData", required = false) String threeDSSessionData, Model model) { String callbackName; if ("3DSMethodFinished".equals(callbackType)) { callbackName = "_on3DSMethodFinished"; } else if ("3DSMethodSkipped".equals(callbackType)) { callbackName = "_on3DSMethodSkipped"; } else if ("AuthResultReady".equals(callbackType)) { callbackName = "_onAuthResult"; } else { throw new IllegalArgumentException("invalid callback type"); } model.addAttribute("transId", transId); model.addAttribute("callbackName", callbackName); model.addAttribute("callbackParam", param); model.addAttribute("callbackThreeDSSessionData", StringUtils.hasLength(threeDSSessionData) ? threeDSSessionData : ""); return "notify_3ds_events"; } |
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 | //MainController.cs [HttpPost, ActionName("3ds-notify")] public ActionResult NotifyResult() { string transId = Request.Params["requestorTransId"]; string callbackType = Request.Params["event"]; string param = Request.Params["param"]; string threeDSSessionData = Request.Params["threeDSSessionData"]; String callbackName; if ("3DSMethodFinished".Equals(callbackType)) callbackName = "_on3DSMethodFinished"; else if ("3DSMethodSkipped".Equals(callbackType)) callbackName = "_on3DSMethodSkipped"; else if ("AuthResultReady".Equals(callbackType)) callbackName = "_onAuthResult"; else throw new ArgumentException("invalid callback type"); dynamic model = new ExpandoObject(); model.transId = transId; model.callbackName = callbackName; model.callbackParam = param; model.callbackThreeDSSessionData = threeDSSessionData; return View("notify_3ds_events", model); } |
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 | <?php //MainController.php public function notifyResult() { $requestorTransId = $_POST["requestorTransId"]; $callbackType = $_POST["event"]; $param = (isset($_POST["param"]) && !empty($_POST["param"])) ? $_POST["param"] : ""; $threeDSSessionData = $_POST["threeDSSessionData"]; if ($callbackType === "3DSMethodFinished") { $callbackName = "_on3DSMethodFinished"; } else if ($callbackType === "3DSMethodSkipped") { $callbackName = "_on3DSMethodSkipped"; } else if ($callbackType === "AuthResultReady") { $callbackName = "_onAuthResult"; } else if ($callbackType === "InitAuthTimedOut") { $callbackName = "_onInitAuthTimedOut"; } else { echo "invalid callback type"; exit; } $this->model["transId"] = $requestorTransId; $this->model["callbackName"] = $callbackName; $this->model["callbackParam"] = $param; $model["callbackThreeDSSessionData"] = $threeDSSessionData; $this->_render("notify_3ds_events"); } |
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 | //main.go //Main controller func mainController(r *gin.Engine, config *Config) { ... r.POST("/3ds-notify", func(c *gin.Context) { transId := c.PostForm("requestorTransId") if transId == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parameter"}) } event := c.PostForm("event") if event == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid parameter"}) } param := c.Param("param") threeDSSessionData := c.PostForm("threeDSSessionData") var callbackName string switch event { case "3DSMethodFinished": callbackName = "_on3DSMethodFinished" case "3DSMethodSkipped": callbackName = "_on3DSMethodSkipped" case "AuthResultReady": callbackName = "_onAuthResult" case "InitAuthTimedOut": callbackName = "_onInitAuthTimedOut" default: c.JSON(http.StatusBadRequest, gin.H{"error": "invalid callback type"}) return } renderPage(gin.H{ "transId": transId, "callbackName": callbackName, "callbackParam": param, "callbackThreeDSSessionData": threeDSSessionData, }, notify3DSEventsTpl, c) }) } |
注目
このハンドラーメソッドはActiveServerにより呼び出され、ActiveServerによりrequestorTransId
、event
、オプションのparam
が提供されます。event
は3DSMethodFinished
、3DSMethodSkipped
、もしくはAuthResultReady
になります。そしてハンドラーメソッドはページコンテキスト内の適切な属性値を設定しnotify_3ds_events.html
ページへ返します。 そしてMustache テンプレートエンジンを使用してページにレンダリングします。
フロントエンドのnotify_3ds_events.html
の実装を確認したい場合はこちらから確認できます。
そして3DSクライアントがブラウザ情報の収集を終えた時、auth
エンドポイントを呼び出し認証を開始します。 3DSリクエスターは認証の実行
リクエストを処理します。(ステップ 9、ステップ 10、ステップ 12とステップ 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 31 32 33 34 35 36 37 38 39 40 41 | //AuthControllerV2.java /** * 認証の実行要求を3DS-web-adapterから受信する。 (ステップ 9) * 受信したデータをActiveServerに送信し認証を実行する。 */ @PostMapping("/v2/auth") @ResponseBody public Message auth(@RequestBody Message request) { return authServiceV2.auth(request, session); } //AuthServiceV2.java public Message auth(Message request, HttpSession session) { verifySessionTransId((String) request.get(THREE_DS_REQUESTOR_TRANS_ID), session); // データをActiveServerに送信し認証を実行する。(ステップ 10) // 応答データをActiveServerから受信する。(ステップ 12) Message response = sendAuthRequest(request, session); logger.info("authResponseBRW: \n{}", response); return response; } private Message sendAuthRequest(Message request, HttpSession session) { setSessionAttribute(AUTH_REQUEST, request, session); // 認証を実行するためのActiveServerのURLをセッションから取ってくる Message initAuthResponse = getSessionAttribute(INIT_AUTH_RESPONSE, session); String authUrl = (String) initAuthResponse.get("authUrl"); logger.info("requesting BRW Auth API {}, body: \n{}", authUrl, request); Message response = sendRequest(authUrl, request, HttpMethod.POST); if (response != null) { setSessionAttribute(AUTH_RESPONSE, response, session); } else { throw new IllegalArgumentException("Invalid auth response"); } return response; } |
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 | //AuthV2Controller.cs /// <summary> /// 認証の実行要求を3DS-web-adapterから受信する。 (ステップ 9) /// 受信したデータをActiveServerに送信し認証を実行する。 /// </summary> /// <param name="trans"></param> /// <returns></returns> [HttpPost, Route("v2/auth")] public Message auth([FromBody]Message request) { return authServiceV2.auth(request); } //AuthServiceV2.cs public Message auth(Message request) { verifySessionTransId((String)request[THREE_DS_REQUESTOR_TRANS_ID]); //データをActiveServerに送信し認証を実行する。 (ステップ 10) //応答データをActiveServerから受信する。 (ステップ 12) Message response = sendAuthRequest(request); logger.Info(string.Format("authResponseBRW: \n{0}", response)); return response; } private Message sendAuthRequest(Message request) { setSessionAttribute(AUTH_REQUEST, request); //get authUrl from session storage Message initAuthResponse = getSessionAttribute(INIT_AUTH_RESPONSE); String authUrl = (String)initAuthResponse["authUrl"]; logger.Info(string.Format("requesting BRW Auth API {0}, body: \n{1}", authUrl, request)); Message response = (Message)RestClientHelper.PostForObject(authUrl, request, typeof(Message)); if (response != null) { setSessionAttribute(AUTH_RESPONSE, response); } else { throw new ArgumentException("Invalid auth response"); } return response; } |
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 | <?php //AuthControllerV2.php /** * 認証の実行要求を3DS-web-adapterから受信する。 (ステップ 9) * 受信したデータをActiveServerに送信し認証を実行する。 */ public function auth() { $requestData = Utils::_getJsonData(); //データをActiveServerに送信し認証を実行する。 (ステップ 10) //応答データをActiveServerから受信する。 (ステップ 12) $response = $this->authService->sendAuthRequest($requestData); //Return data to 3ds-web-adapter (Step 13) Utils::_returnJson($response); } //AuthServiceV2.php function sendAuthRequest($requestData) { $_SESSION[SessionKeys::AUTH_REQUEST] = $requestData; //ActiveServer url for Execute Authentication $authUrl = $_SESSION[SessionKeys::INIT_AUTH_RESPONSE]->authUrl; //データをActiveServerに送信し認証を実行する。 (ステップ 10) //応答データをActiveServerから受信する。 (ステップ 12) $response = $this->restTemplate->post($authUrl, $requestData); if ($response != null) { $_SESSION[SessionKeys::AUTH_RESPONSE] = json_decode($response->getBody()); return $response->getBody(); } else { echo "invalid auth response"; exit; } } |
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 | //api-v2.go r.POST("/v2/auth", func(c *gin.Context) { //get authUrl from session authUrl := getSessionAttribute(c, InitAuthResponse, "authUrl").(string) if authUrl == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid authUrl"}) return } var message Message err := c.ShouldBindJSON(&message) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } callASAPI(asSession{ message: message, url: authUrl, //this url will be used as is context: c, httpClient: httpClient, config: config}) }) ... } |
注目
3DSリクエスターは、フロントエンドへ返答します。返されるメッセージには、フリクションレスフローまたはチャレンジフローとデカップルドフローのいずれかによってY
、D
またはC
のtransStatus
値を含みます。フロントエンドがどのように transStatus
を処理するかを確認するには、こちらを参照してください。応答データの構造を確認するには、APIドキュメントを参照してください。
チャレンジフローをキャンセル¶
transStatus = C
の場合、3DSクライアントはチャレンジを開始するか否かを選択できます。3DSクライアントがチャレンジをキャンセルすることを選択した場合、 /auth/challenge/status
エンドポイントを呼び出してキャンセルした理由を指定できます。 3DSリクエスターがActiveServerに送信します。 フロントエンドがこのリクエストを処理する方法を確認するには、こちらを参照してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //AuthControllerV2.java @PostMapping("/auth/challenge/status") @ResponseBody public Message challengeStatus(@RequestBody Message request) { return authServiceV2.challengeStatus(request); } //AuthServiceV2.cs public Message challengeStatus(Message request) { String challengeStatusUrl = Config.AsAuthUrl + "/api/v2/auth/challenge/status"; logger.Info(string.Format("request challenge status API {0}, body: \n{1}", challengeStatusUrl, request)); Message response = (Message)RestClientHelper.PostForObject(challengeStatusUrl, request, typeof(Message)); logger.Info(string.Format("challengeStatus response: \n{0}", response)); return response; } |
1 2 3 4 5 6 7 8 9 10 11 12 | //AuthV1Controller.cs [HttpPost, Route("v2/auth/challenge/status")] public Message challengeStatus([FromBody]Message request) { String challengeStatusUrl = Config.AsAuthUrl + "/api/v2/auth/challenge/status"; logger.Info(string.Format("request challenge status API {0}, body: \n{1}", challengeStatusUrl, request)); Message response = (Message)RestClientHelper.PostForObject(challengeStatusUrl, request, typeof(Message)); logger.Info(string.Format("challengeStatus response: \n{0}", response)); return response; } |
1 2 3 4 5 6 7 8 9 | <?php //AuthControllerV2.php public function challengeStatus() { $requestData = Utils::_getJsonData(); $challengeStatusUrl = "/api/v2/auth/challenge/status"; $response = $this->restTemplate->post($challengeStatusUrl, $requestData); Utils::_returnJson($response->getBody()->getContents()); } |
1 2 3 4 5 6 7 8 9 10 | //AuthControllerV2 routers r.POST("/v2/auth/challenge/status", func(c *gin.Context) { var message map[string]interface{} err := c.ShouldBindJSON(&message) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } callASAPI(message, "/api/v2/auth/challenge/status", c, httpClient, config, nil) }) |
処理 3: 認証結果の取得¶
認証結果を取得する為に3DSリクエスターが必要な事は:
- 3DS-web-adapterからの
認証結果の取得
リクエストを処理する (ステップ 15(F)もしくはステップ 20(C)) 。 - 結果を取得しフロントエンドへ返す為にActiveServerへリクエストを送信する (ステップ 16(F)、 ステップ 17(F) もしくは ステップ 21(C)、22(C)) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //AuthControllerV2.java @ResponseBody @GetMapping("/v2/auth/brw/result") public Message resultBRW(@RequestParam("txid") String serverTransId) { return authServiceV2.getBRWResult(serverTransId); } //AuthServiceV2.java public Message getBRWResult(String serverTransId) { //ActiveServer url for Retrieve Results String resultUrl = config.getAsAuthUrl() + "/api/v2/auth/brw/result?threeDSServerTransID=" + serverTransId; //Get authentication result from ActiveServer Message response = sendRequest(resultUrl, null, HttpMethod.GET); logger.info("authResponse: \n{}", response); return response; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /// <summary> /// 認証結果の取得要求を受信する。 (ステップ15(F)とステップ20(C)) /// ActiveServerから認証結果を取得する。 /// </summary> /// <param name="txid"></param> /// <returns></returns> /// <summary> [HttpGet, Route("v2/auth/brw/result")] public Message authResult(String txid) { return authServiceV2.getBRWResult(txid); } //AuthServiceV2.cs public Message getBRWResult(String serverTransId) { //ActiveServer url for Retrieve Results string resultUrl = Config.AsAuthUrl + "/api/v2/auth/brw/result?threeDSServerTransID=" + serverTransId; //Get authentication result from ActiveServer Message result = (Message)RestClientHelper.GetForObject(resultUrl, typeof(Message)); return result; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php //AuthControllerV2.php public function brwResult() { $serverTransId = $_GET["txid"]; $response = $this->authService->getBrwResult($serverTransId); //Show authentication results on result.html (Step. 17(F), Step. 28(C) or Step. 21(D), 22(D)). Utils::_returnJson($response); } //AuthServiceV2.php public function getBrwResult($serverTransId) { //ActiveServer url for Retrieve Results $resultUrl = "/api/v2/auth/brw/result?threeDSServerTransID=" . $serverTransId; //Get authentication result from ActiveServer (Step 16(F), Step 27(C) or Step 20(D)) return $this->restTemplate->get($resultUrl)->getBody(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //api-v2.go r.GET("/v2/auth/brw/result", func(c *gin.Context) { transId := getSessionAttribute(c, InitAuthResponse, ThreeDSServerTransId).(string) if transId == "" { c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid transId"}) return } callASAPI(asSession{ url: "/api/v2/auth/brw/result?threeDSServerTransID=" + transId, context: c, httpClient: httpClient, config: config}) }) ... } |
成功
この文書ではバックエンド実装についての説明は以上で終了です。認証が完了した後、チェックアウト処理は取引ステータス、ECIとCAVVを使用して承認の実行を続け、取引を完了出来ます。
次のチャプター
次へボタンを選択しGPayments 3DSリクエスターの為のサンプルコードのすべての機能の説明をご覧ください。