微信和支付宝的H5支付下单成功后都会返回一个跳转支付的url连接,通过这个连接可以拉起微信或支付宝进行支付操作。
如果直接访问,支付宝会有一个中间的页面,而微信有个麻烦的refresh验证问题;那么是否可以跳过这个步骤直接将微信支付宝拉起进行支付呢?
网上大部分的教程都是让做安卓和IOS的自己去拦截微信和支付宝的地址进行处理。但是对这种内嵌网页,特别是那种直接通过前端HTML代码生成多端的情况,前端的同学就非常不好操作了; 那么这个活就需要后端的同学辛苦哈来解决了(ง •_•)ง。
首先需要知道的是每一个手机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;
需要注意的是使用这种方式就不要再将回跳地址传入了,同时需要自己做个是否支付成功的判断;也就是说在支付成功后我们的页面是不会自动回跳的,因此需要让前端的同学辛苦处理哈;比如弄个支付确认弹窗让用户主动点击,或者是定期去轮询几次订单状态,同时如果不跳支付中间页那么最好把支付按钮暂时禁用了,免得由于网络延迟这些问题导致重复支付的问题。
其实去查看微信支付宝的HTML页面,你会发现它们的就是通过简单的js来直接跳转的
支付宝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\"" };
这个就是上面需要的内容,同样通过正则表达式将inData的值提取出来,然后手动来拼接这个支付的URL Scheme。
下面是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);
// 这个isAndroid值是让前端同学传入的,如果不想区分可以直接就用安卓的这个支付链接;因为苹果端的用这个链接也可以拉起支付宝支付
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了,同时也跳过了微信支付中麻烦的验证问题了。