今天公司打算做一个活动,就是可以让用户领取平台发送的红包,根据微信官方文档实现微信企业付款到零钱(因为商户号不满足一些条件无法使用红包,红包跟零钱实现方法基本一样),然后又加入了一些简单的红包算法。微信官方文档地址: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值进行升序排序 SortedMapString,String parameters = new TreeMapString,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(SortedMapString, 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; }上面的就是整个的逻辑,需要的方法都在这里了,配置好了运行肯定是没问题的,测试的效果如下:
随笔记录一下,如果有问题请留言













