跳转链接集成最佳实践
调用 支付 接口后,针对不同支付方式,支付 接口的响应中会返回不同类型的跳转 URL 用于支付流程的推进。本文为您提供不同端类型对应的跳转 URL 使用最佳实践。关于各个支付方式的 URL 实际返回情况,请参考 部分支付方式的URL返回类型汇总。
跳转 URL 参数
针对不同的商户应用端类型(Web, WAP, App),不同支付方式在 支付 接口的响应中会返回以下三种跳转 URL 中的部分或全部,用于支付推进:
链接类型 | 描述 |
normalUrl | HTTPS 地址的 URL,用于同一个浏览器页面跳转至支付方式页面。 |
applinkUrl | 用于推进支付流程的 Android App Links 或 iOS Universal Links。 |
schemeUrl | 用于打开支付方式 app 的 scheme URL。 示例:boostapp://inAppDeeplink?deeplink_path=deeplink/onetimepayment&source=alipay-connect&codeValue=https%3A%2F%2Fglobal.alipay.com%2F2810020400931LFH2t2mq1jjJ8zSetyv31VU |
【注意】
Web 端
在 Web 端场景下,支付 接口的响应中会返回 normalUrl。
URL 打开方式
获取 normalUrl 字段的值后,需要将页面重定向至该支付链接或新页面打开该支付链接。建议您新建一个页面打开该支付链接引导买家完成支付,新建页面代码样例如下:
if (serverResponse.normalUrl != null) {
window.open(serverResponse.normalUrl, '_blank');
}
用户体验
以 normalUrl 对应的页面是扫码支付或账密登录界面为例,下图展示了 Web 端支付流程的示意图:
扫码支付 | |
账密登录支付 |
WAP 端
在 WAP 端场景下,支付 接口的响应中,不同支付方式可能会返回以下三种 URL 中的部分或全部:
- normalUrl
- applinkUrl
- schemeUrl
URL 的使用策略
如果您收到的 支付 接口响应中只返回了三种 URL 中的一种,则直接使用您收到的 URL 作为跳转链接。 如果您收到以上三种 URL 中的多个, 建议按照以下策略使用对应的 URL:
- iOS 系统:优先使用 applinkUrl,次优使用 schemeUrl,最后选择使用 normalUrl。
- 安卓系统:优先使用 schemeUrl,次优使用 applinkurl,最后选择使用 normalUrl。
URL 打开方式
使用 JavaScript 的页面跳转方法。可以使用如下代码打开 URL:
window.location.href = URL;
用户体验
链接类型 | 支付体验 |
normalUrl | 买家从商户 WAP 页面跳转至支付方式 WAP 页。支付方式 WAP 页面对买家所呈现的内容会因支付方式不同而不同,分为以下几种类型:
|
applinkUrl | 根据买家是否安装支付方式 app,自动决定后续支付流程如何推进:
|
schemeUrl | 支付体验取决于买家是否安装了支付方式 app:
|
App 端
在 App 端场景下,支付 接口的响应中,不同支付方式可能会返回以下三种 URL 中的部分或全部:
- normalUrl
- applinkUrl
- schemeUrl
【注意】在 App 端,除了上述用于跳转的 URL 参数,支付 接口的响应中还会返回 appIdentifier 参数,此参数是支付方式 app 对应的包名(package name)。在 app 支付中用于判断支付方式 app 是否安装,还可以用于避免在安卓系统中出现消除歧义框。
URL 的使用策略
如果您收到的 支付 接口响应中,只返回了三种 URL 中的一种,则直接使用您收到的 URL 作为跳转链接。如果您收到以上三种 URL 中的多个, 建议您按照以下顺序使用对应的 URL:
- iOS 系统:优先使用 applinkUrl,次优使用 schemeUrl,最后选择使用 normalUrl。
- 安卓系统:优先使用 schemeUrl,次优使用 applinkurl,最后选择使用 normalUrl。
URL 打开方式
在 iOS 及 Android 系统中,可以通过以下方式打开跳转 URL :
iOS | Android |
|
|
打开方式优缺点对比
每种方式均有其优势,建议您根据自己的系统情况及集成场景选择适合的方案:
打开方式 | 说明 | |
应用外跳转:调用系统方法打开跳转 URL | Alipay推荐使用此方案。
| |
应用内打开跳转 URL |
| WKWebView/WebView 是一个内置的控件,可以在应用程序中嵌入一个 Web 浏览器。你可以使用它来加载网站的链接。
|
| 属于基于 WKWebView/WebView 的升级方案,使用 JSBridge/JavascriptInterface 后,WebView 可以直接调用系统方法打开跳转链接。
| |
| 是 iOS/Android 官方提供的应用内打开链接的方式,与 WebView 相比,能力更全面。
| |
判断是否安装支付方式 app 后, 分别采取应用外跳转或应用内打开跳转 URL。 | 根据买家是否安装支付方式 app,分别提供不同的跳转链接打开方式。
|
代码示例(iOS)
方法1: 调用系统方法打开跳转 URL
以下代码示例是通过系统打开 URL 的方式,在 iOS 系统中实现从商户 app 跳转至支付方式 app:
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url] options:@{} completionHandler:nil];
}else{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url]];
}
方法2:应用内打开跳转 URL
当您的业务有客户端,并倾向于在支付流程中使用应用内打开网页的方式进行下单处理,则需要适配所有支付方式。此方案适用于使用以下 URL 类型:
- normalUrl
- applinkUrl
【注意】部分支付方式不支持 WAP 页面支付,访问 normalUrl 时会经过重定向并触发 scheme 的跳转来唤醒对应的支付方式 app。WKWebView 会拦截到 scheme 外跳应用,您需要操作 WKWebView 的 decidePolicyForNavigationAction 方法并调用系统方法打开跳转链接,即可实现商户 WKWebView 跳转到支付方式 app 支付。
使用 WKWebView
以下代码示例使用 WKWebView 在 iOS 的应用内加载支付方式页面:
// 初始化 webview 配置
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc]init];
// 初始化 webView
WKWebView *webView = [[WKWebView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height) configuration:configuration];
webView.navigationDelegate = self;
//加载支付方式 URL
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:Url]]];
[self.view addSubview:self.webView];
以下代码示例使用 WKWebView 拦截到 scheme 外跳应用,操作 WKWebView 的 decidePolicyForNavigationAction 方法即可实现商户 WebView 跳转到支付方式 app 支付:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
// WKWebView 默认拦截 scheme 需用下面方法处理
// 打开外部应用 Safari 等操作
NSURL *url = navigationAction.request.URL;
NSString *absoluteString = url.absoluteString;
NSString *scheme = url.baseURL.scheme;
DLog(@"navigationAction.request.URL.absoluteString=====> %@", absoluteString);
if ([absoluteString isEqualToString:@"about:blank"]) {
decisionHandler(WKNavigationActionPolicyCancel);
}
if (!([absoluteString containsString:@"https://"] || [absoluteString containsString:@"http://"])) {
if ([absoluteString containsString:@"://"]) {
NSString *urlHeader = [absoluteString componentsSeparatedByString:@"://"][0];
[MBProgressHUD showAutoMessage:[NSString stringWithFormat:@"Scheme\n%@://",urlHeader] toView:self.view];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self openAppWithUrlStr:navigationAction.request.URL];
decisionHandler(WKNavigationActionPolicyAllow);
});
}
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
使用 WKWebView 及 JSBridge
使用该方法会更加优化买家支付的体验,同时需要较强的开发能力,在通过 WKWebView 打开 URL 的基础上结合使用 JSBridge,有利于项目后续的维护及可扩展性,同时又能提高性能和安全性。使用 JSBridge可以帮助网页和原生应用之间的交互更加方便,实现 WAP 页面可以直接调用 app 的 native 方法。以下代码示例展示了在 WKWebView 中使用 JSBridge 的方法:
[self.wkWebView evaluateJavaScript:bridge completionHandler:nil];
使用 SFSafariViewController
SFSafariViewController 支持 normalUrl, applinkUrl, 及 schemeUrl 的打开及渲染。与 WebView 相比,能力更全面,集成方式较为简单。使用 SFSafariViewController 打开链接的实现方式可参考 iOS 官方提供的 SFSafariViewController 使用文档。以下代码示例展示了使用 SFSafariViewController 的方法:
SFSafariViewController *safariVC = [[SFSafariViewController alloc] initWithURL:Url entersReaderIfAvailable:NO];
safariVC.delegate = self;
[self.navigationController presentViewController:safariVC animated:YES completion:nil];
方法3:判断是否安装支付方式 app 并分别处理
首先需要判断买家是否安装了支付方式 app ,再针对不同情况使用不同的 UR L打开方式进行处理。在 iOS 系统下,需要完成以下步骤:
- 在 info.plist 配置中添加 LSApplicationQueriesSchemes 。需注意 info.plist 中对 scheme 有个数限制。
- 在代码中通过 canOpenURL 方法判断是否安装了支付方式 app。代码实现请参考以下示例:
NSString *walletSchemeUrl = @"gcash://";
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:walletSchemeUrl]]){
// 支付方式应用已安装,建议通过原生方式打开
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url] options:@{} completionHandler:nil];
return YES;
} else {
// 支付方式应用未安装,建议通过 WEBVIEW 加载 H5 页面
SFSafariViewController *safariVC = [[SFSafariViewController alloc] initWithURL:Url entersReaderIfAvailable:NO];
safariVC.delegate = self;
[self.navigationController presentViewController:safariVC animated:YES completion:nil];
return NO;
}
代码示例(Android)
方法1: 调用系统方法打开跳转 URL
以下代码示例是通过系统打开 URL 的方式,在 Android 系统中实现从商户 app 跳转至支付方式 app:
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Url));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// use the startActivity function to redirect to the wallet app
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
使用此方法可能会出现消除歧义对话框。
什么是消歧对话框?
Android App Link 在安装应用时向谷歌服务器请求身份认证。 如果身份验证失败,则 Android 应用程序链接将失效,并且当买家尝试通过此类链接打开应用程序时会出现消歧对话框。买家需要手动选择如何打开链接。
此图显示了当买家点击 Android 应用程序链接时出现的消歧对话框示例。买家需要手动选择打开链接的方式:使用谷歌地图或谷歌浏览器。 |
如何防止消歧对话框出现?
当出现消歧对话框时,买家需要手动选择应用程序的打开方式,影响支付成功率。 因此,您需要防止出现消歧对话框,请执行以下操作之一:
- 当买家安装了支付方式 app 时,指定支付方式的 packageName,打开支付方式的 URL。
- 当买家没有安装支付方式 app 时,使用下面的示例代码实现离开商户 app, 并打开跳转 URL:
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(applinkUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//设置支付方式的包名
intent.setPackage(walletPackageName);
PackageManager packageManager = MainActivity.this.getPackageManager();
//查询是否有支持打开该链接的应用。
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
if (activities.size() <= 0) {
//支付方式 app 未安装。请重新初始化 Intent,该链接会由系统默认支持的方式打开。
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(applinkUrl));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} else {
//支付方式 app 已安装。通过指定支付方式报名打开支付方式 URL,消除歧义对话框不会出现。
startActivity(intent);
}
【注意】 以下列表展示了部分支付方式的 packageName 值:
- AlipayHK: hk.alipay.wallet
- GCash: com.globe.gcash.android
- TrueMoney: th.co.truemoney.wallet
- KakaoPay: com.kakao.talk
- Touch 'n Go: my.com.tngdigital.ewallet
- Dana: id.dana
方法2:应用内打开跳转 URL
当您的业务有客户端,并倾向于在支付流程中使用应用内打开网页的方式进行下单处理,则需要适配所有支付方式,可以使用 WebView 容器加载 normalUrl 或 applinkUrl:
- normalUrl: normalUrl 对应的链接为 WAP 页面,直接在 WebView 中打开 WAP 页面,用于完成支付。
- applinkUrl: 对应的页面为 WAP 页面,直接在 WebView 中打开 WAP 页面,用于完成支付。
【注意】部分支付方式不支持 WAP 页面支付, 访问 normalUrl 时会经过重定向并触发 scheme 的跳转来唤醒对应的支付方式 app 。WebView 会拦截到 scheme 外跳应用,您需要操作 WebView 的shouldOverrideUrlLoading 方法并调用系统方法打开跳转链接,即可实现商户 WebView 跳转到支付方式 app 支付。
使用 WebView
以下代码示例使用 WebView 在 Android 的应用内加载支付方式页面:
WebView webView = findViewById(R.id.webview);
//设置 webview 客户端
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
view.loadUrl(request.getUrl().toString());
return super.shouldOverrideUrlLoading(view, request);
}});
WebSettings webSettings = webView.getSettings();
//启用 javascript
webSettings.setJavaScriptEnabled(true);
//启用 scaling
webSettings.setSupportZoom(true);
//启用 scaling controls (buttons)
webSettings.setBuiltInZoomControls(true);
//Webview 有两种缓存模式,这里不使用缓存加载
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
//允许 JavaScript 打开新窗口 (默认为 false)
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
//允许 JavaScript 加在本地缓存
webSettings.setDomStorageEnabled(true);
//WAP 缓存大小 (无需手动设置)
//webSettings.setAppCacheMaxSize(1024 * 1024 * 8);
//WAP 缓存路径
String absolutePath = getApplicationContext().getCacheDir().getAbsolutePath();
//WAP 缓存大小
webSettings.setAppCachePath(absolutePath);
//是否允许 WebView 访问文件 (默认为 true)
webSettings.setAllowFileAccess(true);
//允许保存 WAP 缓存
webSettings.setAppCacheEnabled(true);
//使用概览模式时,如果页面宽度超过WebView显示,缩放页面以适应WebView(默认为false)
webSettings.setLoadWithOverviewMode(true);
//支持视口 HTML 元标签
webSettings.setUseWideViewPort(true);
//加在支付方式支付 URL
webView.loadUrl(Url);
以下代码示例使用 WebView 拦截到 scheme 外跳应用,通过对 WebView 设置 WebViewClient,使用WebViewClient.shouldOverrideUrlLoading() 方法对 URL 进行拦截,拦截后判断其格式,然后 native 解析到对应的方法和数据后执行本地方法:
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if (request.getUrl().toString().startsWith("http")) {
view.loadUrl(request.getUrl().toString());
return super.shouldOverrideUrlLoading(view, request);
} else {
view.onPause();
view.stopLoading();
try {
Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
} catch (URISyntaxException e) {
//提示买家未安装支付方式 app
}
return false;
}
}
【注意】如果您的应用有跳转外部 app 白名单,需注意白名单的处理与配置,考虑旧版兼容性问题并减少应用的发版次数。
使用 WebView 及 JavascriptInterface
如果您研发能力强,且非常重视买家的支付体验,建议在 WevView 中使用 JavascriptInterface。使用JavaScriptInterface 可以帮助 Android 应用程序和 WebView 之间进行双向通信,使得 WAP 页面可以直接调用 app 的 native 方法,让您的应用程序更加灵活和强大,有利于项目后续的维护及扩展。以下代码示例展示了在 WebView 中使用 JavascriptInterface 的方法:
- 在 WebView 里声明一个 JavascriptInterface
@JavascriptInterface
public void openActivity(String url) {
//根据实际情况添加到代码中
try {
Intent intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} catch (Exception e) {
Toast.makeText(context, "App not installed", Toast.LENGTH_SHORT).show();
}
}
- 给 WebView 设置 JavascriptInterface
mWebView.addJavascriptInterface(this, "bridge");
- 使用以下代码对跳转 URL 进行打开
window.bridge.openActivity("Url");
使用 Custom Tabs
Custom Tabs 支持 normalUrl, applinkUrl, 及 schemeUrl 的打开及渲染。与 WebView 相比,能力更全面,集成方式较为简单。以下代码示例展示了使用 Custom Tabs 的方法:
- Gradle 项目文件中引入 Custom Tabs
dependencies {
...
implementation "androidx.browser:browser:1.4.0"
}
- 使用 Custom Tabs 在应用中打开一个 Chrome Tab
// 使用 CustomTabsIntent.Builder 配置 CustomTabsIntent.
// 准备就绪后,调用 CustomTabsIntent.Builder.build() 来创建 CustomTabsIntent
// 使用 CustomTabsIntent.launchUrl() 启动所需的 URL
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(this, Uri.parse(url));
更多关于如何使用 CustomTabs 打开链接的信息,请参考 Android 官方提供的 Custom Tabs 使用指南。
方法3:判断是否安装支付方式 app 并分别处理
首先需要判断买家是否安装了支付方式 app ,再针对不同情况使用不同的 URL 打开方式进行处理。
在 Android 系统下,此方案的实现需要完成以下步骤:
- 出于 Android 系统的隐私和安全政策,需要在 AndroidManifest.xml 中 添加 <queries> 标签,以确保支付链接能打开对应的支付方式 app。<queries> 标签中需要配置支付方式 app 的包名(package name),对应的值可以从 Alipay 的 支付 接口返回中通过 appIdentifier 参数获取,或者联系 Alipay 技术支持。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.llw.scandemo">
...
<queries>
<package android:name="th.co.truemoney.wallet" />
<package android:name="com.eg.android.AlipayGphone" />
<package android:name="my.com.tngdigital.ewallet" />
...
</queries>
...
</manifest>
- 通过以下代码判断是否安装支付方式 app 并拉起支付:
String Url = "applinkUrl/schemeUrl";
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Url));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setPackage("my.com.tngdigital.ewallet");
// 通过以下两行代码判断是否安装支付方式 app
PackageManager packageManager = MainActivity.this.getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
if (activities.size() <= 0) {
// 支付方式 app 未安装。建议在 WEBVIEW 中打开 H5 链接
intent = new Intent(MainActivity.this, WebviewActivity.class);
intent.setData(Uri.parse(Url));
startActivity(intent);
} else {
// 支付方式 app 已安装。通过原生方式唤起交易。
startActivity(intent);
}
} catch (Exception e) {
// 处理报错
e.printStackTrace();
}
用户体验
链接的不同打开方式下所对应的的支付跳转流程如下:
打开方式 | 支付跳转流程 | |
应用外跳转:调用系统方法打开跳转 URL | 应用外跳转。支付体验取决于买家是否安装了支付方式 app:
| |
应用内打开跳转URL | 有应用外跳转及应用内跳转两种支付体验:
| |
最佳实践
建议您根据不同支付方式做打开跳转 URL 的固定配置,或根据支付方式、设备环境和返回内容进行路由。下图展示了根据不同支付方式做固定配置的示例:
针对每一种支付方式,您在决策使用什么方式打开跳转 URL 时,建议按照以下方式进行判断: