標(biāo)題如此拗口, 我也是無(wú)可奈何??
本文會(huì)涉及到兩個(gè)方面:
- H5 支付時(shí)調(diào)起微信或支付寶 App;
- 調(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è),如下圖

最初討論的解決辦法是:走到支付時(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)兩種情況:
- 使用微信支付, 操作完成, 仍停留在微信, 不會(huì)像原生調(diào)起支付那樣返回自己的APP;
- 支付寶支持,操作完后,調(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
- 攔截到支付寶支付的 URL (就是 URL 里包含 @"alipay://alipayclient") 時(shí), 對(duì) URL 進(jìn)行 URL 解碼;
解碼后的 URL 如下:
alipay://alipayclient/?{"requestType":"SafePay","fromAppUrlScheme":"alipays","dataString":"XXX"}
- 解碼后得到一字符串, 字符串里包含了一個(gè)json 串. 把 json 部分截取出來(lái), 再轉(zhuǎn)成 dictionary, dictionary 里面將會(huì)有一個(gè) key 為 fromAppUrlScheme 的鍵值對(duì), 把 fromAppUrlScheme 的值改成自己 App 的 scheme;
- 把第二步得到 dictionary 再轉(zhuǎn)成 json, 再對(duì)已經(jīng)改了 fromAppUrlScheme 值的 json 進(jìn)行 URL 編碼;
- 把第三步編碼好的字符串, 替換掉第一步攔截的 URL 的 json 部分... 注意!!! 這里替換的只是 URL 的 json 部分!!! 只是 URL 的 json 部分!!! 替換后得到一個(gè)新的的 URL;
- 拿第四步得到的帶有自己 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í)間
- 攔截到微信支付的 URL
@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?". (這里需要攔截的 URL 與調(diào)起微信的 URL 不是同一個(gè)) - H5 調(diào)起微信支付時(shí), 需要設(shè)置 Referer 請(qǐng)求頭, 所以直接拿請(qǐng)求頭
newRequest.allHTTPHeaderFields = navigationAction.request.allHTTPHeaderFields; - 給 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)上!!!
- 重新加載修改了 Referer 的請(qǐng)求.
- 攔截包含
@"weixin://wap/pay"的 URL, 調(diào)起微信.
針對(duì)微信這部分, 劃幾個(gè)重點(diǎn):
- scheme 必須唯一, 不唯一的話隨機(jī)打開(kāi)一個(gè) (公司有個(gè) App 不知在什么情況下配了個(gè)跟某支付一毛一樣的 scheme, 導(dǎo)致裝有那個(gè) App 的用戶都不能用某支付來(lái)支付, 后來(lái)被發(fā)律師函了... )
@"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).- 再次強(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è)面.

拿之前的 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è)是新加上去的!!!!!)
