微信H5支付接口
发布于 2021-01-26 10:48
H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。
主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。
微信支付流程:
将参数封装为xml格式。
调用MD5加密算法签名
发送请求
返回中间页面
调起微信支付
支付成功后,回调
生成签名:
1.第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
2.第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
举例:
假设传送的参数如下:
appid:wxd930ea5d5a258f4f
mch_id:10000100
device_info:1000
body:test
nonce_str:ibuaiVcKdpRxkhJA
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下
stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
第二步:拼接API密钥:
MD5签名方式:
stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key为商户平台设置的密钥key
sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5签名方式
<xml>
<appid>wxd930ea5d5a258f4f</appid>
<mch_id>10000100</mch_id>
<device_info>1000</device_info>
<body>test</body>
<nonce_str>ibuaiVcKdpRxkhJA</nonce_str>
<sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>
</xml>
发送完数据后,并不代表调起了微信支付。微信支付相比于支付宝支付,这里多了一个请求。返回的数据是一个xml格式的一堆参数,其中mweu_url是调起微信支付的中间页。
支付成功后,如果如果想要在前端跳转地址,需要在mweu_url中加入跳转地址:
正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面。回调地址需要进行urlencode处理。
微信的回调
官方文档:支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。
注意:
1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
2、后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起多次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。
3、在订单状态不明或者没有收到微信支付结果通知的情况下,建议商户主动调用微信支付【查询订单API】确认订单状态。
特别提醒:
如果不在回调方法中给微信通知,微信会疯狂回调。
支付:
"weixinPayRechargeGold",method = RequestMethod.POST) (value =
public Result weixinPayWap(InfoBean bean,HttpServletRequest request) throws Exception {
Result result=new Result();
result.setCode(200);
result.setMsg("success");
String mweb_url = "";
try {
TreeMap<String, String> paraMap = new TreeMap<>();
paraMap.put("appid", "wx1d84563a6ac34711"); //商户ID
paraMap.put("body", "xxxx"); //商品名称
paraMap.put("mch_id", "1491796222");
paraMap.put("nonce_str", WXPayUtil.generateNonceStr()); //String 随机字符串
String time = String.valueOf(new Date().getTime());
paraMap.put("out_trade_no", bean.getOut_trade_no());//订单号
paraMap.put("spbill_create_ip", getIpAddr(request));//请求IP
paraMap.put("total_fee", bean.getTotal_amount()); //总金额
paraMap.put("trade_type", "MWEB"); //类型
paraMap.put("scene_info", "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"http://m.jnbat.com\",\"wap_name\": \"shangcheng\"}}");
paraMap.put("notify_url",bean.getNotify_url());// 此路径是微信服务器调用支付结果带了一大批参数多次请求
String stringA=SignUtil.map_to_string(paraMap);
String stringSignTemp=stringA+"&key=ggy101600ggy101600ggy101600ggy10";
String sign = SignUtil.MD5(stringSignTemp);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
// 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder
String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
// String xmlStr = sendPost(unifiedorder_url,xml);
String xmlStr = HttpClientUtils.doPostByXml(unifiedorder_url, xml);
//以下内容是返回前端页面的json数据
// String xmlStr2 = HttpClientUtils.doPostString(unifiedorder_url, xml);
System.out.println(xmlStr);
if (xmlStr.indexOf("SUCCESS") != -1) {
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
mweb_url = map.get("mweb_url");
//支付完返回浏览器跳转的地址,如跳到查看订单页面
// String redirect_url = "http://413f2a199c79.ngrok.io/paysuccess.jsp";
// String redirect_urlEncode = URLEncoder.encode(redirect_url,"GBK");//对上面地址urlencode
mweb_url=mweb_url+"&redirect_url="+URLEncoder.encode("http://m.jnbat.com","utf-8");//拼接返回地址
System.out.println(mweb_url);
String s = HttpClientUtils.doPostXml2(mweb_url);
// String s = HttpClientUtils.doGet(mweb_url);
System.out.println(s);
result.setData(s);
// response.sendRedirect(mweb_url);
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
// response.sendRedirect(mweb_url);
// request.getRequestDispatcher(mweb_url).forward(request,response);
return null;
}
回调:
@RequestMapping("weixinRechargeGoldResult")
public void weixinResult(HttpServletRequest request, HttpServletResponse response) {
BufferedReader reader;
try {
reader = request.getReader();
String line = "";
StringBuffer inputString = new StringBuffer();
while ((line = reader.readLine()) != null) {
inputString.append(line);
}
request.getReader().close();
Map<String, String> notifyMap = WXPayUtil.xmlToMap(inputString.toString());
//验签
if (notifyMap.get("return_code").equals("SUCCESS")) {
if (notifyMap.get("result_code").equals("SUCCESS")) {
String out_trade_no = notifyMap.get("out_trade_no"); //商户订单号
String amountpaid = notifyMap.get("total_fee");//实际支付的订单金额:单位 分
BigDecimal amountPay = (new BigDecimal(amountpaid).divide(new BigDecimal("100"))).setScale(2);//将分转换成元-实际支付金额:元
String openid = notifyMap.get("openid"); //如果有需要可以获取
orderService.updateOrderStatus(out_trade_no, "Paid");
}
}
//告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
response.setContentType("text/xml; charset=utf-8");
response.getWriter().write("<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>");
} catch (Exception e) {
e.printStackTrace();
}
}
工具类:
//WXPayUtil在这里就不写了
/**
* 获取用户实际ip
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
//根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { //"***.***.***.***".length() = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
package com.gouwuche.Util;
//在向微信发起请求时,用到的这个类,出现在这里不是很合适。但是也用到了
//等哪天再单独写一篇手写HttpLcient的笔记
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpClientUtils {
public static String doGet(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPostXml2(String url){
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
/* RequestConfig config = RequestConfig.custom()
.setConnectionRequestTimeout(15000) //连接请求超时时间
.setConnectTimeout(60000) //连接服务器超时时间
.setSocketTimeout(60000).build();//设置读取响应数据超时时间
httpPost.setConfig(config);*/
httpPost.addHeader("Content-Type","application/x-www-form-urlencoded ");
httpPost.addHeader("Referer","m.jnbat.com");
String result =null;
try {
CloseableHttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("post error");
}
return result;
}
public static String doPost(String url, Map<String, String> param) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return resultString;
}
/**
* @Description post请求发送xml
* @Param [url, requestDataXml]
* @return java.lang.String
**/
public static String doPostByXml(String url,String requestDataXml){
CloseableHttpClient httpClient=null;
CloseableHttpResponse httpResponse=null;
//创建httpClient连接对象
//httpClient = HttpClients.createDefault();
httpClient = HttpClientUtils.createSSLClientDefault();
//创建post请求连接对象
HttpPost httpPost = new HttpPost(url);
//创建连接请求参数对象,并设置连接参数
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000) //连接服务器主机超时时间
.setConnectionRequestTimeout(60000) //连接请求超时时间
.setSocketTimeout(6000) //设置读取响应数据超时时间
.build();
//为httpPost请求设置参数
httpPost.setConfig(requestConfig);
//将上传参数存放到entity属性中
httpPost.setEntity(new StringEntity(requestDataXml,"UTF-8"));
//添加头信息
httpPost.setHeader("Content-Type","text/xml");
String result="";
try {
//发送请求
httpResponse = httpClient.execute(httpPost);
//获取返回内容
HttpEntity httpEntity = httpResponse.getEntity();
result = EntityUtils.toString(httpEntity, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 创建一个SSL信任所有证书的httpClient对象
*
* @return
*/
public static CloseableHttpClient createSSLClientDefault() {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
public boolean isTrusted(X509Certificate[] chain, String authType){
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return HttpClients.createDefault();
}
}
吐槽:微信的支付api坑比支付宝api多。
调侃:微信支付就是图一热闹,真要支付,害的看支付宝
官方文档地址
https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1
https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7
本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。
相关素材