iOS解决H5支付跳转到支付App及返回原App问题

标题如此拗口, 我也是无可奈何🤣

本文会涉及到两个方面:

  1. H5 支付时调起微信或支付宝 App
  2. 调起微信或支付宝 App 完成支付操作后,返回到自己的原来的 App

公司业务需求,需客户端嵌套一个完整的 H5 开发的网页,其中带有 H5 的微信支付和支付宝支付。微信支付一直无法打开页面,无法支付;支付宝支付可以打开支付宝的网页,如下图

支付宝支付.jpg

最初讨论的解决办法是:走到支付时,通过 js 桥来调起原生支付。如果 H5 页面是同一公司同事开发的,这倒是个简单快捷的方法。但是结合公司情况,考虑到后期可能会接入其他公司的 H5 页面,联调起来会很麻烦,所以还是决定不通过桥解决。

1. H5 支付时调起微信或支付宝 App

H5 支付调起微信或支付宝 App 的原理都一样,以 WK 为例,都是在 decidePolicyForNavigationAction 代理方法里面拦截 URL,再用 [[UIApplication sharedApplication] openURL:navigationAction.request.URL]; 调起。
不同的只是拦截的字段有区分,微信需拦截的字符串为 @"weixin://wap/pay" , 支付宝拦截的字符串为 @"alipay://alipayclient"。简单代码如下:

- (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支付

但是,如果代码写到这里就完的话,可能会出现两种情况:

  1. 使用微信支付, 操作完成, 仍停留在微信, 不会像原生调起支付那样返回自己的APP;
  2. 支付宝支持,操作完后,调起了 Safari,打开的网页就是之前在 WK 里打开的页面

ps:查资料时,有网友微信支付完成时,也会调起 Safari,但我调试过程中未出现这种情况

接下来解决第二个问题,完成支付操作后,返回自己的 App

2. 调起微信或支付宝 App 完成支付操作后,返回到自己的 App

老规矩,先贴上参考的链接
微信返回参考 https://www.jianshu.com/p/90db7dfb075c
支付宝返回参考 https://www.jianshu.com/p/0d8dd04fe94e
以上两篇文章里, 非常详细的描述了解决的整个过程, 包括解决过程中遇到哪些问题, 从哪些方面思考得到灵感, 最终如何一步步找到解决办法, 非常建议去看看, 了解下. 这里就不照搬了, 直接讲解决步骤了...

这部分内容得再拆分成两部分, 一个支付宝的, 一个微信的

支付宝支付返回到 App

  1. 拦截到支付宝支付的 URL (就是 URL 里包含 @"alipay://alipayclient") 时, 对 URL 进行 URL 解码;

解码后的 URL 如下:
alipay://alipayclient/?{"requestType":"SafePay","fromAppUrlScheme":"alipays","dataString":"XXX"}

  1. 解码后得到一字符串, 字符串里包含了一个json 串. 把 json 部分截取出来, 再转成 dictionary, dictionary 里面将会有一个 key 为 fromAppUrlScheme 的键值对, 把 fromAppUrlScheme 的值改成自己 App 的 scheme;
  2. 把第二步得到 dictionary 再转成 json, 再对已经改了 fromAppUrlScheme 值的 json 进行 URL 编码;
  3. 把第三步编码好的字符串, 替换掉第一步拦截的 URL 的 json 部分... 注意!!! 这里替换的只是 URL 的 json 部分!!! 只是 URL 的 json 部分!!! 替换后得到一个新的的 URL;
  4. 拿第四步得到的带有自己 APP 的 scheme 的 URL, 去调起支付宝 App(就是文章第一部分说的调起 App 那样的调起)

需要注意的一个地方是, 进行 URL 编码时, 不是 URL 整体进行编码, 只有 json 那部分需要编码, 如果对完整的 URL 进行编码, 那么我们用来识别支付宝的字符串的那部分 (@"alipay://alipayclient") 也会被编码, 从而导致无法调起支付宝

基本实现如下:

- (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

微信的问题比支付宝的稍微麻烦些. 首先麻烦的就是配置问题, 需要在微信开发者平台上进行了相应的配置. 如果配置不对, 请参照 微信支付开发步骤&常见问题.

提醒下, 微信开发者平台上的配置的有次数限制的, 每个月只能修改多少多少次, 所以配置时尽量把能想到的需要用到的都一起配置了, 省得开发的时候被这些细节问题浪费时间

  1. 拦截到微信支付的 URL @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?". (这里需要拦截的 URL 与调起微信的 URL 不是同一个)
  2. H5 调起微信支付时, 需要设置 Referer 请求头, 所以直接拿请求头 newRequest.allHTTPHeaderFields = navigationAction.request.allHTTPHeaderFields;
  3. 给 Referer 赋值 [newRequest setValue:@"www.xxx.com://" forHTTPHeaderField: @"Referer"];, 自己的 App 也要设置一个 www.xxx.com 的 scheme. 并且取消此次加载.

解释下 www.xxx.com , 其实就是公司的一个域名, 可以是 H5 支付的域名, 也可以是公司其他域名, 但必须确保这个域名存在于公司的微信开发者平台的配置中.
那么问题来了, 这既然是公司的一个域名, 又要把这个域名设置成 scheme, 那有可能出现这么一个问题: 同公司的其他 App 也可能配置了同样的 scheme. 所以, 这里的域名和 scheme 要保证唯一性. 至于怎么保证, 跟后台哥们商量下吧. 记得配置到微信开发者平台上!!!

  1. 重新加载修改了 Referer 的请求.
  2. 拦截包含 @"weixin://wap/pay" 的 URL, 调起微信.

针对微信这部分, 划几个重点:

  1. scheme 必须唯一, 不唯一的话随机打开一个 (公司有个 App 不知在什么情况下配了个跟某支付一毛一样的 scheme, 导致装有那个 App 的用户都不能用某支付来支付, 后来被发律师函了... )
  2. @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?" 会拦截两次. 拦截第一次时, 需要修改 Referer, 取消此次加载, 重新加载修改了请求头的请求; 虽然请求头修改了, 可是 URL 并没有修改, 所以, 重新加载之后拦截到的还是 @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?". 在这一步需做处理, 如不处理, 这一步就死循环.
  3. 再次强调微信开发者平台的配置问题. 谁配谁知道!

基本实现如下:

- (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 更新

不愿意修改 header ? 反正就是不能改 header, 不接受上面微信的解决方案, 怎么办呢? 去翻了下 微信支付开发步骤&常见问题 , 还真的有新发现, 不知道是之前没注意还是新加的... 拼接 redirect_url 可以指定回调页面.

回调页面.png

拿之前的 demo 简单的改改, 试了一下, 确实可以 👍

- (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);
}

简单点说就是不设置 Referer, 把之前需要设置的 Referer 值, 直接作为 redirect_url 的值, 拼在 URL 后面. 但返回自有 App 两者显示的页面效果是不太相同的, 以支付失败为例(没有1分钱的支付就不存支付成功的🌰 🤣)

Referer 回到原有 App, 会停留在跳转前的页面(仿佛时间静止了)

redirect_url 回到原因 App, 打开了一个微信页面, 白屏... 手动返回会回到跳转前的页面

不过 微信支付开发步骤&常见问题 也有提到, 设置 redirect_url 后, 可能需要用户手动触发查单操作, 可能还需要 H5 那边做点啥子操作吧...... (看到这里, 我基本确定了, 这个是新加上去的!!!!!)

redirect_url 注意事项.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容