项目开发遇到web页调起支付的功能,参考了网上很多资料,在此做个笔记,便于记忆,同时也希望帮助到遇到相同问题的小伙伴。
参考链接:
https://www.jianshu.com/p/8ac06ffd090f
https://www.jianshu.com/p/28483a16c4d5
https://www.jianshu.com/p/66d2c0ff51c6
微信支付
问题:web页点击支付跳转微信支付页后点击左上角的取消按钮或完成支付之后,微信APP跳转的是Safari浏览器,而不是跳转回原APP。
解决方案:
1.设置自己项目中Schemes
Info->URL Types
格式为:www.xxxx.com,注意:www.xxxx.com 此域名是H5授权的域名,如果是二级域名,可以写成:aaa.xxxx.com(aaa随便写)。可以直接问申请微信支付的人要。
2.修改redirect_url
目的是保证redirect_url这个回调链接能跟设置的URL Schemes一致。
拦截后我们需要首先关注在原始地址中是否含有redirect_url=字符串,如果含有该字符串则说明你们后台人员是利用该字符串在微信支付完成后跳转到支付完成的界面.而我们也需要利用该字段以实现支付完成后跳转回我们的APP.
- 如果包含redirect_url=字段,我们需要先记住后台重定向的地址,然后将其替换成我们配置好的URL schemes以实现跳转回我们的APP.然后在跳转回我们APP之后我们会手动再加载一次原先重定向的URL地址。
- 如果不包含redirect_url=字段,我们只需要添加该字段到原始URL最后面即可
3.设置webView请求header中的Referer字段
微信支付结束后默认回调Referer字段中地址。
Referer设置为www.xxxx.com://,这样的格式。
4.添加WKNavigationDelegate代理,实现重定向代理方法
注意
- 我们需要判断微信地址的同时判断“redirect_url=www.xxxx.com://”,因为我们在第一次检测到后会重新加载该地址,该地址中也含有 https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb,会形成循环冲突,所以判断条件如下
- 如我们项目中有支付完成的重定向地址,我们可以在跳转前即加载该地址,否则我们在回到APP后会有明显的加载过程,界面不友好
- 一般跳转到其他APP时,地址不是以http或https开头,所以我们在最后的判断中以此为依据,但注意如果服务器端加载错误的地址可能会走入此逻辑,所以必须对特定的scheme进行处理
支付宝支付
问题:web页点击支付跳转支付宝支付页后点击左上角的取消按钮或完成支付之后,停留在支付宝,而不是跳转回原APP。
解决方案:
1.添加 URL Scheme
2.实现代理方法拦截链接并跳转支付宝
WKNavigationDelegate代码
#define XDX_URL_TIMEOUT 30
static const NSString *CompanyFirstDomainByWeChatRegister = @"www.xxxx.com";
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURLRequest *request = navigationAction.request;
NSString *scheme = [request.URL scheme];
// decode for all URL to avoid url contains some special character so that it wasn't load.
NSString *absoluteString = [navigationAction.request.URL.absoluteString stringByRemovingPercentEncoding];
NSLog(@"Current URL is %@",absoluteString);
static NSString *endPayRedirectURL = nil;
// 解决跳转到本地支付宝App不返回的问题
if ([absoluteString hasPrefix:@"alipays://"] || [absoluteString hasPrefix:@"alipay://"])
{
NSURL *openedURL = navigationAction.request.URL;
NSString *prefixString = @"alipay://alipayclient/?";
//替换里面的默认Scheme为自己的Scheme
NSString *urlString = [[self xh_URLDecodedString:absoluteString] stringByReplacingOccurrencesOfString:@"alipays" withString:[NSString stringWithFormat:@"%@",CompanyFirstDomainByWeChatRegister]];
if ([urlString hasPrefix:prefixString]) {
NSRange rang = [urlString rangeOfString:prefixString];
NSString *subString = [urlString substringFromIndex:rang.length];
NSString *encodedString = [prefixString stringByAppendingString:[self xh_URLEncodedString:subString]];
openedURL = [NSURL URLWithString:encodedString];
}
BOOL isSucc = [[UIApplication sharedApplication] openURL:openedURL];
if (!isSucc) {
NSLog(@"未安装某宝客户端");
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
//解决微信支付后为返回当前应用的问题
// Wechat Pay, Note : modify redirect_url to resolve we couldn't return our app from wechat client.
if ([absoluteString hasPrefix:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb"] && ![absoluteString hasSuffix:[NSString stringWithFormat:@"redirect_url=%@://",CompanyFirstDomainByWeChatRegister]]) {
decisionHandler(WKNavigationActionPolicyCancel);
// 1. If the url contain "redirect_url" : We need to remember it to use our scheme replace it.
// 2. If the url not contain "redirect_url" , We should add it so that we will could jump to our app.
// Note : 2. if the redirect_url is not last string, you should use correct strategy, because the redirect_url's value may contain some "&" special character so that my cut method may be incorrect.
NSString *redirectUrl = nil;
if ([absoluteString containsString:@"redirect_url="]) {
NSRange redirectRange = [absoluteString rangeOfString:@"redirect_url"];
endPayRedirectURL = [absoluteString substringFromIndex:redirectRange.location+redirectRange.length+1];
redirectUrl = [[absoluteString substringToIndex:redirectRange.location] stringByAppendingString:[NSString stringWithFormat:@"redirect_url=%@://",CompanyFirstDomainByWeChatRegister]];
}else {
redirectUrl = [absoluteString stringByAppendingString:[NSString stringWithFormat:@"&redirect_url=%@://",CompanyFirstDomainByWeChatRegister]];
}
NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:redirectUrl] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:XDX_URL_TIMEOUT];
newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields;
[newRequest setValue:[NSString stringWithFormat:@"%@",CompanyFirstDomainByWeChatRegister] forHTTPHeaderField:@"Referer"];
newRequest.URL = [NSURL URLWithString:redirectUrl];
[webView loadRequest:newRequest];
return;
}
// Judge is whether to jump to other app.
if (![scheme isEqualToString:@"https"] && ![scheme isEqualToString:@"http"]) {
decisionHandler(WKNavigationActionPolicyCancel);
if ([scheme isEqualToString:@"weixin"]) {
// The var endPayRedirectURL was our saved origin url's redirect address. We need to load it when we return from wechat client.
if (endPayRedirectURL) {
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:endPayRedirectURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:XDX_URL_TIMEOUT]];
}
}else if ([scheme isEqualToString:[NSString stringWithFormat:@"%@",CompanyFirstDomainByWeChatRegister]]) {
}
// BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:request.URL];
// if (canOpen) {
// [[UIApplication sharedApplication] openURL:request.URL];
// }
if ([navigationAction.request.URL.absoluteString hasPrefix:@"weixin://"]) {
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
}
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
- (NSString *)xh_URLDecodedString:(NSString *)urlString {
NSString *string = urlString;
NSString *decodedString=(__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, (__bridge CFStringRef)string, CFSTR(""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
return decodedString;
}
- (NSString *)xh_URLEncodedString:(NSString *)urlString {
NSString *string = urlString;
NSString *encodedString = (NSString *) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)string,
NULL,
(CFStringRef)@"!*'();:@&=+$,/?%#[]",
kCFStringEncodingUTF8));
return encodedString;
}
跳转返回通知
跳转回原APP后,在Appdelegate.m文件中会有回调方法。在方法里,可以写一些其他的逻辑代码。
// 仅支持iOS9以上系统,iOS8及以下系统不会回调
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
//6.3的新的API调用,是为了兼容国外平台的调用[如果用6.2的api调用会没有回调],对国内平台没有影响
BOOL result = [[UMSocialManager defaultManager] handleOpenURL:url options:options];
if (!result) {
// 其他如支付等SDK的回调
// H5微信支付的回调
if ([url.scheme isEqualToString:@"www.xxxx.com"]) {
// 发送通知加载页面
[[NSNotificationCenter defaultCenter] postNotificationName:kWeChatComeBackKey object:nil userInfo:@{@"url" : url.absoluteString}];
}
}
return result;
}