前言
最近在写的项目,关于微信“一物一码”。用户扫码直接跳转到微信小程序中,进入抽奖活动。中奖后可以直接在小程序中发送微信红包。在此记录一下微信小程序发红包的过程
正文
首先介绍一下微信小程序红包的大致流程。
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){}
)}
当在小程序端调用接口时,会直接弹出微信红包,领取后存到零钱当中。