今天公司打算做一个活动,就是可以让用户领取平台发送的红包,根据微信官方文档实现微信企业付款到零钱(因为商户号不满足一些条件无法使用红包,红包跟零钱实现方法基本一样),然后又加入了一些简单的红包算法。微信官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1
红包发送的规则:总共有100个红包,总金额100元,最小红包0.05,最大红包3,也就是说100元,发100次基本上能领完。红包领取的规则:在移动端页面上展示红包,用户领取的时候,调用后台接口领取红包(一些领取规则可以自己添加)。
零钱(红包)发放接口如下
@ResponseBody
@RequestMapping("wxSendWallet")
public void wxSendWallet(String openid) {
//微信金额的单位是分 所以这里要*100
float money = getRedPack();
BigDecimal df = new BigDecimal(money+"");
df = df.multiply(new BigDecimal("100"));
int fee = df.intValue();
//创建一个唯一订单号
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String time = sdf.format(new Date());
String orderId = time + (int)(Math.random()*1000000);
String xml = WeixinCore.wxSendWallet(orderId,openid,String.valueOf(fee));
try {
//指定读取证书格式为PKCS12
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//windows系统
//FileInputStream instream = new FileInputStream(new File("D:\\cert\\apiclient_cert.p12"));
//linux系统,读取本机存放的PKCS12证书文件
FileInputStream instream = new FileInputStream(new File("/alidata/opt/paycert/apiclient_cert.p12"));
try {
//指定PKCS12的密码(商户ID)
//keyStore.load(instream, accountUtil.getWxPartnerId().toCharArray());
keyStore.load(instream, "微信商户号".toCharArray());
}finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, "微信商户号".toCharArray()).build();
//指定TLS版本, Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
//设置httpclient的SSLSocketFactory
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers");
//这里要设置编码,不然xml中有中文的话会提示签名失败或者展示乱码
httppost.addHeader("Content-Type", "text/xml");
StringEntity se = new StringEntity(xml,"UTF-8");
httppost.setEntity(se);
CloseableHttpResponse responseEntry = httpclient.execute(httppost);
try {
HttpEntity entity = responseEntry.getEntity();
if (entity != null) {
System.out.println("响应内容长度 : "+ entity.getContentLength());
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(entity.getContent());
Element rootElt = document.getRootElement();
String resultCode = rootElt.elementText("result_code");
if(resultCode.equals("SUCCESS")){
//保存红包信息到数据库
//保存用户领取记录
}else{
System.out.println(rootElt.elementText("err_code_des"));
}
}
EntityUtils.consume(entity);
}catch(Exception e){
System.out.println("请求失败");
}
finally {
responseEntry.close();
}
}catch(Exception e){
System.out.println("请求失败");
}
}
getRedPack方法如下, 假如第一次领取(后台获取表示从后台查询所得)
public float getRedPack(){
//红包数 (后台获取)
int number = 100;
//剩余红包总额 (后台获取)
float total = 100;
float money = 0f;
//最小红包 (后台获取)
double min = 0.05;
//系统最大红包(后台获取)
float systemMax = 3;
//最大红包
double max;
//剩余领取次数(后台获取)
int surplusNumber = 100;
//本次领取之后,剩余领取次数减一
surplusNumber--;
DecimalFormat df = new DecimalFormat("###.##");
if (surplusNumber > 0) {
//保证即使一个红包是最大的了,后面剩下的红包,每个红包也不会小于最小值
max = total - min * surplusNumber;
int k = (int)surplusNumber / 2;
//保证最后两个人拿的红包不超出剩余红包
if (surplusNumber <= 2) {
k = surplusNumber;
}
//最大的红包限定的平均线上下
max = max / k;
//保证每个红包大于最小值,又不会大于最大值
money = (int) (min * 100 + Math.random() * (max * 100 - min * 100 + 1));
money = (float)money / 100;
//保留两位小数
money = Float.parseFloat(df.format(money));
//如果红包大于默认最大值,将红包职位默认最大值
if(money > systemMax){
money = systemMax;
}
total=(int)(total*100 - money*100);
total = total/100;
System.out.println("第" + (number - surplusNumber) + "个人拿到" + money + "剩下" + total);
} else if(surplusNumber == 0){//如果是最后一次,不需要计算
//如果最后一次红包超过系统最大红包,设置为系统默认最大红包
if(total > systemMax){
money = systemMax;
total=(int)(total*100 - money*100);
total = total/100;
System.out.println("最后一个人拿到" + money + "剩下"+total);
}else{
money = total;
System.out.println("最后一个人拿到" + money + "剩下0");
}
}else{
System.out.println("红包已发放完毕");
}
return money;
}
进行签名,将参数排序并拼接成xml
public static String wxSendWallet(String partner_trade_no, String openid,String total_amount) {
String data = null;
try {
String nonceStr = genNonceStr();
//SortedMap接口主要提供有序的Map实现,默认的排序是根据key值进行升序排序
SortedMap<String,String> parameters = new TreeMap<String,String>();
parameters.put("mch_appid", "商户appid");
parameters.put("mchid", "商户号");
parameters.put("nonce_str", nonceStr);
parameters.put("partner_trade_no", partner_trade_no);
parameters.put("openid", openid);
parameters.put("check_name", "NO_CHECK");
parameters.put("amount", total_amount);
parameters.put("spbill_create_ip", WeChatPayUtil.getLocalIP());
parameters.put("desc", "福利红包");
//签名
parameters.put("sign", createSign(parameters, "商户的key"));
data =SortedMaptoXml(parameters);
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
getLocalIp() 获取ip地址
public static String getLocalIP() {
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
byte[] ipAddr = addr.getAddress();
String ipAddrStr = "";
for (int i = 0; i < ipAddr.length; i++) {
if (i > 0) {
ipAddrStr += ".";
}
ipAddrStr += ipAddr[i] & 0xFF;
}
return ipAddrStr;
}
createSign() 签名算法
/**
* @Title: createSign
* @Description: 签名算法,创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
* 参照:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3
*/
public static String createSign(SortedMap<String, String> packageParams, String AppKey) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + AppKey);
String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
return sign;
}
MD5Encode() 常见md5摘要
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
请求值转换为xml格式 SortedMap转xml
private static String SortedMaptoXml(SortedMap<String,String> params) {
StringBuilder sb = new StringBuilder();
Set es = params.entrySet();
Iterator it = es.iterator();
sb.append("<xml>\n");
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
sb.append("<"+k+">");
sb.append(v);
sb.append("</"+k+">\n");
}
sb.append("</xml>");
return sb.toString();
}
上面的就是整个的逻辑,需要的方法都在这里了,配置好了运行肯定是没问题的,测试的效果如下:
随笔记录一下,如果有问题请留言