iOS解決H5支付跳轉(zhuǎn)到支付App及返回原App問(wèn)題

標(biāo)題如此拗口, 我也是無(wú)可奈何??

本文會(huì)涉及到兩個(gè)方面:

  1. H5 支付時(shí)調(diào)起微信或支付寶 App;
  2. 調(diào)起微信或支付寶 App 完成支付操作后,返回到自己的原來(lái)的 App。

公司業(yè)務(wù)需求,需客戶端嵌套一個(gè)完整的 H5 開(kāi)發(fā)的網(wǎng)頁(yè),其中帶有 H5 的微信支付和支付寶支付。微信支付一直無(wú)法打開(kāi)頁(yè)面,無(wú)法支付;支付寶支付可以打開(kāi)支付寶的網(wǎng)頁(yè),如下圖

支付寶支付.jpg

最初討論的解決辦法是:走到支付時(shí),通過(guò) js 橋來(lái)調(diào)起原生支付。如果 H5 頁(yè)面是同一公司同事開(kāi)發(fā)的,這倒是個(gè)簡(jiǎn)單快捷的方法。但是結(jié)合公司情況,考慮到后期可能會(huì)接入其他公司的 H5 頁(yè)面,聯(lián)調(diào)起來(lái)會(huì)很麻煩,所以還是決定不通過(guò)橋解決。

1. H5 支付時(shí)調(diào)起微信或支付寶 App

H5 支付調(diào)起微信或支付寶 App 的原理都一樣,以 WK 為例,都是在 decidePolicyForNavigationAction 代理方法里面攔截 URL,再用 [[UIApplication sharedApplication] openURL:navigationAction.request.URL]; 調(diào)起。
不同的只是攔截的字段有區(qū)分,微信需攔截的字符串為 @"weixin://wap/pay" , 支付寶攔截的字符串為 @"alipay://alipayclient"。簡(jiǎn)單代碼如下:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // 在發(fā)送請(qǐng)求之前,決定是否跳轉(zhuǎn)
    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 支付可以成功調(diào)起微信或支付寶的 App 進(jìn)行支付了。

支付寶還有另一種調(diào)起的方式, 在支付寶的開(kāi)發(fā)文檔中有提到, 這里也貼一下地址, 有興趣的可以去試下: 支付寶手機(jī)網(wǎng)站支付轉(zhuǎn)App支付

但是,如果代碼寫(xiě)到這里就完的話,可能會(huì)出現(xiàn)兩種情況:

  1. 使用微信支付, 操作完成, 仍停留在微信, 不會(huì)像原生調(diào)起支付那樣返回自己的APP;
  2. 支付寶支持,操作完后,調(diào)起了 Safari,打開(kāi)的網(wǎng)頁(yè)就是之前在 WK 里打開(kāi)的頁(yè)面

ps:查資料時(shí),有網(wǎng)友微信支付完成時(shí),也會(huì)調(diào)起 Safari,但我調(diào)試過(guò)程中未出現(xiàn)這種情況

接下來(lái)解決第二個(gè)問(wèn)題,完成支付操作后,返回自己的 App

2. 調(diào)起微信或支付寶 App 完成支付操作后,返回到自己的 App

老規(guī)矩,先貼上參考的鏈接
微信返回參考 http://m.itdecent.cn/p/90db7dfb075c
支付寶返回參考 http://m.itdecent.cn/p/0d8dd04fe94e
以上兩篇文章里, 非常詳細(xì)的描述了解決的整個(gè)過(guò)程, 包括解決過(guò)程中遇到哪些問(wèn)題, 從哪些方面思考得到靈感, 最終如何一步步找到解決辦法, 非常建議去看看, 了解下. 這里就不照搬了, 直接講解決步驟了...

這部分內(nèi)容得再拆分成兩部分, 一個(gè)支付寶的, 一個(gè)微信的

