微信支付、微信企业付款到零钱工具类
主要依赖:apache httpclient 4.5 jdk1.8
工具类主要功能:
- xml参数拼接
- 签名算法实现
- post加密请求
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
@Slf4j
public class WxPayHelper {
/**
* 企业付款到零钱接口URL.
*/
private static final String TRANS_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
/**
* 微信支付签名算法sign
* @param parameters 参数集合
* @return 返回md5签名
*/
public static String createSign(String signKey,SortedMap<Object,Object> parameters) {
StringBuilder sb = new StringBuilder(); // 多线程访问的情况下需要用StringBuffer
Set es = parameters.keySet(); // 所有参与传参的key按照accsii排序(升序)
for (Object set : es) {
String k = set.toString();
Object v = parameters.get(k);
sb.append(k)
.append("=")
.append(v.toString())
.append("&");
}
sb.append("key=").append(signKey);
return str2MD5(sb.toString(), "utf-8").toUpperCase();
}
/**
* MD5加密
* @param data 要加密的数据
* @param encode 加密的编码
* @return md5字符串
*/
public static String str2MD5(String data, String encode) {
String resultString = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
if (encode == null || "".equals(encode))
resultString = byteArrayToHexString(md.digest(data
.getBytes()));
else {
resultString = byteArrayToHexString(md.digest(data
.getBytes(encode)));
}
} catch (Exception exception) {
}
return resultString;
}
/**
* byte数组转换16进制字符串
* @param b 要转换的byte数组
* @return 16进制字符串
*/
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
/**
* byte转换成16进制字符串
* @param b 要转换的byte
* @return byte对应的16进制字符串
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
/**
* map集合参数转换为xml格式
* @param dataMap 要转换的map对象
* @return XML格式的字符串
*/
public static String map2XML(SortedMap<Object, Object> dataMap) {
StringBuilder strBuilder = new StringBuilder();
Set<Object> objSet = dataMap.keySet();
strBuilder.append("<xml>\n");
for (Object key : objSet)
{
if (key == null)
{
continue;
}
Object value = dataMap.get(key);
strBuilder.append("<")
.append(key.toString())
.append(">")
.append(value)
.append("</")
.append(key.toString())
.append(">\n");
}
strBuilder.append("</xml>");
return strBuilder.toString();
}
/** 生成随机数.
* @param count 要生成的随机数位数
* @return 随机数字符串
*/
private String createNonceStr(int count){
String[] nums = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
int maxIndex = nums.length - 1;
int numIndex;
StringBuilder builder = new StringBuilder(count);
for (int i = 0; i < count; i++){
numIndex = (int)(Math.random() * maxIndex);
builder.append(nums[numIndex]);
}
return builder.toString();
}
/**
* 生成签名和xml参数的main方法测试.
* see https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3
* @param args
*/
public static void main(String[] args) {
String key = "192006250b4c09247ec02edce69f6a2d";
SortedMap<Object, Object> map = new TreeMap<>();
map.put("appid","wxd930ea5d5a258f4f");
map.put("mch_id","10000100");
map.put("device_info","1000");
map.put("body","test");
map.put("nonce_str","ibuaiVcKdpRxkhJA"); // 这里为了跟文档的结果对应,直接使用文档的随机数
String sign = WxPayHelper.createSign(key, map);
map.put("sign",sign);
String xmlParam = WxPayHelper.map2XML(map);
System.out.println(xmlParam);
}
/
/**
* 以下是http请求相关接口方法.
*/
/
/**
* 加载证书.
* @param certPath 证书文件绝对路径
* @param mchid 商户id
*/
private static KeyStore initCert(String certPath, String mchid) {
KeyStore keyStore = null;
// 加载本地的证书进行https加密传输
FileInputStream instream = null;
try {
keyStore = KeyStore.getInstance("PKCS12");
instream = new FileInputStream(new File(certPath));
keyStore.load(instream, mchid.toCharArray()); // 加载证书密码,默认为商户ID
} catch (KeyStoreException e) {
log.error(e.getMessage(), e);
} catch (FileNotFoundException e) {
log.error(e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
log.error(e.getMessage(), e);
} catch (CertificateException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
if (instream!=null) {
try {
instream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
return keyStore;
}
/**
*
* 创建CloseableHttpClient.
* @param keyStore
* @param keyPassword
* @return
*/
private static CloseableHttpClient createHttpClient(KeyStore keyStore, char[] keyPassword) {
// Trust own CA and all self-signed certs
SSLContext sslcontext = null;
try {
sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, keyPassword).build();
} catch (NoSuchAlgorithmException e) {
log.error(e.getMessage(), e);
} catch (KeyManagementException e) {
log.error(e.getMessage(), e);
} catch (KeyStoreException e) {
log.error(e.getMessage(), e);
} catch (UnrecoverableKeyException e) {
log.error(e.getMessage(), e);
}
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] {"TLSv1"},
null,
new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
// 微信商户平台的支付接口验证直接通过
if ("api.mch.weixin.qq.com".equals(hostname)) {
return true;
} else {
HostnameVerifier hv = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
return hv.verify(hostname, sslSession);
}
}
});
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
// 根据默认超时限制初始化requestConfig
return httpClient;
}
/**
* post传输xml数据.
* @param url 传输url
* @param xmlObj xml参数
* @param mchid 商户id
* @param certPath 证书文件的绝对路径
* @return
*/
public static String post(String url, String xmlObj, String mchid, String certPath) {
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(10000).build();
// 加载证书
KeyStore keyStore = initCert(certPath, mchid);
CloseableHttpClient httpClient = createHttpClient(keyStore, mchid.toCharArray());
String result = null;
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
String defaultCharset = "UTF-8";
StringEntity postEntity = new StringEntity(xmlObj, defaultCharset);
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
// 设置请求器的配置
httpPost.setConfig(requestConfig);
try {
HttpResponse res = httpClient.execute(httpPost);
if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = res.getEntity();
result = EntityUtils.toString(entity, defaultCharset);
}
} catch (ConnectionPoolTimeoutException e) {
log.error(e.getMessage(), e);
} catch (ConnectTimeoutException e) {
log.error(e.getMessage(), e);
} catch (SocketTimeoutException e) {
log.error(e.getMessage(), e);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
httpPost.abort();
}
return result;
}
/**
* 发送请求.
* @param key 密钥
* @param certPath API证书证书apiclient_cert.p12
* @param model 企业付款到零钱接口所需参数
* @return String
*/
public static String doTransfers(String key, String certPath, WxMchPayB2c model)
throws IllegalAccessException {
SortedMap<Object, Object> map = new TreeMap<>();
Field[] declaredFields = model.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
String k = field.getName();
Object v = field.get(model);
if (v != null) {
map.put(k, v);
}
}
String sign = WxPayHelper.createSign(key, map);
map.put("sign",sign);
String xmlParam = WxPayHelper.map2XML(map);
String result = post(TRANS_URL, xmlParam, model.getMchid(), certPath);
return result;
}
}
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @Desc https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
*/
@Data
@ApiModel(value="net.mondelez.scan.microservice.entity.vo.WxMchPayB2c", description = "微信商户平台-微信支付-企业付款到零钱")
public class WxMchPayB2c {
@ApiModelProperty(value="申请商户号的appid或商户号绑定的appid", required=true)
private String mch_appid;
@ApiModelProperty(value="微信支付分配的商户号", required=true)
private String mchid;
@ApiModelProperty(value="微信支付分配的终端设备号", required=false)
private String device_info;
@ApiModelProperty(value="随机字符串,不长于32位", required=true)
private String nonce_str;
@ApiModelProperty(value="签名,签名算法:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3", required=true)
private String sign;
@ApiModelProperty(value="商户订单号,需保持唯一性(只能是字母或者数字,不能包含有其它字符)", required=true)
private String partner_trade_no;
@ApiModelProperty(value="商户appid下,某用户的openid", required=true)
private String openid;
@ApiModelProperty(value="校验用户姓名选项,NO_CHECK:不校验真实姓名,FORCE_CHECK:强校验真实姓名", required=true)
private String check_name="NO_CHECK";
@ApiModelProperty(value="收款用户真实姓名,如果check_name设置为FORCE_CHECK,则必填用户真实姓名,如需电子回单,需要传入收款用户姓名", required=false)
private String re_user_name;
@ApiModelProperty(value="企业付款金额,单位为分", required=true)
private Integer amount;
@ApiModelProperty(value="企业付款备注,必填。注意:备注中的敏感词会被转成字符*", required=true)
private String desc;
@ApiModelProperty(value="Ip地址,该IP同在商户平台设置的IP白名单中的IP没有关联,该IP可传用户端或者服务端的IP", required=false)
private String spbill_create_ip;
}
参考:
https://blog.csdn.net/u013564742/article/details/88648885
https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3
https://kf.qq.com/faq/140225MveaUz150107ERniAN.html
https://www.jianshu.com/p/a361ed7c6691
https://www.jianshu.com/p/ad4c7ce94518