微信和支付寶的H5支付下單成功后都會返回一個跳轉支付的url連接,通過這個連接可以拉起微信或支付寶進行支付操作。
如果直接訪問,支付寶會有一個中間的頁面,而微信有個麻煩的refresh驗證問題;那么是否可以跳過這個步驟直接將微信支付寶拉起進行支付呢?
網上大部分的教程都是讓做安卓和IOS的自己去攔截微信和支付寶的地址進行處理。但是對這種內嵌網頁,前端的同學就非常不好操作了;那么這個活就需要后端的同學辛苦哈來解決了(ง •_•)ง。
首先需要知道的是每一個手機APP都有一個唯一的URL Scheme地址,訪問這個地址即可將對應的APP打開。
基於這個原理,那么微信和支付寶的支付最終肯定也是基於此來實現將其APP拉起然后讓用戶進行支付的。
因此讓后端對支付地址處理下,直接返回可以拉起微信和支付寶的支付URL Scheme;這樣就可以直接用了,微信的refresh驗證也可以跳過了。
首先是微信H5支付
通過程序直接請求微信H5支付下單返回的支付鏈接,返回如下(下面是返回的部分html代碼):
在Html代碼中有以weixin://
開頭的鏈接;而weixin://
正好是微信的URL Scheme,這個就是之后調用微信支付的鏈接,在手機瀏覽器上打開這個鏈接正好可以調起微信支付進行支付。
說明微信在這個頁面上並沒有做其他的騷操作,那些Referer攔截只是一些簡單的前台攔截。那么我們通過后端程序直接去請求微信返回H5支付鏈接,然后將返回的HTML中的微信支付URL Scheme提取出來直接返回給前端即可。
下面是Java示例代碼
HttpHeaders headers = new HttpHeaders(); headers.add("Host", "wx.tenpay.com"); headers.add("Accept-Language", "en, zh-CN; q=0.8,zh; q=0.6,en-US; q=0.4"); headers.add("Accept", "text/html,application/xhtml+xml, application/xml ; g=0. 9 ,image/webp,*/* ; q=0.8"); headers.add("Upgrade-Insecure-Requests", "1"); // 這個地方寫你自己在微信支付后台配置的安全域名 headers.add("Referer", "https://www.baidu.com"); HttpEntity<String> httpEntity = new HttpEntity<>(headers); try{ // 使用spring的 RestTemplate; mweb_url是微信的H5支付鏈接 ResponseEntity<String> exchange = this.restTemplate.exchange(mweb_url, HttpMethod.GET, httpEntity, String.class); String body = exchange.getBody(); if(StringUtils.isBlank(body)){ System.out.println("請求無響應"); return url; } // 通過正則表達式提取需要的字符串 String pattern= "\"weixin(.*?)\""; Pattern p = Pattern.compile(pattern); Matcher matcher = p.matcher(body); if(matcher.find()){ String pullUrl = matcher.group(); return pullUrl.substring(1, pullUrl.length()-1); } }catch (Exception e){ System.out.println("請求異常"); } return url;
需要注意的是使用這種方式就不要再將會回跳地址傳入了,同時需要自己做個是否支付成功的判斷。
支付寶H5支付
基於剛才微信的思路,使用同樣的方式來處理支付寶的。支付寶返回的HTML內容中沒有現成的支付寶支付的URL Scheme。通過調試和HTML代碼分析,提取出其支付URL Scheme如下:
# 安卓的(實際測試中,蘋果手機使用這個也可以拉起支付寶) alipays://platformapi/startApp?appId=102564&orderSuffix=' + o.android + '#Intent;scheme=alipays;package=com.eg.android.AlipayGphone;end # 蘋果的 alipay://alipayclient/?o.ios
其中o.ios和o.android的內容是使用url encoder編碼了的;其中蘋果的內容是如下的JSON串:
{
"requestType": "SafePay", "fromAppUrlScheme": "alipays", "dataString": "h5_route_token=\"FPwoiehfPAWuiofw\"&is_h5_route=\"true\"&need_invoke_app=\"true\"" }
安卓的只有一個dataString的值。通過字段的值對比h5_route_token
其值就是HTML中的session的值;在返回的HTML代碼中有如下代碼:
var inData = { "requestType": "SafePay", "fromAppUrlScheme": "alipays", "dataString": "h5_route_token=\"FPwoiehfPAWuiofw\"&is_h5_route=\"true\"&need_invoke_app=\"true\"" };
這個就是上面需要的內容,同樣通過正則表達式將內容提取出來。
下面是Java示例代碼:
HttpGet httpGet = new HttpGet(h5Url); httpGet.setConfig(RequestConfig.custom() .setConnectTimeout(HttpConstants.CONNECT_TIMEOUT) .setConnectionRequestTimeout(HttpConstants.CONNECTION_REQUEST_TIMEOUT) .setSocketTimeout(HttpConstants.SOCKET_TIMEOUT).build()); CloseableHttpClient httpClient = HttpClientBuilder.create().build(); try (CloseableHttpResponse response = httpClient.execute(httpGet)) { if(response.getEntity()!=null){ String body = EntityUtils.toString(response.getEntity(), "UTF-8"); // 通過正則表達式提取需要的字符串;也可以直接提取session的值 `pattern = "'session':(.*)'";` String pattern= "inData =(.*)"; Pattern p = Pattern.compile(pattern); Matcher matcher = p.matcher(body); if(matcher.find()){ String pullUrl = matcher.group(); if(pullUrl.length()>9){ pullUrl = pullUrl.substring(9, pullUrl.length()-1); if(isAndroid){ JSONObject params = JSONObject.parseObject(pullUrl); if(params.getString("dataString")!=null){ pullUrl = params.getString("dataString"); // 安卓 return String.format("alipays://platformapi/startApp?appId=549984&orderSuffix=%s#Intent;scheme=alipays;package=com.eg.android.AlipayGphone;end", URLEncoder.encode(pullUrl, "utf-8")); } }else { // iso return String.format("alipay://alipayclient/?%s", URLEncoder.encode(pullUrl.trim(), "utf-8")); } } } System.out.println("請求返回內容:"+ body); }else { System.out.println("無請求內容返回"); } } catch (IOException e) { System.out.println("處理異常"); }finally { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } return null;
這種方式對原生的APP開發應該也適用,即不使用微信支付寶的APP支付方式,直接使用H5的支付方式,這樣就無需再去對接其APP支付的SDK了。