iOS WKWebView实现H5跳转微信、支付宝支付,并返回原APP

项目开发遇到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随便写)。可以直接问申请微信支付的人要。

Schemes.png

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