支付寶支付返回到 App

  1. 攔截到支付寶支付的 URL (就是 URL 里包含 @"alipay://alipayclient") 時(shí), 對(duì) URL 進(jìn)行 URL 解碼;

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

  1. 解碼后得到一字符串, 字符串里包含了一個(gè)json 串. 把 json 部分截取出來(lái), 再轉(zhuǎn)成 dictionary, dictionary 里面將會(huì)有一個(gè) key 為 fromAppUrlScheme 的鍵值對(duì), 把 fromAppUrlScheme 的值改成自己 App 的 scheme;
  2. 把第二步得到 dictionary 再轉(zhuǎn)成 json, 再對(duì)已經(jīng)改了 fromAppUrlScheme 值的 json 進(jìn)行 URL 編碼;
  3. 把第三步編碼好的字符串, 替換掉第一步攔截的 URL 的 json 部分... 注意!!! 這里替換的只是 URL 的 json 部分!!! 只是 URL 的 json 部分!!! 替換后得到一個(gè)新的的 URL;
  4. 拿第四步得到的帶有自己 APP 的 scheme 的 URL, 去調(diào)起支付寶 App(就是文章第一部分說(shuō)的調(diào)起 App 那樣的調(diào)起)

需要注意的一個(gè)地方是, 進(jìn)行 URL 編碼時(shí), 不是 URL 整體進(jìn)行編碼, 只有 json 那部分需要編碼, 如果對(duì)完整的 URL 進(jìn)行編碼, 那么我們用來(lái)識(shí)別支付寶的字符串的那部分 (@"alipay://alipayclient") 也會(huì)被編碼, 從而導(dǎo)致無(wú)法調(diào)起支付寶

基本實(shí)現(xiàn)如下:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // 在發(fā)送請(qǐng)求之前,決定是否跳轉(zhuǎn)
    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); // 這里為偽代碼, 自行轉(zhuǎn)成 dictionary
            if (![json isKindOfClass:[NSDictionary class]]) {
                decisionHandler(WKNavigationActionPolicyAllow);
                return;
            }
            
            NSMutableDictionary *dicM = [NSMutableDictionary dictionaryWithDictionary:json];
            dicM[@"fromAppUrlScheme"] = 自己App的scheme;
            
            NSString *jsonStr = classToJson(dicM); // 這里為偽代碼, 自行轉(zhuǎn)成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

微信的問(wèn)題比支付寶的稍微麻煩些. 首先麻煩的就是配置問(wèn)題, 需要在微信開(kāi)發(fā)者平臺(tái)上進(jìn)行了相應(yīng)的配置. 如果配置不對(duì), 請(qǐng)參照 微信支付開(kāi)發(fā)步驟&常見(jiàn)問(wèn)題.

提醒下, 微信開(kāi)發(fā)者平臺(tái)上的配置的有次數(shù)限制的, 每個(gè)月只能修改多少多少次, 所以配置時(shí)盡量把能想到的需要用到的都一起配置了, 省得開(kāi)發(fā)的時(shí)候被這些細(xì)節(jié)問(wèn)題浪費(fèi)時(shí)間

  1. 攔截到微信支付的 URL @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?". (這里需要攔截的 URL 與調(diào)起微信的 URL 不是同一個(gè))
  2. H5 調(diào)起微信支付時(shí), 需要設(shè)置 Referer 請(qǐng)求頭, 所以直接拿請(qǐng)求頭 newRequest.allHTTPHeaderFields = navigationAction.request.allHTTPHeaderFields;
  3. 給 Referer 賦值 [newRequest setValue:@"www.xxx.com://" forHTTPHeaderField: @"Referer"];, 自己的 App 也要設(shè)置一個(gè) www.xxx.com 的 scheme. 并且取消此次加載.

