前期准备条件
1.企业号 https://work.weixin.qq.com/
2.已开通支付功能的微信商户号 https://pay.weixin.qq.com/
一、获取支付证书apiclient_cert.p12下载
微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全
(放置到web访问无法下载的地方,防止证书下载泄露)
二、企业台开通微信支付
企业管理后台-->应用管理-->企业支付-->绑定商户平台商户号
三、发放企业红包
官方文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90274
发送红包接口
请求方式:POST(HTTPS)
请求地址:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendworkwxredpack
是否需要证书:是
数据格式:xml
部分容易混淆参数说明:
商户号 微信商户平台-->账户中心-->商户信息-->商户号
公众账号appid 企业微信后台-->我的企业-->企业信息-->企业ID
用户openid 使用企业用户的userId转为openId 官方文档:http://work.weixin.qq.com/api/doc#11279
微信支付签名算法
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
参数名ASCII码从小到大排序(字典序);如果参数的值为空不参与签名;参数名区分大小写;验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
附:key为商户平台API密钥里面设置的key,key设置之后不能查看,建议设置后另外保存一份,以免遗忘。另外,再建议将key顺便保存到 商户平台->产品中心->企业微信收款->API密钥管理 里面,这样后续企业微信收款才能正常使用。
签名字段:
除sign字段外所有字段都参与签名(包括企业微信签名字段workwx_sign一起参与签名).
企业微信签名算法
第一步: 设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
注意:
参数名ASCII码从小到大排序(字典序)如果参数的值为空不参与签名参数名区分大小写传送的sign参数不参与签名,将生成的签名与该sign值作校验
第二步: 在stringA最后拼接上企业微信支付应用secret(参见企业微信管理端支付应用页面的secret),得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
企业微信签名字段说明:
发红包api固定如下几个字段参与签名:
act_name
mch_billno
mch_id
nonce_str
re_openid
total_amount
wxappid
第一步: 对参数按照key=value的格式,并按照参数名ASCII字典序排序如下
stringA=”act_name=XXX&mch_billno=11111234567890&mch_id=10000098&nonce_str=qFKEgfig76DF9912fewmkp&re_openid=oxTWIuGaIt6gTKsQRLau2M0yL16E&total_amount=100&wxappid=wx12345678
第二步:拼接企业微信支付应用secret(参见企业微信管理端支付应用页面)
stringSignTemp=”stringA&secret=192006250b4c09247ec02edce69f6a2d”
sign=MD5(stringSignTemp).toUpperCase()
/** * 发送红包请求
* @param openId 用户openid
* @param mchBillNo 订单号
* @param totalAmount 发送金额(单位:分)
* @param wishing 祝福语
* @param actName 项目名称(在微信端不显示)
* @param senderName 发送人姓名
* @return
*/
public
Boolean sendWorkWxRedPack(String openId,String mchBillNo,
int
totalAmount,String wishing,String actName,String senderName)
throws
Exception
Map<String,String> paramMap =
new
TreeMap<>();
paramMap.put(
"act_name"
,actName);
//项目名称
paramMap.put(
"mch_billno"
,mchBillNo);
paramMap.put(
"mch_id"
,mchId);
paramMap.put(
"nonce_str"
,WXPayUtil.generateNonceStr());
paramMap.put(
"re_openid"
,openId);
//openId
paramMap.put(
"total_amount"
,String.valueOf(totalAmount));
//金额,单位分
paramMap.put(
"wxappid"
,corpId);
String workWxSign = gerCompanySign(paramMap);
//生成企业签名
paramMap.put(
"workwx_sign"
,workWxSign);
paramMap.put(
"wishing"
,wishing);
//红包祝福语
paramMap.put(
"remark"
,
"星宏"
);
paramMap.put(
"sender_name"
,senderName);
paramMap.put(
"sender_header_media_id"
,
"1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0"
);
paramMap.put(
"scene_id"
,
"PRODUCT_4"
);
String paySign = gerPaySign(paramMap);
//生成微信支付签名
paramMap.put(
"sign"
,paySign);
String xmlBody = WXPayUtil.mapToXml(paramMap);
String request = requestOnce(CP_SEND_WORK_WX_REDPACK,xmlBody,
8
*
1000
,
8
*
1000
,
true
);
Map<String,String> retMap = WXPayUtil.xmlToMap(request);
if
(retMap.containsKey(
"result_code"
)&&
"SUCCESS"
.equals(retMap.get(
"result_code"
)))
log.debug(
"微信发送参数"
+xmlBody);
log.debug(
"##############发送红包成功:"
+request);
return
true
;
}
else
{
log.error(
"微信发送参数"
+xmlBody);
log.error(
"###############发送红包失败,返回原因:"
+request);
return
false
;
}
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
private
static
String generateSignature(
final
Map<String, String> data, String key,String type)
throws
Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(
new
String[keySet.size()]);
Arrays.sort(keyArray);
StringBuffer sb =
new
StringBuffer();
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(
"&"
);
}
if
(
"companyPaySecret"
.equals(type)){
sb.append(
"secret="
).append(key);
log.debug(
"企业拼接结果"
+sb.toString());
}
if
(
"paySecret"
.equals(type)){
sb.append(
"key="
).append(key);
log.debug(
"微信支付拼接结果"
+sb.toString());
}
String sign = SecureUtil.md5(sb.toString()).toUpperCase();
log.debug(
"生成:"
+sign);
return
sign;
}
/**
* 发送https请求
* @param url 请求地址
* @param data 请求体
* @param connectTimeoutMs 连接超时时间
* @param readTimeoutMs 读取超时时间
* @param useCert 是否使用证书,针对退款、撤销等操作
*/
private
String requestOnce(String url, String data,
int
connectTimeoutMs,
int
readTimeoutMs,
boolean
useCert)
throws
Exception {
try
( InputStream certStream =
this
.getClass().getClassLoader().getResourceAsStream(
"apiclient_cert.p12"
)){
BasicHttpClientConnectionManager connManager;
if
(useCert) {
// 证书
char
[] password = mchId.toCharArray();
KeyStore ks = KeyStore.getInstance(
"PKCS12"
);
ks.load(certStream, password);
// 实例化密钥库 & 初始化密钥工厂
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password);
// 创建 SSLContext
SSLContext sslContext = SSLContext.getInstance(
"TLS"
);
sslContext.init(kmf.getKeyManagers(),
null
,
new
SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory =
new
SSLConnectionSocketFactory(sslContext,
new
String[]{
"TLSv1"
},
null
,
new
DefaultHostnameVerifier());
connManager =
new
BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register(
"http"
, PlainConnectionSocketFactory.getSocketFactory())
.register(
"https"
, sslConnectionSocketFactory)
.build(),
null
,
null
,
null
);
}
else
{
connManager =
new
BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register(
"http"
, PlainConnectionSocketFactory.getSocketFactory())
.register(
"https"
, SSLConnectionSocketFactory.getSocketFactory())
.build(),
null
,
null
,
null
);
}
HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
HttpPost httpPost =
new
HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity =
new
StringEntity(data,
"UTF-8"
)
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return
EntityUtils.toString(httpEntity,
"UTF-8"
);
}