前言

最近在写的项目,关于微信“一物一码”。用户扫码直接跳转到微信小程序中,进入抽奖活动。中奖后可以直接在小程序中发送微信红包。在此记录一下微信小程序发红包的过程

正文

首先介绍一下微信小程序红包的大致流程。
1、组织参数,调用微信发送红包接口。(此处最重要的是参数签名)
2、获取微信返回结果,组织小程序端调用领取红包接口参数(还有签名!)
3、小程序端,调用领取微信红包接口,领取红包

组织参数

//获取发红包的参数
WechatRedPackRequest request = setRequest(amount, mchBillno, openId, actName);
//读取安全证书
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(CERTIFICATE_NAME);
logger.info("开始发送红包-->请求实体-->"+ JSON.toJSONString(request));
//发送发红包请求到微信
String resp = HttpUtil.wechatPost(WeixinConfig.API_URL, WeChatUtils.convertObjectToXml(request, WechatRedPackRequest.class), inputStream);
logger.info("红包发送结束-->返回XML-->"+ resp);
//将xml 转换成map
Map<String,String> response = WeChatUtils.xmlToMap(resp);
logger.info("解析返回XML-->"+ JSON.toJSONString(response));
RedPacketLog result = JSONObject.parseObject(JSON.toJSONString(response),new TypeReference<RedPacketLog>(){});
result.setResultXml(JSON.toJSONString(response));
result.setRequestParam(JSON.toJSONString(request));
return result;

这里最主要的是参数的组织,一般会遇到问题也就是签名的问题。
所以在签名时要注意,推荐使用微信封装好的工具类,最起码不会遇到其他问题。

  • 拼接参数(ASCII码从小到大排序(字典序))
private static String createLinkString(Map<String, String> params) {
        System.out.println(params.toString());
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);

        StringBuilder preStr = new StringBuilder();

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            // 拼接时,不包括最后一个&字符
            if (i == keys.size() - 1) {
                preStr.append(key).append("=").append(value);
            } else {
                preStr.append(key).append("=").append(value).append("&");
            }
        }
        return preStr.toString();
    }

这里的Parms就是所有的参数,注意,商户key参数最后拼接。
如上述,最终的参数拼接位 preStr.toString()+“&key=”+“你的商户key”;

  • 加密参数

对最终拼接的参数进行MD5加密,转换成大写字母即可。

  • 将参数实体转换成xml格式

在微信的参数格式,使用的是xml格式。 所以,当获取到所有参数和签名后,还需要将其转换成xml格式,这里有两种方式:

1、将 实体对象 转换成 xml,需要使用 Xstream , 在 pom文件中,添加下面依赖

    <!-- 将javaBean 转换成 xml-->
    <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.4.10</version>
    </dependency>

  public static <T> String convertObjectToXml(Object obj, Class<T> type) {
        XStream xstream = new XStream(new XppDriver(new XmlFriendlyNameCoder("__", "_")));
        xstream.alias("xml", type);
        return xstream.toXML(obj);
    }

2、将 map 转换成 xml,微信提供的demo案例中使用此方法

public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = WeChatXmlUtil.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value =  data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString();
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }
  • 调用微信接口
 //keyStream 为证书的输入流
 public static String wechatPost(String url,String params, InputStream keyStream ) throws Exception{
        KeyStore keyStore  = KeyStore.getInstance("PKCS12");
        try {
            keyStore.load(keyStream, WeixinConfig.MCH_ID.toCharArray());
        } finally {
            keyStream.close();
        }
        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, WeixinConfig.MCH_ID.toCharArray())
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[] { "TLSv1" },
                null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();
        try {
            String resp = "";
            HttpPost httpPost = new HttpPost(url);
            StringEntity ent = new StringEntity(params,"utf-8");
            ent.setContentType("application/x-www-form-urlencoded");
            httpPost.setEntity(ent);
            CloseableHttpResponse response = httpclient.execute(httpPost);
            try {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    System.out.println("Response content length: " + entity.getContentLength());
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
                    String text;
                    while ((text = bufferedReader.readLine()) != null) {
                        resp += text;
                    }
                }
                EntityUtils.consume(entity);
                return resp;
            }catch(Exception e){
            }
            finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
        return null;
    }
  • 最后就是对微信返回的参数进行解析,建议遇到问题先看微信文档!

组织小程序端需要参数

//response 即为发送红包微信返回的信息转换成的实体类
public static Map<String, String> signResponse(RedPacketLog response)throws Exception{
        if (null != response) {
            if (StringUtils.isEmpty(response.getWxPackage())){
                return null;
            }
			//对微信返回的package要进行URL加密(不需要解密)
            String wxPackage = URLEncoder.encode(response.getWxPackage(), "UTF-8");
            String nonceStr = UUID.randomUUID().toString().replace("-", "");
            String timeStamp = String.valueOf(System.currentTimeMillis());
            Map<String, String> params = new HashMap<String, String>();
            params.put("package", wxPackage);
            params.put("nonceStr", nonceStr);
            params.put("timeStamp", timeStamp);
            params.put("appId", WeixinConfig.APP_ID);
            //获取签名
            String paySign = WeChatUtils.buildResponseSign(params, WeixinConfig.KEY);
            params.put("paySign", paySign);
            logger.info("领取红包所需参数--->" + JSON.toJSONString(params));
            return params;
        }
        return null;
    }

小程序端,调用接口领取红包

wx.sendBizRedPacket({
     timeStamp: timeStamp,
     nonceStr: nonceStr,
     package: package,
     signType: 'MD5', //固定值
     paySign: paySign,
     success: function (res) {},
	 fail : function(res){}
)}

当在小程序端调用接口时,会直接弹出微信红包,领取后存到零钱当中。