解釋下 www.xxx.com , 其實(shí)就是公司的一個(gè)域名, 可以是 H5 支付的域名, 也可以是公司其他域名, 但必須確保這個(gè)域名存在于公司的微信開(kāi)發(fā)者平臺(tái)的配置中.
那么問(wèn)題來(lái)了, 這既然是公司的一個(gè)域名, 又要把這個(gè)域名設(shè)置成 scheme, 那有可能出現(xiàn)這么一個(gè)問(wèn)題: 同公司的其他 App 也可能配置了同樣的 scheme. 所以, 這里的域名和 scheme 要保證唯一性. 至于怎么保證, 跟后臺(tái)哥們商量下吧. 記得配置到微信開(kāi)發(fā)者平臺(tái)上!!!

  1. 重新加載修改了 Referer 的請(qǐng)求.
  2. 攔截包含 @"weixin://wap/pay" 的 URL, 調(diào)起微信.

針對(duì)微信這部分, 劃幾個(gè)重點(diǎn):

  1. scheme 必須唯一, 不唯一的話隨機(jī)打開(kāi)一個(gè) (公司有個(gè) App 不知在什么情況下配了個(gè)跟某支付一毛一樣的 scheme, 導(dǎo)致裝有那個(gè) App 的用戶都不能用某支付來(lái)支付, 后來(lái)被發(fā)律師函了... )
  2. @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?" 會(huì)攔截兩次. 攔截第一次時(shí), 需要修改 Referer, 取消此次加載, 重新加載修改了請(qǐng)求頭的請(qǐng)求; 雖然請(qǐng)求頭修改了, 可是 URL 并沒(méi)有修改, 所以, 重新加載之后攔截到的還是 @"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?". 在這一步需做處理, 如不處理, 這一步就死循環(huán).
  3. 再次強(qiáng)調(diào)微信開(kāi)發(fā)者平臺(tái)的配置問(wèn)題. 誰(shuí)配誰(shuí)知道!

基本實(shí)現(xiàn)如下:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // 在發(fā)送請(qǐng)求之前,決定是否跳轉(zhuǎn)
    //self.load  用來(lái)控制對(duì) @"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);
}

至此, 已經(jīng)完成文章開(kāi)頭所說(shuō)的兩個(gè)部分, 能調(diào)起也能返回了.

2019/8/23 更新

不愿意修改 header ? 反正就是不能改 header, 不接受上面微信的解決方案, 怎么辦呢? 去翻了下 微信支付開(kāi)發(fā)步驟&常見(jiàn)問(wèn)題 , 還真的有新發(fā)現(xiàn), 不知道是之前沒(méi)注意還是新加的... 拼接 redirect_url 可以指定回調(diào)頁(yè)面.

回調(diào)頁(yè)面.png

拿之前的 demo 簡(jiǎn)單的改改, 試了一下, 確實(shí)可以 ??

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    // 在發(fā)送請(qǐng)求之前,決定是否跳轉(zhuǎn)
    //self.load  用來(lái)控制對(duì) @"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);
}

簡(jiǎn)單點(diǎn)說(shuō)就是不設(shè)置 Referer, 把之前需要設(shè)置的 Referer 值, 直接作為 redirect_url 的值, 拼在 URL 后面. 但返回自有 App 兩者顯示的頁(yè)面效果是不太相同的, 以支付失敗為例(沒(méi)有1分錢(qián)的支付就不存支付成功的?? ??)

Referer 回到原有 App, 會(huì)停留在跳轉(zhuǎn)前的頁(yè)面(仿佛時(shí)間靜止了)

redirect_url 回到原因 App, 打開(kāi)了一個(gè)微信頁(yè)面, 白屏... 手動(dòng)返回會(huì)回到跳轉(zhuǎn)前的頁(yè)面

不過(guò) 微信支付開(kāi)發(fā)步驟&常見(jiàn)問(wèn)題 也有提到, 設(shè)置 redirect_url 后, 可能需要用戶手動(dòng)觸發(fā)查單操作, 可能還需要 H5 那邊做點(diǎn)啥子操作吧...... (看到這里, 我基本確定了, 這個(gè)是新加上去的!!!!!)

redirect_url 注意事項(xiàng).png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容