微信官方文档:发放红包接口
代码前的准备:
1.开通现金红包功能
◆ 根据监管要求,新申请商户号使用现金红包需要满足两个条件:1、入驻时间超过90天 2、连续正常交易30天。
2.拿到用户的openid
3.登录微信商户平台拿到:商户号+商户秘钥+商户证书
4.随机字符串
/**
* 随机字符串的生成
*
* @return
*/
public static String getNonce_str() {
return UUID.randomUUID().toString().replace("-", "").toUpperCase();
}
5.生成商户订单号:商户号+时间戳+4位随机数 我的商户号10位 时间戳 14 加上4位随机数 刚好符合28位
//商户订单号默认用商户号+时间戳+4位随机数
String mchBillno = model.getMchId()
+ new SimpleDateFormat("yyyyMMddHHmmss").format(date)
+ (int) ((Math.random() * 9 + 1) * 1000);
因为 Math.random() 是随机生成0.0~1.0 前闭后开的随机数,为了保证是4位数,所以(Math.random() * 9 + 1) * 1000。
6.签名 签名算法
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
- ◆ 参数名ASCII码从小到大排序(字典序);
- ◆ 如果参数的值为空不参与签名;
- ◆ 参数名区分大小写;
- ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
- ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
以上是官方给出的步骤,然后我们一步一步来
6.1 设所有发送或者接收到的数据为集合M 这里最好单独写一个方法装参数集合,因为后面的签名步骤是一样的,调用查询接口,企业付款到零钱等需要签名的接口用得上。
/**
* 签名算法
* 算法说明:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3
* @param model
* @return
*/
private static String getSign(TransfersDto model) {
Map<String, Object> paramMap = new HashMap<String, Object>();
//随机字符串
paramMap.put("nonce_str", model.getNonceStr());
//商户订单号
paramMap.put("mch_billno", model.getMchBillno());
//商户号
paramMap.put("mch_id", model.getMchId());
//公众账号appid
paramMap.put("wxappid", model.getWxAppId());
//商户名称
paramMap.put("send_name", model.getSendName());
//用户openid
paramMap.put("re_openid", model.getReOpenId());
//付款金额
paramMap.put("total_amount", model.getTotalAmount());
//红包发放总人数
paramMap.put("total_num", model.getTotalNum());
//红包祝福语
paramMap.put("wishing", model.getWishing());
//Ip地址
paramMap.put("client_ip", model.getClientIp());
//活动名称
paramMap.put("act_name", model.getActName());
//备注
paramMap.put("remark", model.getRemark());
//场景id 发放红包使用场景,红包金额大于200或者小于1元时必传,以外非必须。
paramMap.put("scene_id", model.getSceneId());
//活动信息 非必须参数。
paramMap.put("risk_info", model.getRiskInfo());
return WXUtil.getSign(paramMap,KEY);
}
6.2 传入需要进行的签名的参数,注意官网对于签名的要求,都要满足,然后拿到签名。(此处用了商户秘钥)+md5对于参数进行了加密,保证数据安全。
/**
* 签名算法
* 算法说明:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3
* @param paramMap
* @return
*/
public static String getSign( Map<String, Object> paramMap,String key) {
List<String> keys = new ArrayList(paramMap.keySet());
//参数名ASCII码从小到大排序(字典序)
Collections.sort(keys);
String sortString = "";
for (int i = 0; i < keys.size(); i++ )
{
String mapKey = keys.get(i);
Object value = paramMap.get(mapKey);
//参数的值为空不参与签名
if (value != null) {
sortString = sortString + mapKey + "=" + value + "&";
}
}
//去掉最后一个 "&" 符号
sortString = sortString.substring(0, sortString.length() -1);
//拼接API密钥
String mysign = sortString + "&key=" + key;
//进行MD5运算,再将得到的字符串所有字符转换为大写
String sign = DigestUtils.md5Hex(mysign).toUpperCase();
return sign;
}
7.封装请求参数,下面的参数中上面介绍没有用到的属性,就是需要你手动设置的,如多少钱啊,openid,商户号,红包祝福语等等。
//封装请求参数
StringBuilder reqXmlStr = new StringBuilder();
reqXmlStr.append("<xml>");
reqXmlStr.append("<sign><![CDATA[" + model.getSign() + "]]></sign>");
reqXmlStr.append("<mch_billno><![CDATA[" + model.getMchBillno() + "]]></mch_billno>");
reqXmlStr.append("<mch_id><![CDATA[" + model.getMchId() + "]]></mch_id>");
reqXmlStr.append("<wxappid><![CDATA[" + model.getWxAppId() + "]]></wxappid>");
reqXmlStr.append("<send_name><![CDATA[" + model.getSendName() + "]]></send_name>");
reqXmlStr.append("<re_openid><![CDATA[" + model.getReOpenId() + "]]></re_openid>");
reqXmlStr.append("<total_amount><![CDATA[" + model.getTotalAmount() + "]]></total_amount>");
reqXmlStr.append("<total_num><![CDATA[" + model.getTotalNum() + "]]></total_num>");
reqXmlStr.append("<wishing><![CDATA[" + model.getWishing() + "]]></wishing>");
reqXmlStr.append("<client_ip><![CDATA[" + model.getClientIp() + "]]></client_ip>");
reqXmlStr.append("<act_name><![CDATA[" + model.getActName() + "]]></act_name>");
reqXmlStr.append("<remark><![CDATA[" + model.getRemark() + "]]></remark>");
reqXmlStr.append("<nonce_str><![CDATA[" + model.getNonceStr() + "]]></nonce_str>");
if (model.getNonceStr() != null) {
reqXmlStr.append("<scene_id><![CDATA[" + model.getSceneId() + "]]></scene_id>");
}
if (model.getRiskInfo() != null) {
reqXmlStr.append("<risk_info>" + model.getRiskInfo() + "</risk_info>");
}
reqXmlStr.append("</xml>");
8.加载证书 证书的位置在测试时可以放在本地,路径给绝对路径,如果要正式使用前面的商户秘钥+商户证书 都是不能直接暴露在代码里面的,这涉及商户号金额,权限等操作,十分重要,十分重要,十分重要,切记找个安全的地方。
/**
* 加载证书
*
* @throws KeyStoreException
* @throws IOException
* @throws CertificateException
* @throws NoSuchAlgorithmException
* @throws UnrecoverableKeyException
* @throws KeyManagementException
*/
private static void loadCertificate(String mchId, String certificatePath) throws KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// 读取证书
FileInputStream fileInputStream = new FileInputStream(new File("apiclient_cert.p12"));
try {
// 加载证书密码,默认为商户ID
keyStore.load(fileInputStream, mchId.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
fileInputStream.close();
}
//信任自签名证书
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore,
mchId.toCharArray()).build();
SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslcontext,
new String[] {"TLSv1"}, null,new DefaultHostnameVerifier());
httpClient = HttpClients.custom().setSSLSocketFactory(sslcsf).build();
}
9.请求接口
TRANS_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
reqXmlStr:步骤7封装好的请求数据
HttpPost httpPost = new HttpPost(transUrl);
try {
// 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
StringEntity postEntity = new StringEntity(reqXmlStr, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
//根初始化requestConfig 连接超时时间,默认10秒 传输超时时间,默认30秒
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(30000).build();
httpPost.setConfig(requestConfig);
HttpResponse response = httpClient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpPost.abort();
}
10.返回的结果是string型的xml数据
怎么把xml文件的内容拿出来存储到数据库,请移步这里:怎么把微信红包返回的xml字符串值取出来