大致的業(yè)務(wù)場(chǎng)景是這樣的:我們的客戶端APP本身不包含支付SDK,但是在APP內(nèi)打開的HTML5是包含了第三方支付的,而且在Safari內(nèi)是可以正常調(diào)起支付寶/微信客戶端進(jìn)行支付的,然而在APP的webview內(nèi)打開同樣的URL則毫無反應(yīng)。
原因大致是支付寶/微信的h5支付sdk沒有對(duì)客戶端支持,當(dāng)然也存在一些系統(tǒng)的限制。
現(xiàn)在就來解決一下這個(gè)問題。
柳暗
經(jīng)過稍微的查詢和參考,解決方案其實(shí)非常簡(jiǎn)單,只需要在WKWebView的代理方法func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)內(nèi)監(jiān)聽微信/支付寶的特定前綴URL,然后使用openUrl方法打開這個(gè)URL就可以觸發(fā)支付寶/微信的scheme。具體代碼大致如下:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
pushCurrentSnapshotViewWithRequest(request: navigationAction.request)
guard let curUrl = navigationAction.request.url else {
decisionHandler(.allow); return
}
if curUrl.absoluteString.hasPrefix("alipay://alipayclient/") || curUrl.absoluteString.hasPrefix("weixin://"){
decisionHandler(WKNavigationActionPolicy.cancel)
UIApplication.shared.openURL(url)
return
}
decisionHandler(WKNavigationActionPolicy.allow)
}
開心,居然這么簡(jiǎn)單。
然后…emmmmm,跳是可以正常跳了,但是好像支付結(jié)束后無法跳回APP。
冷靜分析一下,我們都知道iOS內(nèi)的應(yīng)用間跳轉(zhuǎn),基本都是通過scheme的方式,跳出去如此,要返回也是如此。
花明
先看下支付寶支付:
捕獲支付寶web支付跳轉(zhuǎn)鏈接如 alipay://alipayclient/?{"requestType":"SafePay","fromAppUrlScheme":"alipays","dataString":"h5_route_token=\"shierRZ25\"&is_h5_route=\"true\""}
發(fā)現(xiàn)其中只要將fromAppUrlScheme改為APP內(nèi)配置的scheme,即可正確跳轉(zhuǎn)回應(yīng)用。具體代碼示例如下:
fileprivate func handleAlipayUrl(url: URL) -> URL? {
if url.absoluteString.hasPrefix("alipay://alipayclient/") {
// 更換scheme
var decodePar = url.query ?? ""
decodePar.urlDecode()
var dict = JSON(parseJSON: decodePar)
dict["fromAppUrlScheme"] = "xproject"
if let strData = try? JSONSerialization.data(withJSONObject: dict.dictionaryObject ?? [:], options: []) {
var param = String(data: strData, encoding: .utf8)
param?.urlEncode()
let finalStr = "alipay://alipayclient/?\(param ?? "")"
if let finalUrl = URL(string: finalStr) {
return finalUrl
}
}
return url
}
return nil
}
似乎挺順利,再看一下微信,微信的h5支付回調(diào)應(yīng)該是服務(wù)端提供的一個(gè)h5地址,因此支付完成后默認(rèn)是跳轉(zhuǎn)到了Safari,在APP內(nèi)進(jìn)行的支付,我們要換掉這個(gè)回調(diào),變成我們自己的。
大致步驟是:
- 工程文件添加Scheme,內(nèi)容為[APP本地配置的scheme]
- 捕獲跳轉(zhuǎn)鏈接 https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb,將其中其中的redirect_url參數(shù)換成[APP本地配置的scheme]
- 重新發(fā)起請(qǐng)求,給請(qǐng)求頭加上Referer字段,內(nèi)容為[APP本地配置的scheme]
- 使用openUrl發(fā)起微信客戶端調(diào)用
這里參考了這篇文章 http://www.itdecent.cn/p/c1973aacc774
需要注意的一點(diǎn)就是,[APP本地配置的scheme]需要是http的URL形式,而且根域名是要包含在微信支付后臺(tái)填寫的白名單內(nèi)的,譬如白名單域名是abc.com,你可以將你的scheme設(shè)置為ios.abc.com,否則也不會(huì)生效。
具體代碼大致如下:
let wxpayScheme = "ios.abc.com://"
// 去除原有的URL回調(diào)地址,換成自己的配置
if curUrl.absoluteString.hasPrefix("https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb") {
if var comps = URLComponents(string: curUrl.absoluteString) {
var needChange = false
for (idx, item) in (comps.queryItems ?? []).enumerated() {
if item.name == "redirect_url" && item.value != wxpayScheme {
needChange = true
comps.queryItems?.remove(at: idx)
break
}
}
if needChange {
comps.queryItems?.append(URLQueryItem(name: "redirect_url", value: wxpayScheme))
if let finalUrl = comps.url {
// 給請(qǐng)求頭加上Referer字段
let mRequest = NSMutableURLRequest(url: finalUrl)
mRequest.setValue(wxpayScheme, forHTTPHeaderField: "Referer")
decisionHandler(WKNavigationActionPolicy.cancel)
webView.load(mRequest as URLRequest)
return
}
}
}
}
替換的過程有一點(diǎn)繞,其實(shí)就是找到相應(yīng)字段替換掉,有更好地寫法。
嘗試了一下,可以成功跳轉(zhuǎn)回來了,但是新的問題又出現(xiàn)了→_→
又一村
因?yàn)樘鎿Q了微信支付的回調(diào),h5的跳轉(zhuǎn)可能會(huì)出現(xiàn)白屏的問題,上面的文章也有提到。
我根據(jù)自己的實(shí)際情況采用了直接強(qiáng)制調(diào)用webView.goBack(),因?yàn)楸旧淼腍5頁面自帶了支付等待完成頁,支付完成后返回APP,確認(rèn)一下支付狀態(tài)就好了。
需要注意的是,調(diào)用微信支付5秒后,webview會(huì)收到一個(gè)鏈接調(diào)整,截獲然后進(jìn)行后退就好:
if curUrl.absoluteString.hasPrefix(wxpayScheme) {
// 進(jìn)入空白頁,強(qiáng)制后退
decisionHandler(WKNavigationActionPolicy.cancel)
webView.goBack()
return
}
不過這個(gè)白屏問題可能會(huì)根據(jù)本身h5的不同而采取不同的解決方案,所以這個(gè)應(yīng)該并非萬全之策。