质疑显示
如果身份验证响应 outcome
被 challenged
,并且 authentication.version
是 2.x.x
(3DS2),则您可以使用 SDK 来为移动设备提供经改进的
如果 authentication.version
是 1.x.x
(3DS1),则您必须遵循
3DS2 质疑显示
如果 authentication.version
是 2.x.x
,则您需要从身份验证响应中获得以下值,以便用在 SDK 中。
访问名称 | 值来源 | Cardinal SDK 名称 |
---|---|---|
challenge.reference | 身份验证响应 | transactionId |
challenge.payload | 身份验证响应 | payload |
Access 3DS API 会针对最新的 Cardinal SDK 定期进行测试。当前已测试的 Cardinal SDK 版本: v2.2.5
SDK 质疑显示:
自定义质疑界面
作为 SDK 设置的一部分,您可以自定义质疑用户界面
3DS1 质疑显示
如果 authentication.version
是 1.x.x
,则使用以下步骤来显示质疑屏幕。
注释:早在智能手机问世之前,发卡机构就开始实施 3DS 版本 1,但与版本 2 相比,它的体验较差。我们预计在 2021 年期间来自发卡机构的版本 1 流量将出现下降。
质疑表格 (webView)
POST 请求至 challenge.url
(通过 challenge.jwt
和可选 MD
)。
MD
字段让您能够在响应 url (challenge.returnUrl
) 中包含/重复的质疑表格中传递 url 参数(最多 1024 个字符)。
在您的故事板和 UIViewController 中添加 WKWebView,然后启用 JavaScript
@IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.configuration.preferences.javaScriptEnabled = true
}
@IBOutlet var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() webView.configuration.preferences.javaScriptEnabled = true }
在您的 UIViewController 中实施与以下代码片段类似的方法
let iframeSrcDoc = """
<html>
<body onload='document.frmLaunch.submit();'>
<form name='frmLaunch' method='POST' action='\(challengeUrl)'>
<input type='hidden' name='JWT' value='\(jwt)'>
<input type='hidden' name='MD' value='\(md)'>
</form>
</body>
</html>
"""
// A viewport meta tag is used to scale the content nicely to the device's screen size
let html = """
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<iframe srcdoc="\(iframeSrcDoc)" width="100%" height="400" frameborder="0">
</iframe>
</body>
</html>
"""
webView.loadHTMLString(html, baseURL: URL(string: "about:srcdoc")!)
let iframeSrcDoc = """ <html> <body onload='document.frmLaunch.submit();'> <form name='frmLaunch' method='POST' action='\(challengeUrl)'> <input type='hidden' name='JWT' value='\(jwt)'> <input type='hidden' name='MD' value='\(md)'> </form> </body> </html> """ // A viewport meta tag is used to scale the content nicely to the device's screen size let html = """ <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <iframe srcdoc="\(iframeSrcDoc)" width="100%" height="400" frameborder="0"> </iframe> </body> </html> """ webView.loadHTMLString(html, baseURL: URL(string: "about:srcdoc")!)
在您的片段中添加 WebView 并启用 JavaScript
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val webView = view.findViewById<WebView>(...)
webView.settings.javaScriptEnabled = true
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val webView = view.findViewById<WebView>(...) webView.settings.javaScriptEnabled = true }
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
WebView webView = view.findViewById(...);
webView.getSettings().setJavaScriptEnabled(true);
}
@Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); WebView webView = view.findViewById(...); webView.getSettings().setJavaScriptEnabled(true); }
在您的片段中实施与以下代码片段类似的方法
val iframeSrcDoc = """
<html>
<body onload='document.frmLaunch.submit();'>
<form name='frmLaunch' method='POST' action='$challengeUrl'>
<input type='hidden' name='JWT' value='$jwt'>
<input type='hidden' name='MD' value='$md'>
</form>
</body>
</html>
"""
// A viewport meta tag is used to scale the content nicely to the device's screen size
val html = """
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<iframe srcdoc="$iframeSrcDoc" width="100%" height="400" frameborder="0">
</iframe>
</body>
</html>
"""
webView.loadData(html, "text/html; charset=utf-8", "UTF-8")
val iframeSrcDoc = """ <html> <body onload='document.frmLaunch.submit();'> <form name='frmLaunch' method='POST' action='$challengeUrl'> <input type='hidden' name='JWT' value='$jwt'> <input type='hidden' name='MD' value='$md'> </form> </body> </html> """ // A viewport meta tag is used to scale the content nicely to the device's screen size val html = """ <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <iframe srcdoc="$iframeSrcDoc" width="100%" height="400" frameborder="0"> </iframe> </body> </html> """ webView.loadData(html, "text/html; charset=utf-8", "UTF-8")
String challengeUrl = "add challengeUrl here";
String iframeSrcDoc = "<html>" +
"<body onload='document.frmLaunch.submit();'>" +
"<form name='frmLaunch' method='POST' action='" + challengeUrl + "'>" +
"<input type='hidden' name='JWT' value='" + jwt + "'>" +
"<input type='hidden' name='MD' value='" + md + "'>" +
"</form>" +
"</body>" +
"</html>";
// A viewport meta tag is used to scale the content nicely to the device's screen size
String html = "<html>" +
"<head>" +
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" +
"</head>" +
"<iframe srcdoc=\"" + iframeSrcDoc + "\" width=\"100%\" height=\"400\" frameborder=\"0\">" +
"</iframe>" +
"</body>" +
"</html>";
webView.loadData(html, "text/html; charset=utf-8", "UTF-8");
String challengeUrl = "add challengeUrl here"; String iframeSrcDoc = "<html>" + "<body onload='document.frmLaunch.submit();'>" + "<form name='frmLaunch' method='POST' action='" + challengeUrl + "'>" + "<input type='hidden' name='JWT' value='" + jwt + "'>" + "<input type='hidden' name='MD' value='" + md + "'>" + "</form>" + "</body>" + "</html>"; // A viewport meta tag is used to scale the content nicely to the device's screen size String html = "<html>" + "<head>" + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" + "</head>" + "<iframe srcdoc=\"" + iframeSrcDoc + "\" width=\"100%\" height=\"400\" frameborder=\"0\">" + "</iframe>" + "</body>" + "</html>"; webView.loadData(html, "text/html; charset=utf-8", "UTF-8");
拦截质疑返回
拦截对此 url 的 POST 请求: challenge.returnUrl
让您的 UIViewController 采用 WKNavigationDelegate 协议
在调用 webView.loadHTMLString 之前,将您的 WKWebView 的 navigationDelegate 属性指向您的 UIViewController
webView.navigationDelegate = self
webView.navigationDelegate = self
在 UIViewController 中实施以下方法,以便在质疑完成时收到通知
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if navigationAction.request.url!.absoluteString == "<value of your challenge return url>" {
// TODO: add your logic
}
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { if navigationAction.request.url!.absoluteString == "<value of your challenge return url>" { // TODO: add your logic } decisionHandler(.allow) }
拦截对您的质疑返回 url 的 POST 请求
创建一个可扩展 WebViewClient 的类并覆盖 onLoadResource 方法,以便在质疑完成时收到通知
private class CustomWebViewClient: WebViewClient() {
override fun onLoadResource(view: WebView?, url: String?) {
if (url.toString() == "<value of your challenge return url>") {
// TODO: add your logic
}
super.onLoadResource(view, url)
}
}
private class CustomWebViewClient: WebViewClient() { override fun onLoadResource(view: WebView?, url: String?) { if (url.toString() == "<value of your challenge return url>") { // TODO: add your logic } super.onLoadResource(view, url) } }
private static class CustomWebViewClient extends WebViewClient {
@Override
public void onLoadResource(WebView view, String url) {
if (url.equals("<value of your challenge return url>")) {
// TODO: add your logic
}
super.onLoadResource(view, url);
}
}
private static class CustomWebViewClient extends WebViewClient { @Override public void onLoadResource(WebView view, String url) { if (url.equals("<value of your challenge return url>")) { // TODO: add your logic } super.onLoadResource(view, url); } }
在调用 webView.loadData() 之前将您的 WebView 的 webViewClient 属性设为该类的一个实例
webView.webViewClient = CustomWebViewClient()
webView.webViewClient = CustomWebViewClient()
webView.setWebViewClient(new CustomWebViewClient());
webView.setWebViewClient(new CustomWebViewClient());
验证
一旦填写了质疑,您就必须发出一个验证请求来验证质疑的结果。
重要信息:您只应从后端系统请求验证 API。您不得使用 Access 凭证直接从移动应用程序中调用。
POST 您的验证请求至在您的身份验证响应中收到的 3ds:verify
操作链接。
验证示例请求
注释:对于 Android/iOS SDK,您必须使用 v3
版的 API
POST https://try.access.worldpay.com/verifications/customers/3ds/verification
验证请求正文:
{
"transactionReference": "Memory265-13/08/1876",
"merchant": {
"entity": "default"
},
"challenge": {
"reference": "123456789"
}
}
验证响应
最佳实践:Access Worldpay 在服务响应的头文件中返回WP-CorrelationId
。我们强烈建议您将此记录下来。我们使用WP-CorrelationId
检查单个服务请求。
以下是您会收到的验证响应的示例。
{
"outcome": "authenticated",
"transactionReference": "Memory265-13/08/1876",
"authentication": {
"version": "2.1.0",
"authenticationValue": "MAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"eci": "05",
"transactionId": "c5b808e7-1de1-4069"
}
}
{
"outcome": "authenticationFailed",
"transactionReference": "Memory265-13/08/1876",
"authentication": {
"version": "1.0.2",
"eci": "00",
"transactionId": "N+en2I5+ZK/kQqk69wXdI8XIPg8="
},
"_links": {
"3ds:authenticate": {
"href": "https://try.access.worldpay.com/verifications/customers/3ds/authentication"
},
"curies": [{
"href": "https://try.access.worldpay.com/rels/verifications/customers/3ds/{rel}",
"templated": true,
"name": "3ds"
}]
}
}
{
"outcome": "signatureFailed",
"transactionReference": "Memory265-13/08/1876",
"authentication": {
"version": "1.0.2",
"eci": "02"
},
"_links": {
"3ds:authenticate": {
"href": "https://try.access.worldpay.com/verifications/customers/3ds/authentication"
},
"curies": [{
"href": "https://try.access.worldpay.com/rels/verifications/customers/3ds/{rel}",
"templated": true,
"name": "3ds"
}]
}
}
{
"outcome": "unavailable",
"transactionReference": "Memory265-13/08/1876",
"_links": {
"3ds:authenticate": {
"href": "https://try.access.worldpay.com/verifications/customers/3ds/authentication"
},
"3ds:verify": {
"href": "https://try.access.worldpay.com/verifications/customers/3ds/verification"
},
"curies": [{
"href": "https://try.access.worldpay.com/rels/verifications/customers/3ds/{rel}",
"templated": true,
"name": "3ds"
}]
}
}
使用值:version
、authenticationValue
、eci
、transactionId
,它们都源自
参数 | 描述 |
---|---|
authentication.version | 用于处理该交易的 3DS 版本。 注释:对于授权中的 Mastercard 身份校验交易是必需的。 |
authentication.authenticationValue | 提供 3DS 验证结果证据的密码值。
在 |
authentication.eci | 电子商务指标 (ECI)。 表示 3DS 身份验证的结果。
您会在 |
authentication.transactionId | 交易识别码。 如果提供,则您应将它用作 如果 authentication.version 具有以下主要版本:
|
后续步骤