大致的業務場景是這樣的:我們的客戶端APP本身不包含支付SDK,但是在APP內打開的HTML5是包含了第三方支付的,而且在Safari內是可以正常調起支付寶/微信客戶端進行支付的,然而在APP的webview內打開同樣的URL則毫無反應。
原因大致是支付寶/微信的h5支付sdk沒有對客戶端支持,當然也存在一些系統的限制。
現在就來解決一下這個問題。
柳暗
經過稍微的查詢和參考,解決方案其實非常簡單,只需要在WKWebView
的代理方法func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
內監聽微信/支付寶的特定前綴URL,然后使用openUrl
方法打開這個URL就可以觸發支付寶/微信的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)
}
開心,居然這么簡單。
然后…emmmmm,跳是可以正常跳了,但是好像支付結束后無法跳回APP。
冷靜分析一下,我們都知道iOS內的應用間跳轉,基本都是通過scheme的方式,跳出去如此,要返回也是如此。
花明
先看下支付寶支付:
捕獲支付寶web支付跳轉鏈接如 alipay://alipayclient/?{"requestType":"SafePay","fromAppUrlScheme":"alipays","dataString":"h5_route_token=\"shierRZ25\"&is_h5_route=\"true\""}
發現其中只要將fromAppUrlScheme改為APP內配置的scheme,即可正確跳轉回應用。具體代碼示例如下:
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支付回調應該是服務端提供的一個h5地址,因此支付完成后默認是跳轉到了Safari,在APP內進行的支付,我們要換掉這個回調,變成我們自己的。
大致步驟是:
- 工程文件添加Scheme,內容為[APP本地配置的scheme]
- 捕獲跳轉鏈接 https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb,將其中其中的redirect_url參數換成[APP本地配置的scheme]
- 重新發起請求,給請求頭加上Referer字段,內容為[APP本地配置的scheme]
- 使用openUrl發起微信客戶端調用
這里參考了這篇文章 http://www.lxweimin.com/p/c1973aacc774
需要注意的一點就是,[APP本地配置的scheme]需要是http的URL形式,而且根域名是要包含在微信支付后臺填寫的白名單內的,譬如白名單域名是abc.com,你可以將你的scheme設置為ios.abc.com,否則也不會生效。
具體代碼大致如下:
let wxpayScheme = "ios.abc.com://"
// 去除原有的URL回調地址,換成自己的配置
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 {
// 給請求頭加上Referer字段
let mRequest = NSMutableURLRequest(url: finalUrl)
mRequest.setValue(wxpayScheme, forHTTPHeaderField: "Referer")
decisionHandler(WKNavigationActionPolicy.cancel)
webView.load(mRequest as URLRequest)
return
}
}
}
}
替換的過程有一點繞,其實就是找到相應字段替換掉,有更好地寫法。
嘗試了一下,可以成功跳轉回來了,但是新的問題又出現了→_→
又一村
因為替換了微信支付的回調,h5的跳轉可能會出現白屏的問題,上面的文章也有提到。
我根據自己的實際情況采用了直接強制調用webView.goBack()
,因為本身的H5頁面自帶了支付等待完成頁,支付完成后返回APP,確認一下支付狀態就好了。
需要注意的是,調用微信支付5秒后,webview會收到一個鏈接調整,截獲然后進行后退就好:
if curUrl.absoluteString.hasPrefix(wxpayScheme) {
// 進入空白頁,強制后退
decisionHandler(WKNavigationActionPolicy.cancel)
webView.goBack()
return
}
不過這個白屏問題可能會根據本身h5的不同而采取不同的解決方案,所以這個應該并非萬全之策。