标题太混乱了,没办法
本文将涵盖两个方面:
H5:支付时打开微信或支付宝App;打开微信或支付宝App并完成支付操作后,返回您原App。
公司业务需求,需要客户嵌入一个完整的H5开发的网页,里面包含H5微信支付和支付宝支付。微信支付已经无法打开页面和支付;支付宝支付可以打开支付宝的网页,如下图
支付宝付款.jpg
我们最初讨论的解决方案是在支付时使用 js 桥接来调用原生支付。如果 H5 页面是同一公司的同事开发的,这是一个简单快捷的方法。但考虑到公司的情况,以及未来可能对接其他公司的 H5 页面,协调调试起来会非常麻烦,所以我们决定不使用桥接来解决问题。
1. H5支付时打开微信或支付宝APP
H5支付中调用微信或者支付宝App的原理是一样的,以WK为例,在代理方法中拦截URL,然后用[[ ]:..URL];进行调用。
唯一不同的就是拦截的字段,微信拦截的字符串是@"://wap/pay",支付宝拦截的字符串是@"://",简单代码如下:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ // 在发送请求之前,决定是否跳转 NSString *url = navigationAction.request.URL.absoluteString; if ([url containsString:@"weixin://wap/pay"] || [url containsString:@"alipay://alipayclient"]) { [[UIApplication sharedApplication] openURL:navigationAction.request.URL]; decisionHandler(WKNavigationActionPolicyCancel); return; } decisionHandler(WKNavigationActionPolicyAllow); }
至此H5支付可以成功调用微信或支付宝App进行支付。
还有一种调用支付宝的方法,在支付宝开发文档中有提到,这里是地址,有兴趣的可以试试:支付宝手机网站支付到App支付
但是如果代码到这里就结束了,可能会出现两种情况:
使用微信支付的时候,操作完成还是会停留在微信里,不会像原生支付那样返回到你自己的APP里,支付宝是支持的,操作完成后会调起来,打开的网页就是之前在WK里打开的页面。
ps:查资料的时候有网友也称微信支付完成时才调用,不过我调试过程中并没有出现这种情况
接下来解决第二个问题。完成支付操作后,返回到你的App
2.打开微信或支付宝App,完成支付后返回自己的App
和往常一样,我会先发布参考链接。
微信返回参考
支付宝退货参考
上面两篇文章非常详细地描述了整个解决过程,包括解决过程中遇到的问题,从哪些方面获得灵感,以及最终是如何一步步找到解决方案的,非常推荐大家去看看,好好学习一下。这里我就不抄袭了,直接说解决步骤吧……
这部分内容得拆成两部分,一部分是支付宝,一部分是微信
当支付宝支付返回App时,拦截到支付宝支付的URL(即URL中包含@“://”),对该URL进行解码;
解码后的URL如下:
:///?{"":"","":"","":"XXX"}
解码之后得到一个包含json字符串的字符串,把json部分取出来转换为 ,里面会有key为 的键值对,把 的值改为自己的App;再把第二步转换成json,然后对改变后的json进行URL ;把第一步截取的URL中的json部分替换成第三步编码后的字符串……注意!!!这里只替换URL中的json部分!!!只替换URL中的json部分!!!替换之后得到一个新的URL;把第四步得到的带有自己APP的URL带过来,调用支付宝App(就跟文章第一部分调用App一样)
需要注意的一点是,对URL进行编码的时候,并不是整个URL都需要编码,只需要对json部分进行编码。如果对完整的URL进行了编码,那么我们用来标识支付宝的字符串部分(@“://”)也会被编码,导致无法调用支付宝。
基本实现如下:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ // 在发送请求之前,决定是否跳转 NSString *url = navigationAction.request.URL.absoluteString; // 支付宝 if ([url containsString:@"alipay://alipayclient"]) { NSMutableString *param = [NSMutableString stringWithFormat:@"%@", (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, (__bridge CFStringRef)url, CFSTR(""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))]; NSRange range = [param rangeOfString:@"{"]; // 截取 json 部分 NSString *param1 = [param substringFromIndex:range.location]; if ([param1 rangeOfString:@"\"fromAppUrlScheme\":"].length > 0) { id json = jsonToClass(param1); // 这里为伪代码, 自行转成 dictionary if (![json isKindOfClass:[NSDictionary class]]) { decisionHandler(WKNavigationActionPolicyAllow); return; } NSMutableDictionary *dicM = [NSMutableDictionary dictionaryWithDictionary:json]; dicM[@"fromAppUrlScheme"] = 自己App的scheme; NSString *jsonStr = classToJson(dicM); // 这里为伪代码, 自行转成json NSString *encodedString = (NSString*) CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)jsonStr, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8)); // 只替换 json 部分 [param replaceCharactersInRange:NSMakeRange(range.location, param.length - range.location) withString:encodedString]; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:param]]; } decisionHandler(WKNavigationActionPolicyCancel); } decisionHandler(WKNavigationActionPolicyAllow); }
微信支付回归App
微信的问题比支付宝稍微麻烦一点,首先就是配置的问题,需要在微信开发者平台上进行配置,如果配置不正确,请参考微信支付开发步骤&常见问题。
提醒一下,微信开发者平台的配置每个月都有修改次数的限制,所以配置的时候尽量把你能想到的配置都配置上,避免开发过程中在这些细节上浪费时间。
拦截微信支付的URL@"。(这里拦截的URL和调用微信的URL不是同一个)H5调用微信支付的时候需要设置请求头,所以直接把请求头=..;赋值给[:@"://" : @""];,设置一个为自己App的。并取消这次加载。
解释一下,其实就是公司的一个域名,可以是H5支付的域名,也可以是公司的其他域名,但是必须保证公司微信开发者平台的配置里存在这个域名。
那么问题来了,既然这是一个公司的域名,而这个域名又需要设置,那么可能会出现这样的问题:同一家公司的其他APP也可能配置相同的。所以这里的域名一定要保证唯一,至于怎么保证,跟后台小伙伴商量一下吧。记得在微信开发者平台上配置!!!
重新加载修改后的请求。拦截包含@"://wap/pay"的URL,并调用微信。
关于微信,主要有以下几点:
必须是唯一的,如果不唯一,就随便开一个(该公司在不明情况下有一款App和某种支付方式完全相同,导致安装了该App的用户无法使用该支付方式,后来还发了律师函。。。)@""会被拦截两次,第一次拦截时需要修改,取消当前加载,用修改后的请求头重新加载请求;虽然修改了请求头,但是没有修改URL,所以重新加载之后,拦截的请求还是@"",需要处理这一步,如果不处理,这一步就会死循环。再次强调微信开发者平台的配置问题,只有配置过的人知道!
基本实现如下:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ // 在发送请求之前,决定是否跳转 //self.load 用来控制对 @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?" 的拦截 NSString *url = navigationAction.request.URL.absoluteString; if ([url containsString:@"weixin://wap/pay"]) { self.load = NO; [[UIApplication sharedApplication] openURL:navigationAction.request.URL]; decisionHandler(WKNavigationActionPolicyCancel); } else if ([url containsString:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"] && !self.isLoad) { NSURLRequest *request = navigationAction.request; NSMutableURLRequest *newRequest = [[NSMutableURLRequest alloc] init]; newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields; #warning scheme 要改 [newRequest setValue:@"www.xxx.cn://" forHTTPHeaderField: @"Referer"]; newRequest.URL = request.URL; [webView loadRequest:newRequest]; self.load = YES; decisionHandler(WKNavigationActionPolicyCancel); } else if ([url containsString:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"]) { self.load = NO; decisionHandler(WKNavigationActionPolicyAllow); } decisionHandler(WKNavigationActionPolicyAllow); }
至此,文章开头提到的两部分已经完成,可以调用和返回了。
2019/8/23 更新
不想修改?反正我修改不了,上面的微信解决方案我不接受,怎么办? 我去查了一下微信支付开发步骤&常见问题,还真发现了新东西,不知道是之前没注意到还是新加的... 拼接可以指定回调页面。
回调页面.png
我采用了之前的演示并做了一些简单的更改。我试了一下,它有效。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{ // 在发送请求之前,决定是否跳转 //self.load 用来控制对 @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?" 的拦截 NSString *url = navigationAction.request.URL.absoluteString; if ([url containsString:@"weixin://wap/pay"]) { self.load = NO; [[UIApplication sharedApplication] openURL:navigationAction.request.URL]; decisionHandler(WKNavigationActionPolicyCancel); } else if ([url containsString:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"] && !self.isLoad) { NSURLRequest *request = navigationAction.request; NSMutableURLRequest *newRequest = [[NSMutableURLRequest alloc] init]; // newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields; // [newRequest setValue:@"www.xxx.cn://" forHTTPHeaderField: @"Referer"]; // newRequest.URL = request.URL; // 这里 redirect_url 要传的值, 就是上面 Referer 的值 NSString *urlStr = [NSString stringWithFormat:@"%@&redirect_url=www.xxx.cn://", [request.URL absoluteString]]; newRequest.URL = [NSURL URLWithString:urlStr]; [webView loadRequest:newRequest]; self.load = YES; decisionHandler(WKNavigationActionPolicyCancel); } else if ([url containsString:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"]) { self.load = NO; decisionHandler(WKNavigationActionPolicyAllow); } decisionHandler(WKNavigationActionPolicyAllow); }
简单来说就是不设置之前需要设置的值,直接作为值,放在URL后面。但是返回自己App时显示的页面效果不一样。以支付失败为例(没有支付1分钱就不算支付成功)
回到原APP,页面会停留在跳转前的页面(仿佛时间停止了)
回到源app,打开微信页面,白屏。。。手动返回会回到跳转前的页面
不过微信支付开发步骤及常见问题也提到,设置好之后,可能需要用户手动触发订单查询操作,H5可能也需要做一些操作……(看到这里我基本确定这个是新增的!!!!)
注释.png