標(biāo)題如此拗口, 我也是無可奈何??
本文會(huì)涉及到兩個(gè)方面:
- H5 支付時(shí)調(diào)起微信或支付寶 App;
- 調(diào)起微信或支付寶 App 完成支付操作后章咧,返回到自己的原來的 App惨险。
公司業(yè)務(wù)需求食呻,需客戶端嵌套一個(gè)完整的 H5 開發(fā)的網(wǎng)頁,其中帶有 H5 的微信支付和支付寶支付嘶摊。微信支付一直無法打開頁面邑跪,無法支付族铆;支付寶支付可以打開支付寶的網(wǎng)頁,如下圖
最初討論的解決辦法是:走到支付時(shí)摘昌,通過 js 橋來調(diào)起原生支付斥杜。如果 H5 頁面是同一公司同事開發(fā)的虱颗,這倒是個(gè)簡(jiǎn)單快捷的方法。但是結(jié)合公司情況蔗喂,考慮到后期可能會(huì)接入其他公司的 H5 頁面忘渔,聯(lián)調(diào)起來會(huì)很麻煩,所以還是決定不通過橋解決缰儿。
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)起的方式, 在支付寶的開發(fā)文檔中有提到, 這里也貼一下地址, 有興趣的可以去試下: 支付寶手機(jī)網(wǎng)站支付轉(zhuǎn)App支付
但是椅棺,如果代碼寫到這里就完的話犁罩,可能會(huì)出現(xiàn)兩種情況:
- 使用微信支付, 操作完成, 仍停留在微信, 不會(huì)像原生調(diào)起支付那樣返回自己的APP;
- 支付寶支持,操作完后两疚,調(diào)起了 Safari床估,打開的網(wǎng)頁就是之前在 WK 里打開的頁面
ps:查資料時(shí),有網(wǎng)友微信支付完成時(shí)诱渤,也會(huì)調(diào)起 Safari丐巫,但我調(diào)試過程中未出現(xiàn)這種情況
接下來解決第二個(gè)問題,完成支付操作后勺美,返回自己的 App
2. 調(diào)起微信或支付寶 App 完成支付操作后递胧,返回到自己的 App
老規(guī)矩,先貼上參考的鏈接
微信返回參考 http://www.reibang.com/p/90db7dfb075c
支付寶返回參考 http://www.reibang.com/p/0d8dd04fe94e
以上兩篇文章里, 非常詳細(xì)的描述了解決的整個(gè)過程, 包括解決過程中遇到哪些問題, 從哪些方面思考得到靈感, 最終如何一步步找到解決辦法, 非常建議去看看, 了解下. 這里就不照搬了, 直接講解決步驟了...
這部分內(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 部分截取出來, 再轉(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(就是文章第一部分說的調(diào)起 App 那樣的調(diào)起)
需要注意的一個(gè)地方是, 進(jìn)行 URL 編碼時(shí), 不是 URL 整體進(jìn)行編碼, 只有 json 那部分需要編碼, 如果對(duì)完整的 URL 進(jìn)行編碼, 那么我們用來識(shí)別支付寶的字符串的那部分 (@"alipay://alipayclient") 也會(huì)被編碼, 從而導(dǎo)致無法調(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
微信的問題比支付寶的稍微麻煩些. 首先麻煩的就是配置問題, 需要在微信開發(fā)者平臺(tái)上進(jìn)行了相應(yīng)的配置. 如果配置不對(duì), 請(qǐng)參照 微信支付開發(fā)步驟&常見問題.
提醒下, 微信開發(fā)者平臺(tái)上的配置的有次數(shù)限制的, 每個(gè)月只能修改多少多少次, 所以配置時(shí)盡量把能想到的需要用到的都一起配置了, 省得開發(fā)的時(shí)候被這些細(xì)節(jié)問題浪費(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è)域名存在于公司的微信開發(fā)者平臺(tái)的配置中.
那么問題來了, 這既然是公司的一個(gè)域名, 又要把這個(gè)域名設(shè)置成 scheme, 那有可能出現(xiàn)這么一個(gè)問題: 同公司的其他 App 也可能配置了同樣的 scheme. 所以, 這里的域名和 scheme 要保證唯一性. 至于怎么保證, 跟后臺(tái)哥們商量下吧. 記得配置到微信開發(fā)者平臺(tái)上!!!
- 重新加載修改了 Referer 的請(qǐng)求.
- 攔截包含
@"weixin://wap/pay"
的 URL, 調(diào)起微信.
針對(duì)微信這部分, 劃幾個(gè)重點(diǎn):
- scheme 必須唯一, 不唯一的話隨機(jī)打開一個(gè) (公司有個(gè) App 不知在什么情況下配了個(gè)跟某支付一毛一樣的 scheme, 導(dǎo)致裝有那個(gè) App 的用戶都不能用某支付來支付, 后來被發(fā)律師函了... )
@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"
會(huì)攔截兩次. 攔截第一次時(shí), 需要修改 Referer, 取消此次加載, 重新加載修改了請(qǐng)求頭的請(qǐng)求; 雖然請(qǐng)求頭修改了, 可是 URL 并沒有修改, 所以, 重新加載之后攔截到的還是@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"
. 在這一步需做處理, 如不處理, 這一步就死循環(huán).- 再次強(qiáng)調(diào)微信開發(fā)者平臺(tái)的配置問題. 誰配誰知道!
基本實(shí)現(xiàn)如下:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
// 在發(fā)送請(qǐng)求之前缎脾,決定是否跳轉(zhuǎn)
//self.load 用來控制對(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)完成文章開頭所說的兩個(gè)部分, 能調(diào)起也能返回了.
2019/8/23 更新
不愿意修改 header ? 反正就是不能改 header, 不接受上面微信的解決方案, 怎么辦呢? 去翻了下 微信支付開發(fā)步驟&常見問題 , 還真的有新發(fā)現(xiàn), 不知道是之前沒注意還是新加的... 拼接 redirect_url
可以指定回調(diào)頁面.
拿之前的 demo 簡(jiǎn)單的改改, 試了一下, 確實(shí)可以 ??
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
// 在發(fā)送請(qǐng)求之前,決定是否跳轉(zhuǎn)
//self.load 用來控制對(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)說就是不設(shè)置 Referer
, 把之前需要設(shè)置的 Referer
值, 直接作為 redirect_url
的值, 拼在 URL 后面. 但返回自有 App 兩者顯示的頁面效果是不太相同的, 以支付失敗為例(沒有1分錢的支付就不存支付成功的?? ??)
Referer
回到原有 App, 會(huì)停留在跳轉(zhuǎn)前的頁面(仿佛時(shí)間靜止了)
redirect_url
回到原因 App, 打開了一個(gè)微信頁面, 白屏... 手動(dòng)返回會(huì)回到跳轉(zhuǎn)前的頁面
不過 微信支付開發(fā)步驟&常見問題 也有提到, 設(shè)置 redirect_url 后, 可能需要用戶手動(dòng)觸發(fā)查單操作, 可能還需要 H5 那邊做點(diǎn)啥子操作吧...... (看到這里, 我基本確定了, 這個(gè)是新加上去的!!!!!)