本文介绍的是基于Java语言开发微信现金红包的例子。由于个人表达能力和编程能力有限,请多多包涵。本文仅介绍拥有微信支付权限的微信公众号开发。
本文分为以下两部分:
1.开发现金红包SDK
2.构造现金红包并发送
首先看一下现金红包接口文档:http://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_1
首页现金红包
功能介绍
春节期间,微信红包以其独特的魅力,优秀的用户体验和安全的支付环境,一经推出即受到了广大用户的热烈欢迎,现微信支付现金红包向微信支付商户开发,具体能力如下:
1、商户调用接口时,通过指定发送对象以及发送金额的方式发放红包,这样的方式,允许商户灵活的应用于各种各样丰富的活动场景
2、领取到红包后,用户的资金直接进入微信零钱,避免繁复的领奖流程,带给用户微信支付原生的流畅体验
微信红包发送规则1. 发送频率规则
◆ 每分钟发送红包数量不得超过1800个;
◆ 北京时间0:00-8:00不触发红包赠送;(如果以上规则不满足您的需求,请发邮件至wxhongbao@tencent.com获取升级指引)
2. 红包规则
◆ 单个红包金额介于[1.00元,200.00元]之间;
◆ 同一个红包只能发送给一个用户;(如果以上规则不满足您的需求,请发邮件至wxhongbao@tencent.com获取升级指引)
商户侧调用红包接口流程1. 登录微信支付商户平台下载证书以及充值
在调用接口前,请商户使用微信支付商户号登录微信支付商户平台完成下述工作:
备注:
微信支付商户平台地址为pay.weixin.qq.com。微信支付商户号会在商户申请微信支付成功后,通过开户邮件发送给您。请不要使用微信公众平台账号或者appid登录。如果您登录时遇到问题,请联系微信支付小助手weixinpay@tencent.com
◆ 下载证书
商户调用微信红包接口时,服务器会进行证书验证,请在商户平台下载证书
◆ 充值
发放现金红包将扣除商户的可用余额,请注意,可用余额并不是微信支付交易额,需要预先充值,确保可用余额充足。查看可用余额、充值、提现请登录微信支付商户平台,进入“资金管理”菜单,进行操作
2. 微信红包接口调用流程
◆ 后台API调用:待进入联调过程时与开发进行详细沟通;
◆ 告知服务器:告知服务器接收微信红包的用户openID,告知服务器该用户获得的金额;
◆ 从商务号扣款:服务器获取信息后从对应的商务号扣取对应的金额;
◆ 调用失败:因不符合发送规则,商务号余额不足等原因造成调用失败,反馈至调用方;
◆ 发送成功:以微信红包公众账号发送对应红包至对应用户;
用户交互流程
调用现金红包接口,发放成功后,用户领取红包流程如下:
步骤(一):收到领取红包消息,根据用户微信版本不同,分为:
微信版本在6.1及以上的用户收到企业自身微信号(调用接口时传入appid对应的商户号)下发领取消息;如果用户未关注微信号,那么会收到由“服务通知”下发的消息
微信版本在6.1以下的用户仍按原流程收取消息:由微信红包公众号下发领取消息
步骤(二):点击领取消息,拆红包
src="http://pay.weixin.qq.com/wiki/doc/api/img/chapter13_5.png" "width="261" height="464" style="border: 0px none; vertical-align: top;">
接口列表
业务 | 接口 | 简介 |
现金红包 | 发放红包 | 用于企业向微信用户个人发现金红包,目前支持向指定微信用户的openid发放指定金额红包。(获取openid参见微信公众平台开发者文档: 网页授权获取用户基本信息) |
接口详细说明
1.红包发放说明
用于企业向微信用户个人发现金红包
目前支持向指定微信用户的openid发放指定金额红包。(获取openid参见微信公众平台开发者文档: 网页授权获取用户基本信息)
接口参数与用户领用实际效果对应关系如下:
如需操作请登录https://pay.weixin.qq.com/
2.接口调用请求说明
请求Url | https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack |
是否需要证书 | 是(证书及使用说明详见商户证书) |
请求方式 | POST |
3.请求参数,返回参数,错误码,成功返回示例xml,失败返回示例xml请参考https://pay.weixin.qq.com/helper/cashredopenapi_V2.pdf
一、开发现金红包SDK
1.创建一个红包,本文介绍的是创建一个固定金额的红包。
2.接口调用说明
请求Url | https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack |
是否需要证书 | 是(证书及使用说明详见商户证书) |
请求方式 | POST |
证书是必须的,上面的现金红包接口文档里面有介绍。
点击这个链接:http://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_3
这一节有介绍的。
3.请求参数。注意看下,有的参数是必填的。
二、构造现金红包并发送
构造现金红包实体类
public class SendRedPackPo { private String nonce_str; private String sign; private String mch_billno; private String mch_id; private String sub_mch_id; private String wxappid; private String nick_name; private String send_name; private String re_openid; private String total_amount; private String min_value; private String max_value; private String total_num; private String wishing; private String client_ip; private String act_name; private String remark; private String logo_imgurl; private String share_content; private String share_url; private String share_imgurl; public String getNonce_str() { return nonce_str; } public void setNonce_str(String nonceStr) { nonce_str = nonceStr; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } public String getMch_billno() { return mch_billno; } public void setMch_billno(String mchBillno) { mch_billno = mchBillno; } public String getMch_id() { return mch_id; } public void setMch_id(String mchId) { mch_id = mchId; } public String getSub_mch_id() { return sub_mch_id; } public void setSub_mch_id(String subMchId) { sub_mch_id = subMchId; } public String getWxappid() { return wxappid; } public void setWxappid(String wxappid) { this.wxappid = wxappid; } public String getNick_name() { return nick_name; } public void setNick_name(String nickName) { nick_name = nickName; } public String getSend_name() { return send_name; } public void setSend_name(String sendName) { send_name = sendName; } public String getRe_openid() { return re_openid; } public void setRe_openid(String reOpenid) { re_openid = reOpenid; } public String getTotal_amount() { return total_amount; } public void setTotal_amount(String totalAmount) { total_amount = totalAmount; } public String getMin_value() { return min_value; } public void setMin_value(String minValue) { min_value = minValue; } public String getMax_value() { return max_value; } public void setMax_value(String maxValue) { max_value = maxValue; } public String getTotal_num() { return total_num; } public void setTotal_num(String totalNum) { total_num = totalNum; } public String getWishing() { return wishing; } public void setWishing(String wishing) { this.wishing = wishing; } public String getClient_ip() { return client_ip; } public void setClient_ip(String clientIp) { client_ip = clientIp; } public String getAct_name() { return act_name; } public void setAct_name(String actName) { act_name = actName; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public String getLogo_imgurl() { return logo_imgurl; } public void setLogo_imgurl(String logoImgurl) { logo_imgurl = logoImgurl; } public String getShare_content() { return share_content; } public void setShare_content(String shareContent) { share_content = shareContent; } public String getShare_url() { return share_url; } public void setShare_url(String shareUrl) { share_url = shareUrl; } public String getShare_imgurl() { return share_imgurl; } public void setShare_imgurl(String shareImgurl) { share_imgurl = shareImgurl; } }
生成32位字符串工具类
/** * 生成32位字符串 * */ public class UUIDHexGenerator { private String sep = ""; private static final int IP; private static short counter = (short) 0; private static final int JVM = (int) (System.currentTimeMillis() >>> 8); private static UUIDHexGenerator uuidgen = new UUIDHexGenerator(); static { int ipadd; try { ipadd = toInt(InetAddress.getLocalHost().getAddress()); } catch (Exception e) { ipadd = 0; } IP = ipadd; } public static UUIDHexGenerator getInstance() { return uuidgen; } public static int toInt(byte[] bytes) { int result = 0; for (int i = 0; i < 4; i++) { result = (result << 8) - Byte.MIN_VALUE + (int) bytes[i]; } return result; } protected String format(int intval) { String formatted = Integer.toHexString(intval); StringBuffer buf = new StringBuffer("00000000"); buf.replace(8 - formatted.length(), 8, formatted); return buf.toString(); } protected String format(short shortval) { String formatted = Integer.toHexString(shortval); StringBuffer buf = new StringBuffer("0000"); buf.replace(4 - formatted.length(), 4, formatted); return buf.toString(); } protected int getJVM() { return JVM; } protected synchronized short getCount() { if (counter < 0) { counter = 0; } return counter++; } protected int getIP() { return IP; } protected short getHiTime() { return (short) (System.currentTimeMillis() >>> 32); } protected int getLoTime() { return (int) System.currentTimeMillis(); } public String generate() { return new StringBuffer(36) .append(format(getIP())) .append(sep) .append(format(getJVM())) .append(sep) .append(format(getHiTime())) .append(sep) .append(format(getLoTime())) .append(sep) .append(format(getCount())) .toString(); } /** * @param args */ public static void main(String[] args) { String id; UUIDHexGenerator uuid = UUIDHexGenerator.getInstance(); for(int i = 0;i<100;i++) { id = uuid.generate(); } } }
MD5工具类
/** * 功能:MD5签名处理核心文件,不需要修改 * 说明: * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 * */ public class MD5 { /** * 签名字符串 * @param text 需要签名的字符串 * @param key 密钥 * @param input_charset 编码格式 * @return 签名结果 */ public static String sign(String text, String key, String input_charset) { text = text + key; return DigestUtils.md5Hex(getContentBytes(text, input_charset)); } /** * 签名字符串 * @param text 需要签名的字符串 * @param sign 签名结果 * @param key 密钥 * @param input_charset 编码格式 * @return 签名结果 */ public static boolean verify(String text, String sign, String key, String input_charset) { text = text + key; String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)); if(mysign.equals(sign)) { return true; } else { return false; } } /** * @param content * @param charset * @return * @throws SignatureException * @throws UnsupportedEncodingException */ private static byte[] getContentBytes(String content, String charset) { if (charset == null || "".equals(charset)) { return content.getBytes(); } try { return content.getBytes(charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } } /** * 生成6位或10位随机数 * param codeLength(多少位) * @return */private String createCode(int codeLength) { String code=""; for(int i=0; i<codeLength; i++) { code += (int)(Math.random() * 9); } return code; } private static boolean isValidChar(char ch) { if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')|| (ch >= 'a' && ch <= 'z')) return true; if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f)) return true;// 简体中文汉字编码 return false; } /** * 除去数组中的空值和签名参数 * @param sArray 签名参数组 * @return 去掉空值与签名参数后的新签名参数组 */ public static Map<String, String> paraFilter(Map<String, String> sArray) { Map<String, String> result = new HashMap<String, String>(); if (sArray == null || sArray.size() <= 0) { return result; } for (String key : sArray.keySet()) { String value = sArray.get(key); if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) { continue; } result.put(key, value); } return result; } /** * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 * @param params 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(Map<String, String> params) { List<String> keys = new ArrayList<String>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } /** * 发送现金红包 * @throws KeyStoreException * @throws IOException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws UnrecoverableKeyException * @throws KeyManagementException * @throws DocumentException */ public void sendRedPack() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, UnrecoverableKeyException, DocumentException { // 获取uuid作为随机字符串 String nonceStr = uuidGenerator.generate(); String today = new SimpleDateFormat("yyyyMMdd").format(new Date()); String code = createCode(10); String mch_id = "10000100";//商户号 String appid = "wxd930ea5d5a258f4f"; String opendid = "xxxxxxxxxxxxxxxxx"; //发送给指定微信用户的openid SendRedPackPo sendRedPackPo = new SendRedPackPo(); String totalAmount = "1"; sendRedPackPo.setNonce_str(nonceStr); sendRedPackPo.setMch_billno(mch_id + today + code); sendRedPackPo.setMch_id(mch_id); sendRedPackPo.setWxappid(appid); sendRedPackPo.setNick_name("xxx"); sendRedPackPo.setSend_name("xxx"); sendRedPackPo.setRe_openid(opendid); sendRedPackPo.setTotal_amount(totalAmount); sendRedPackPo.setMin_value(totalAmount); sendRedPackPo.setMax_value(totalAmount); sendRedPackPo.setTotal_num("1"); sendRedPackPo.setWishing("祝您新年快乐!"); sendRedPackPo.setClient_ip("192.168.1.1"); //IP sendRedPackPo.setAct_name("小游戏"); sendRedPackPo.setRemark("快来抢红包!"); //把请求参数打包成数组 Map<String, String> sParaTemp = new HashMap<String, String>(); sParaTemp.put("nonce_str", sendRedPackPo.getNonce_str()); sParaTemp.put("mch_billno", sendRedPackPo.getMch_billno()); sParaTemp.put("mch_id", sendRedPackPo.getMch_id()); sParaTemp.put("wxappid", sendRedPackPo.getWxappid()); sParaTemp.put("nick_name", sendRedPackPo.getNick_name()); sParaTemp.put("send_name", sendRedPackPo.getSend_name()); sParaTemp.put("re_openid", sendRedPackPo.getRe_openid()); sParaTemp.put("total_amount", sendRedPackPo.getTotal_amount()); sParaTemp.put("min_value", sendRedPackPo.getMin_value()); sParaTemp.put("max_value", sendRedPackPo.getMax_value()); sParaTemp.put("total_num", sendRedPackPo.getTotal_num()); sParaTemp.put("wishing", sendRedPackPo.getWishing()); sParaTemp.put("client_ip", sendRedPackPo.getClient_ip()); sParaTemp.put("act_name", sendRedPackPo.getAct_name()); sParaTemp.put("remark", sendRedPackPo.getRemark()); //除去数组中的空值和签名参数 Map<String, String> sPara = paraFilter(sParaTemp); String prestr = createLinkString(sPara); //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 String key = "&key=192006250b4c09247ec02edce69f6a2d"; //商户支付密钥 String mysign = MD5.sign(prestr, key, "utf-8").toUpperCase(); sendRedPackPo.setSign(mysign); String respXml = MessageUtil.messageToXml(sendRedPackPo); //打印respXml发现,得到的xml中有“__”不对,应该替换成“_” respXml = respXml.replace("__", "_"); // 将解析结果存储在HashMap中 Map<String, String> map = new HashMap<String, String>(); KeyStore keyStore = KeyStore.getInstance("PKCS12"); FileInputStream instream = new FileInputStream(new File("/home/apiclient_cert.p12")); //此处为证书所放的绝对路径 try { keyStore.load(instream, mch_id.toCharArray()); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, 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 { HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"); StringEntity reqEntity = new StringEntity(respXml, "utf-8"); // 设置类型 reqEntity.setContentType("application/x-www-form-urlencoded"); httpPost.setEntity(reqEntity); System.out.println("executing request" + httpPost.getRequestLine()); CloseableHttpResponse response = httpclient.execute(httpPost); try { HttpEntity entity = response.getEntity(); System.out.println(response.getStatusLine()); if (entity != null) { // 从request中取得输入流 InputStream inputStream = entity.getContent(); // 读取输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 List<Element> elementList = root.elements(); // 遍历所有子节点 for (Element e : elementList) map.put(e.getName(), e.getText()); // 释放资源 inputStream.close(); inputStream = null; } EntityUtils.consume(entity); } finally { response.close(); } } finally { httpclient.close(); } // 返回状态码 String return_code = map.get("return_code"); // 返回信息 String return_msg = map.get("return_msg"); // 业务结果 String result_code = map.get("result_code"); // 错误代码 String err_code = map.get("err_code"); // 错误代码描述 String err_code_des = map.get("err_code_des"); /** * 根据以上返回码进行业务逻辑处理 */ . . . . }
MessageUtil工具类代码
package com.test.util; /*** * V型知识库www.vxzsk.com * */ import java.io.InputStream; import java.io.Writer; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.test.message.resp.Article; import com.test.message.resp.MusicMessage; import com.test.message.resp.NewsMessage; import com.test.message.resp.TextMessage; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; /** * 消息工具类 */ public class MessageUtil { /** * 返回消息类型:文本 */ public static final String RESP_MESSAGE_TYPE_TEXT = "text"; /** * 返回消息类型:音乐 */ public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; /** * 返回消息类型:图文 */ public static final String RESP_MESSAGE_TYPE_NEWS = "news"; /** * 请求消息类型:文本 */ public static final String REQ_MESSAGE_TYPE_TEXT = "text"; /** * 请求消息类型:图片 */ public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; /** * 请求消息类型:链接 */ public static final String REQ_MESSAGE_TYPE_LINK = "link"; /** * 请求消息类型:地理位置 */ public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; /** * 请求消息类型:音频 */ public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; /** * 请求消息类型:推送 */ public static final String REQ_MESSAGE_TYPE_EVENT = "event"; /** * 事件类型:subscribe(订阅) */ public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; /** * 事件类型:unsubscribe(取消订阅) */ public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; /** * 事件类型:CLICK(自定义菜单点击事件) */ public static final String EVENT_TYPE_CLICK = "CLICK"; /** * 解析微信发来的请求(XML) * * @param request * @return * @throws Exception */ @SuppressWarnings("unchecked") public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { // 将解析结果存储在HashMap中 Map<String, String> map = new HashMap<String, String>(); // 从request中取得输入流 InputStream inputStream = request.getInputStream(); // 读取输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 List<Element> elementList = root.elements(); // 遍历所有子节点 for (Element e : elementList) { map.put(e.getName(), e.getText()); } // 释放资源 inputStream.close(); inputStream = null; return map; } /** * 文本消息对象转换成xml * @param textMessage 文本消息对象 * @return xml */ public static String textMessageToXml(TextMessage textMessage) { xstream.alias("xml", textMessage.getClass()); return xstream.toXML(textMessage); } /** * 音乐消息对象转换成xml * @param musicMessage 音乐消息对象 * @return xml */ public static String musicMessageToXml(MusicMessage musicMessage) { xstream.alias("xml", musicMessage.getClass()); return xstream.toXML(musicMessage); } /** * 图文消息对象转换成xml * @param newsMessage 图文消息对象 * @return xml */ public static String newsMessageToXml(NewsMessage newsMessage) { xstream.alias("xml", newsMessage.getClass()); xstream.alias("item", new Article().getClass()); return xstream.toXML(newsMessage); } /** * 扩展xstream,使其支持CDATA块 * @date */ private static XStream xstream = new XStream(new XppDriver() { public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { // 对所有xml节点的转换都增加CDATA标记 boolean cdata = true; @SuppressWarnings("unchecked") public void startNode(String name, Class clazz) { super.startNode(name, clazz); } protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } else { writer.write(text); } } }; } }); }
关于MessageUtil的封装代码请参考https://www.vxzsk.com/63.html 当然,读者也可以修改代码中MessageUtil解析xml部分代码。
代码中httpclient-4.3.4.jar没有放上去,麻烦小伙伴们自己找下吧。
实现效果
感觉本站内容不错,读后有收获?小额赞助,鼓励网站分享出更好的教程