官方文档
微信官方提供的SDK和demo,下载地址
springboot整合微信支付(完整)
springboot 使用 RestTemplate 携带 微信API证书发送请求实现企业付款到零钱/提现/微信退款等场景
这个是别人把官方的SDK放到了Maven仓库里,也可以使用
<dependency>
<groupId>com.github.tedzhdz</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>3.0.10</version>
</dependency>
第一步:把官方提供的SDK复制到自己的项目中
这个时候,里面有地方会缺少依赖,我们需要引入对应的依赖。一般就缺少httpclient依赖,
<!--微信支付请求里需要用到的依赖,是http请求相关的-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
第二步,根据要求生成XML的请求体参数
1.定义一个JAVA实体类
package com.tj.qywx.domain;
import lombok.Data;
import java.io.Serializable;
/**
* 向员工付款的XML数据字段
*/
@Data
public class WxPayManXml implements Serializable {
//商户订单号
private String partner_trade_no;
//用户openid
private String userid;
//金额
private Integer amount;
//向员工付款说明信息。必填
private String desc;
//审批编号
private String approval_number;
//项目名称
private String act_name;
private static final long serialVersionUID = 1L;
}
2、定义map填入请求需要的数据
//1.定义Map
String mchId = baseNumvarService.getMchId();
HashMap<String, String> xmlMap = MapUtils.newHashMap();
xmlMap.put("appid", baseNumvarService.getCorpid());
xmlMap.put("mch_id", mchId);
xmlMap.put("nonce_str", WXPayUtil.generateNonceStr());
xmlMap.put("partner_trade_no", wxPayManXml.getPartner_trade_no());
xmlMap.put("openid", wxUserlistService.getOpenidByUserid(wxPayManXml.getUserid()));
xmlMap.put("amount", String.valueOf(wxPayManXml.getAmount()));
xmlMap.put("desc", wxPayManXml.getDesc());
xmlMap.put("ww_msg_type", "APPROVAL_MSG");
//进行企业微信签名(不是所有字段都需要参与签名,根据文档要求)
log.info("企业微信签名的原始数据:{}", xmlMap);
//微信官方工具默认最后拼接的是“key”,但是企业微信签名算法最后需要拼接的是“secret”,这个bug有点坑,这个地方自己要特殊处理一下
String workwxSign = MyWXPayUtils.getSignByMD5(xmlMap, baseNumvarService.getSecretPay(), "secret");
xmlMap.put("workwx_sign", workwxSign);
xmlMap.put("act_name", wxPayManXml.getAct_name());
xmlMap.put("approval_number", wxPayManXml.getApproval_number());
xmlMap.put("spbill_create_ip", "192.168.0.1");
//进行微信支付签名
log.info("微信支付签名的原始数据:{}", xmlMap);
String sign = WXPayUtil.generateSignature(xmlMap, baseNumvarService.getAPIv2());
xmlMap.put("sign", sign);
处理签名的方法(微信官方提供的sdk里面有些小坑,要注意)
/**
* 签名算法,
* 第一步: 对参数按照key=value的格式,并按照参数名ASCII字典序排序如下
* 第二步:拼接企业微信支付应用secret(参见企业微信管理端支付应用页面):
*
* @param data
* @param keyValue
* @param key
* @return
*/
public static String getSignByMD5(final Map<String, String> data, String keyValue, String key) {
if (key == null) {
key = "key";
}
//排序
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append(key).append("=").append(keyValue);
System.out.println("微信签名组合sb = " + sb);
//md5加密转大写
return DigestUtils.md5DigestAsHex(sb.toString().getBytes()).toUpperCase();
}
3、注意,这个里面WXPayUtil是官方提供的SDK里工具类,
//进行企业微信签名(不是所有字段都需要参与签名,根据文档要求)
log.info("企业微信签名的原始数据:{}", xmlMap);
//微信官方工具默认最后拼接的是“key”,但是企业微信签名算法最后需要拼接的是“secret”,这个bug有点坑,这个地方自己要特殊处理一下
String workwxSign = MyWXPayUtils.getSignByMD5(xmlMap, baseNumvarService.getSecretPay(), "secret");
xmlMap.put("workwx_sign", workwxSign);
//进行微信支付签名
log.info("微信支付签名的原始数据:{}", xmlMap);
String sign = WXPayUtil.generateSignature(xmlMap, baseNumvarService.getAPIv2());
xmlMap.put("sign", sign);
4、把Map转成XML字符串,使用的还是微信官方提供的工具类
//2.Map转成XML
String PostXml = WXPayUtil.mapToXml(xmlMap);
5、执行请求,再吧请求结果转换成map,方便使用
//3.请求地址
String url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/paywwsptrans2pocket";
// 执行请求
String xmlString = MyWXPayUtils.requestWithCert(mchId, this.path, url, PostXml);
log.info("付款后返回的Xml信息:{}", xmlString);
//4.xml转map
Map<String, String> resultMap = WXPayUtil.xmlToMap(xmlString);
log.info("付款后返回的转换后的map信息:{}", resultMap);
附录:
http请求封装
/**
* 微信小程序退款订单(需要双向证书)--微信支付需要双向证书
* @param mchId 商户号
* @param certPath 证书文件夹路径,全路径
* @param url 请求地址
* @param data xml请求参数数据
* @return
* @throws Exception
*/
public static String requestWithCert(String mchId, String certPath, String url, String data) throws Exception {
//小程序退款需要调用双向证书的认证
CloseableHttpClient httpClient = readCertificate(mchId, certPath);
try {
HttpPost httpost = new HttpPost(url); // 设置响应头信息
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
httpClient.close();
}
}
读取P12双向证书
/**
* 读取p12双向证书
*
* @param mchId 商户号,主要作为密码使用
* @param certPath 证书文件位置
* @return
* @throws Exception
*/
@PostConstruct
private static CloseableHttpClient readCertificate(String mchId, String certPath) throws Exception {
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
String filepath = certPath;
System.out.println("filepath->" + filepath);
FileInputStream instream = new FileInputStream(filepath + "apiclient_cert.p12");
try {
keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID(商户号)
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();//这里也是写密码的
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
}