前几天用java实现了微信商户发红包给用户的功能。现在整理一下,方便以后参考。
总体的步骤是:
- 1.在微信客户端转发自己的uri路径
- 2.拿到微信授权的Code
- 3.获取用户openID
- 4.获取用户信息
- 5.给指定用户发送红包
在开发之前了解一下完整的微信需要准备的参数:
微信公众账号:appid
商户号:mchid
发送红包方的名称(商户名称):sendname
红包祝福语:wishing
ip地址:ip
秘钥:key
安全码:secret
证书:apiclient_cert.p12
在接下来步骤中会详细介绍每参数需要注意的地方。
在微信客户端转发自己的uri路径
在查看微信的官方文档有具体说明:用户在微信客户端访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
但是微信授权回调的域名可以参考微信官方文档关于域名的要求:
https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
这里要注意的是你的后台服务必须是要发布在80端口下,列如tomcat发布在80端口下。
域名配置和服务都发布好之后,就可以调用微信的接口获取Code啦。
url:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
params介绍:
appid:发送方的微信公众账号,微信分配的公众账号ID(企业号corpid即为此appId)。接口传入的所有appid应该为公众号的appid(在mp.weixin.qq.com申请的),不能为APP的appid(在open.weixin.qq.com申请的)。eg:wx8888888888888888
redirect_uri:转发到自己的url,eg:www.abc.com/项目名/weixin/hongbao
scope:Scope为snsapi_base,,Scope为snsapi_userinfo时弹出微信获取用户信息授权提示页面。
state:重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
注意:必须带上#wechat_redirect,并且参数顺序必须和上述一致。
如果你是通过你的服务器发起的请求代码如下:
your control:
String url=https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx8888888&redirect_uri=http://www.abc.com/test/weixin/hongbao&response_type=code&scope=snsapi_base&state=1#wechat_redirect
//微信授权转发
response.sendRedirect(url);
如果直接在微信客户端打开上述url,只要确保REDIRECT_URI参数正确,就可以直接转发到你的地址里面去。
遇到的问题:
- 请在微信客户端打开连接,可以将链接生成二维码扫描。
- 确保转发url已审批,并在80端口下发布,不然会微信会返回提示:REDIRECT_URI参数错误。
拿到微信授权的Code
微信在转发到指定的url后会携带两个参数:code和state
code:是用来获取用户的openId接口必要的参数之一。
state:是你在转发url时填写的参数。是一个双方之间的凭证,可用来判断是否合法。
www.abc.com/test/weixin/hongbao的代码:
@RequestMapping(value="/hongbao",method={RequestMethod.GET,RequestMethod.POST})
public String hongbao( @RequestParam(value = "code", required = false) String code,@RequestParam(value = "state", required = false) String state,HttpServletResponse response,HttpServletRequest request){
request.getSession();
//这样就可以获取code和state啦,打印一下
Log.logger.info(request.getParameter("code")); Log.logger.info(request.getParameter("state"));
//获取openiD
...
}
获取用户openID
调用微信接口获取openID:
url: https://api.weixin.qq.com/sns/oauth2/access_token
method: GET
params: appid:wx88888 , secret:qwe123123, code:上面获取的code, grant_type:一个定值authorization_code
代码:
SortedMap<String, String> params = new TreeMap<String, String>();
params.put("appid",wechatConfig.getWxappid());
params.put("secret",wechatConfig.getSecret());
params.put("code",code);
params.put("grant_type","authorization_code");
//将参数进行排序
String sortparams = WeChatUtils.getSortParams(params);
//调用接口获取结果
String result=HttpNetUtils.getInstance().sendGet(WeChatContants.WECHAT_OPENID_URL,sortparams);
//解析result获取opeId;
WeiChatGetOpenIdVo getOpenIdVo = JSON.parseObject(result, WeiChatGetOpenIdVo.class);
String openId=getOpenIdVo.getOpenid();
返回结果参数有:
{
“access_token”:”ACCESS_TOKEN”,
“expires_in”:7200,
“refresh_token”:”REFRESH_TOKEN”,
“openid”:”OPENID”,
“scope”:”SCOPE”,
“unionid”: “o6_bmasdasdsad6_2sgVt7hMZOPfL”
}
咱们只要获取他的openID即可,其他的参数说明可以参考官方文档 。
获取用户信息
获取用户信息前还需要一个必要的参数:该公众账号下的access_token,每个公众账号的token每天是有次数限制的(2000次)其他的限制可参考:
调用接口获取token:
url:https://api.weixin.qq.com/cgi-bin/token
method:GET
params:grant_type:”client_credential “, appid:wx88888 , secret:qwe1232123
代码:
SortedMap<String, String> params = new TreeMap<String, String>();
params.put("grant_type","client_credential");
params.put("appid",wechatConfig.getWxappid());
params.put("secret",wechatConfig.getSecret());
String sortparams = WeChatUtils.getSortParams(params);
String result = HttpNetUtils.getInstance().sendGet(WeChatContants.WECHAT_ACCESSTOKEN_URL,sortparams);
WeiChatAccessTokenVo token = JSON.parseObject(result, WeiChatAccessTokenVo.class);
String accessToken=token.getAccess_token();
//可以将token存在数据库里,不必每次都去调用微信接口获取。判断超过7200s后才去更新。
文档位置:获取接口调用凭证–>获取access_token
拿到token和openID后调用获取用户信息接口:
url: https://api.weixin.qq.com/cgi-bin/user/info
method: GET
params: access_token:上面获取的,openid:上面获取的,lang:”zh_CN”
代码:
SortedMap<String, String> params = new TreeMap<String, String>();
params.put("access_token",accessToken);
params.put("openid",openId);
params.put("lang","zh_CN");
String sortparams = WeChatUtils.getSortParams(params);
String result=HttpNetUtils.getInstance().sendGet(WeChatContants.WECHAT_USERINFO_URL,sortparams);
JSON.parseObject(result.trim(),WeiChatUserInfoVo.class);
返回结果:
注意:如果该用户没有关注该微信公众账号是不能获取这么详细的信息的,没有关注的返回:subscribe:0,和openid。
未关注的情况下获取详细信息:
你也可以采用在转发url时提示用户授权获取他的详细信息,即scope=snsapi_userinfo
给指定用户发送红包
其实拿到用户的openID后就可已给指定的用户发红包啦。
url: https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack
证书:需要证书
method: POST
params: nonce_str 随机字符串,
mch_billno 商户订单号(商户号+一个10位不能重复的字符)
mch_id 商户号
wxappid 微信公众账号
send_name 发送方名字
re_openid 接受红包者的openID
total_amount 发送总金额
total_num 发送总数量
wishing 红包祝福语
client_ip 调用接口的机器Ip地址
act_name 活动名字
remark 备注
sign 将以上参数配上key打包签名的值
参数详细说明:
https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
关于证书和签名可查看:
https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3
代码:
//使用该企业下的微信红包参数来封装调取微信接口的参数
SortedMap<String, String> params = new TreeMap<String, String>();
params.put("wxappid",wechatConfig.getWxappid());
params.put("nonce_str",WeChatUtils.createNonceStr());
params.put("mch_billno",WeChatUtils.createBillNo(wechatConfig.getMchId()));
params.put("mch_id",wechatConfig.getMchId());
params.put("send_name",wechatConfig.getSendName());
params.put("re_openid",openId);
params.put("total_amount",100+"");//分
params.put("total_num", 1+"");
params.put("wishing", wechatConfig.getWishing());
params.put("client_ip", wechatConfig.getHostIp());
params.put("act_name", wechatConfig.getActName());
params.put("remark",wechatConfig.getRemark());
//将参数转换为有序的
String sortParams = WeChatUtils.getSortParams(params);
//将参数打包签名
String key=wechatConfig.getMyKey();
WeChatUtils.sign(params,sortParams,key);
//将参数已xml的装换为格式
String paramsXml = WeChatUtils.getRequestXml(params);
//调取微信发红包接口
String contextPath=request.getSession().getServletContext().getRealPath("");
//httppost请求鞋带证书
String certPath=contextPath.substring(0,contextPath.lastIndexOf("\\"))+wechatConfig.getCert();
String certPassword=wechatConfig.getMchId();
String rets =HttpNetUtils.getInstance().certPost(WeChatContants.WECHAT_SENDHONGBAO_URL,paramsXml,certPath,certPassword);
Log.logger.info(rets);
打包签名和获取随机字符的代码也贴出来方便参考:
/**
* 生成随机字符串
* @return
*/
public static String createNonceStr() {
return UUID.randomUUID().toString().toUpperCase().replace("-", "");
}
/**
* 生成商户订单号
* @param mch_id 商户号
* @return
*/
public static String createBillNo(String mchId){
//组成: mch_id+yyyymmdd+10位一天内不能重复的数字
//10位一天内不能重复的数字实现方法如下:
Date dt=new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String nowTime= df.format(dt);
return mchId + nowTime + getRandomNum(4);
}
/**
* 生成特定位数的随机数字
* @param length
* @return
*/
public static String getRandomNum(int length) {
String val = "";
Random random = new Random();
for (int i = 0; i < length; i++) {
val += String.valueOf(random.nextInt(10));
}
return val;
}
/**
* 将传入的参数加上key并通过MD5签名
*
* @param params
*/
public static void sign(SortedMap<String, String> params,String sortparams,String key){
//添加秘钥并且签名
sortparams +="key="+key;
String sign = null;
try {
sign = MD5Util.MD5(sortparams);
} catch (Exception e) {
e.printStackTrace();
}
params.put("sign", sign);
}
/**
* 将SortedMap集合里的按顺序参数转换为:name=value&name1=value1
* 按顺序
* @param params
*/
public static String getSortParams(SortedMap<String, String> params){
Set<Entry<String,String>> entrys=params.entrySet();
Iterator<Entry<String,String>> it=entrys.iterator();
String result = "";
while(it.hasNext())
{
Entry<String,String> entry=it.next();
result +=entry.getKey()+"="+entry.getValue()+"&";
}
return result;
}
/**
* 将传入的参数装换为xml格式,用于调取微信红包接口
* @param params
* @return
*/
public static String getRequestXml(SortedMap<String,String> params){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = params.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 ("nick_name".equalsIgnoreCase(k)||"send_name".equalsIgnoreCase(k)||"wishing".equalsIgnoreCase(k)||"act_name".equalsIgnoreCase(k)||"remark".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
}else {
sb.append("<"+k+">"+v+"</"+k+">");
}
}
sb.append("</xml>");
return sb.toString();
}
HTTP发送POST方法的携带证书请求代码:
/**
* 向指定URL 发送POST方法的携带证书请求
*
* @param url
* @param requestXML xml格式的参数
* @param certPath 证书的地址
* @param certPassword 证书的密码
*
* @return 远程资源的响应结果
*/
@SuppressWarnings("deprecation")
public String certPost(String url,String requestXML,String cretPath,String certPwd) throws NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, UnrecoverableKeyException, KeyStoreException{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File(cretPath));
keyStore.load(instream, certPwd.toCharArray());
instream.close();
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, certPwd.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
String result = "";
try {
HttpPost httpPost = new HttpPost(url);
StringEntity reqEntity = new StringEntity(requestXML,"UTF-8");
// 设置类型
reqEntity.setContentType("application/x-www-form-urlencoded");
httpPost.setEntity(reqEntity);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
String text;
while ((text = bufferedReader.readLine()) != null) {
result +=text;
}
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
return result;
}
如果成功调通接微信会返回:xml格式的数据。例如:
如果用户没有关注该公众账号:发送红包时一个微信客户端的服务通知。
如果用户关注了公众账号:发送红包是在该公众账号下的一条消息。
到这里微信商户给用户发送红包和获取用户信息的流程就全部完成啦。
如果你看了之后对你哪怕是有一丢丢的帮助,别忘了给我点赞啊。
最后附上微信官方文档的地址:
- 用户授权及获取用户信息:https://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html
- 微信支付和发红包:https://pay.weixin.qq.com/wiki/doc/api/index.html