公司为一个系统提供了微信公众号服务,使用nutz框架自带的微信集成功能,其中可以做一些微信公众后台的一些功能
如何开发微信公众号支付功能?
第一步、先把需要用的东西都准备好
开发环境,已经认证的微信公众号,已经认证微信商户,以及可外网访问的域名。这些都不细说了。当然我是基于nutz框架集成的微信功能来开发的,我尽量细化过程,让所有的java开发人员能更轻松掌握了解,其中微信官方文档已经说明的事项,这里就不会再仔细说明,但会提示!
第二步、相关设置
1、域名:首先请准备好一个可外网访问的域名。我使用的是nutz社区提供的内网穿透工具,一个挺好用的东西,在这里贴出链接:https://nutz.cn/yvr/links/ngrok.html,网上也有很多工具,大家自己动手查找配置,要确保能外网访问,已有域名的可以不需要;
2、微信公众号:首先搜索“微信公众平台”,打开登录首页,使用准备好的公众号登录,进入首页后
1-点击【公众号设置】->【功能设置】
2-设置两个地方为你所要使用的域名,只是域名,不需要http之类的字符
3-点击【基本配置】,配置“服务器配置”下的相关内容,然后选择启用,此处相关配置不作详解
注意:微信公众号所需要的配置已经完成,接下来都与之无关。之后所需要的相关参数请自行查看保存,以便使用。
请注意保存几个比较重要的参数,nutz框架都有相关配置
3、微信商户平台:搜索“微信商户平台”,打开首页登录进去,登录需要安装控件
1-商户id
点击【账户中心】
比较重要的事来了:**首先大家请注意,此处有你的商户id,即参数Mch_id**
我之前上网查过资料,有人说Mchid是官方返回的邮件中的8位数字,也看到了说此处就是Mchid,经过亲身体验,我可以告诉你,这个10位数字就是Mchid,没有错,至于邮件,我只是开发人员,没看到我也不知道。获取Mchid后,到下一个重要的参数地方去
2-安装操作证书
还是在【账户中心】,点击【操作证书】-【申请安装】
然后就是发送验证码,安装操作证书的步骤,不详写,自己动手。
3-设置API秘钥
安装完操作证书后,还是在【账户中心】,点击【API安全】,此处由于个人原因,将引用他人经验,大家可点击链接查阅相关配置:http://jingyan.baidu.com/article/75ab0bcbbf7034d6864db2c3.html,其中涉及到一个重要参数“API秘钥”,即后面我们所要使用到的参数:key,请设置好之后妥善保存并记录,微信方将不予显示。
4-添加支付授权目录
API秘钥设置好之后,点击【产品中心】-【开发配置】(此处也显示了Mchid),配置“支付配置”下的“公众号支付”,请点击 【添加】按钮,选择添加支付授权目录。JSPAI支付授权目录设置要求官方有说明,必须是你请求支付的路径,并以“/”结尾
值得注意说明的是,这个目录的意义到底是什么?我怎么才能确定我设置的是支付目录?
通过查阅资料和亲自测试,就此来说明一下:
通过任何途径访问到一个支付页面,可以输入money后点击付款,弹出支付界面输入密码的那样一个页面
这就是所谓的支付页面,请在此页面点击右上角的三个点,点击【复制链接】,粘贴出来查看你的URL。比如我的URL是:http://XXXXXXX.ngrok.wendal.cn/aaaa/bbbbb/payonline?usercode=0010001302&metercode=00890021,那么你的JSAPI支付授权目录就是:“http://XXXXXXX.ngrok.wendal.cn/aaaa/bbbbb/”,至于能不能设置到“.../aaaa/”这里,还有待验证,我估计应该也是可以的。如果你的URL最后是个页面,例如“http://XXXXXXX.ngrok.wendal.cn/aaaa/bbbbb/index.html”,则和上面同样设置即可。
支付授权目录设置好之后,接下来就要写代码了。。。
以上有关微信后台的相关设置全部搞定,之后所设计的操作不再进行配置动作,再有相关的事,我想就是查看官方文档了。
第三步、第一次发送请求参数,获得返回参数
1-包装全部所需要的请求参数;
其中所有参数说明请查看官方文档的【统一下单】https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
这里我只使用必须要用到的参数,一共有10个参数,请看代码
private NutMap getRespData(int money, String wxid, String openId, HttpServletRequest request){
String out_trade_no = DateUtil.format(new Date(), "yyyyMMddHHmmss");//获取当前时间为商户订单号,可自定义
Wx_config config = wxConfigService.fetch(wxid);//通过用户的请求携带的微信公众id获取当前微信公众号的配置信息
NutMap payinfo = Json.fromJson(NutMap.class, config.getPayInfo());//格式化数据成Map对象
WxApi2 wxApi2 = wxConfigService.getWxApi2(config.getId());
WxPayUnifiedOrder order = new WxPayUnifiedOrder();//实例化请求参数对象
//设置所必须的请求参数
order.setAppid(config.getAppid());
order.setMch_id(payinfo.getString("wxpay_mchid"));
order.setNonce_str(R.UU32());
order.setBody("Test");//运行环境 -Dfile.encoding=UTF-8
order.setOut_trade_no(out_trade_no);
order.setTotal_fee(money);
order.setSpbill_create_ip(Lang.getIP(request));//获取用户请求地址的IP
order.setNotify_url("http://" + Globals.AppDomain + "/open/pay/reBackNotify");//支付结果回调通知地址,此目录须添加到授权目录
order.setTrade_type("JSAPI");//支付方式为JSAPI,即公众号支付
order.setOpenid(openId);//trade_type为JSAPI时,此参数必传
NutMap resp = wxApi2.pay_unifiedorder(payinfo.getString("wxpay_key"), order);//发送请求参数并接受返回参数
if (!resp.getString("result_code").equals("SUCCESS")){
log.debug("调用接口失败");
return null;
}
log.debug("resp:===="+Json.toJson(resp));
String prepayid = resp.getString("prepay_id");//从返回参数中得到预付订单的prepay_id
NutMap finalmap = new NutMap();//设置存放预付单参数的容器
//设置请求支付的必须参数为5个
finalmap.addv("appId",resp.getString("appid"));
finalmap.addv("timeStamp",String.valueOf(System.currentTimeMillis()/1000));
finalmap.addv("nonceStr",R.UU32());
finalmap.addv("package","prepay_id="+prepayid);//请注意此处的参数格式
finalmap.addv("signType","MD5");
//将以上设置的请求支付参数和商户秘钥共6个参数排序组合后生成密文,即sign签名
String signStr = WxPaySign.createSign(payinfo.getString("wxpay_key"),finalmap);
log.debug("signStr======"+signStr);
finalmap.addv("paySign",signStr);//将sign设置成请求参数
log.debug("finalmap:::" + Json.toJson(finalmap));
//返回以上设置的必须请求参数共6个
return finalmap;
}
以上代码已经尽量说明,不明白的可以再问我。官方文档是将所有参数以XML格式给的,此处是用nutz框架内置的NutMap包装所有参数,传到后台进行解析,其中NutMap实质就是Map;
此处强调,官方文档给了一个接口链接:URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder,一开始我没弄懂这个到底用来干嘛的,后来看了别人的经验才有所领悟,当然此处是nutz框架已经封装好了方法,这里直接调用,想看如何调用,请查看http://blog.csdn.net/aofavx/article/details/52220394
所有参数的名称、释义、示例、是否必须,在官方文档中都有指出,看文档就能明白的,这里不再解释,接下来就说说比较有歧义的地方;(这里只针对必须参数)
nonce_str——随机字符串——32位以内,数字,字母都ok,不必纠结
sign——签名——这是第一次出现签名的地方,此sign是将你所有的请求参数按照ASCII字典排序后,按照规定格式拼接成字符串,再使用MD5加密后生成,最后再加入到请求参数中发送到微信端接口;加密方式也可以用HMAC-SHA256方式
sign_type——你所用的加密sign的方式
body——商品详情——此处任意填写,建议此处值先使用英文测试
notify_url——微信异步调用的URL,用来返回用户支付的结果,此处填写的地址同样需要添加到JSAPI支付授权目录下,添加方式和之前一样
trade_type——交易类型——此处直接填写JSAPI,微信公众号支付
openid——用户标识——交易类型为JSAPI,此参数为必选
注意:sign的生成比较重要,请一定保证参数名包括其大小写下划线要与官方文档一致,参数值要符合标准,加密sign之前,要打印你所拼接的字符串,查看是否准确,发送请求数据之前请打印出来,查看所有参数及其值是否正确,另外所有参数的值均为String类型。
2-接收微信端返回的数据
请求参数发送之后,无论请求数据是否异常,微信端均返回XML格式数据,请自行解析;返回的数据中,其中return_code和result_code值均为SUCCESS时,表示你的请求成功了,如果失败,请参考return_msg的值,自行解决问题。再请求成功后,大家可以看到,其中有个参数名为sign,没错,这个也是签名,这是第二次出现签名的地方,不同的是这个签名是微信端返回的,它的作用主要是用来验证这些返回的数据是否是微信端返回的,官方文档中在提供的demo中有个方法是用来验证此类签名的,贴出来一点方法给大家,有兴趣的可以自己下载
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
所以此处的sign唯一的作用就是用来验证的,能看到SUCCESS就很开心了,这个验证就算了吧,我想尽快到下一步;
所以最重要的东西来了,我们之前费了那么大劲去发送请求,然后接收一堆这些参数,不可能就这点作用,当然,主角还是它:**prepay_id**,最主要的就是要拿到这个参数和它的值,官方文档中解释其为:预支付交易会话标识,有效期为2小时;其实就是预支付订单的id,只不过这个id是微信端生成的,人家只承认自己的id,主要用于下一步的请求参数。所以再接下来
3-发送交易请求参数,调起微信支付;
很重要、很关键、很激动的一步,因为这一步完成了,你就完成了99%的工作了,就能见到心仪已久的内个选择支付方式,输入密码或者指纹支付的弹框了!!!
此步骤所需要的必须参数一共有6个,其实所有参数也只有6个,以下分别说明:
appId——公众号id——就是之前用的那个appid,此处注意appId的I要大写
timeStamp——时间戳——不要想太多,直接给你代码: String.valueOf(System.currentTimeMillis()/1000); 直接拿去用,同时注意参数名的大小写
nonceStr——随机字符串——就直接用之前的生成方式生成即可,同时注意参数名大小写
package——订单详情扩展字符串——它的值比较特别,格式为:prepay_id=########;注意仅此参数名为全部小写字母
signType——签名方式——仅支持MD5,啥也别想,直接一个“MD5”OK了;
paySign——签名——再次注意,这是第三次出现签名的地方,此处的签名生成方式依然和第一次的签名一样,先排序组成特定字符串后再用MD5方式加密;
注意:现在来详细说明一下这个paySign的生成:什么都不必多说,我直接给你一个字符串格式:
String signStr="appId=XXX&nonceStr=XXX&package=prepay_id=XXX&signType=MD5&timeStamp=XXX&key=XXX";
请把字符串signStr中的所有XXX替换成你自己的值,可能会有人奇怪,上面的字符串参数中少了paySign,却多了一个key,说明你没有认真看官方文档中的签名生成方式。除了paySign这个参数,我们需要将之前说明的其它5个参数按照字典排序组好字符串,最后才能加上key这个参数,这个key就是之前设置的微信商户的秘钥,不知道的请查看本文档的第二步第3条的第3点,这里使用的就是那个秘钥。最后把这一串字符串MD5加密后,就生成了paySign
到此为止
,请将上面说的6个参数原封不动的封装发送到前台页面,前台调用接口的方法里的参数值,请全部从后台获取,请在调用接口之前打印你获取参数的形式是否准确的获取了后台的参数,避免因前端取值原因造成怀疑后台出错!我曾经因为此原因,在后台调试了一个上午,最后发现是页面取值的原因,请大家引以为戒!
前端页面的调用方法官方文档有写,这里贴出来说明一下
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jgfjsdfgsdg888",
"signType":"MD5", //微信签名方式:
"paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
请大家使用这个方式调用微信支付,参数请从后台发送到前台,方法最后有个回调函数function(res){},参数res是返回的请求结果,其中参数err_msg为"get_brand_wcpay_request:ok"时表示调用成功,这时才会弹出支付框,如果有调用失败的情况,可以alert(err_desc);来查看微信端返回的错误信息
微信订单的查看和微信退款等功能后续再完善,另外微信端会在用户支付完成后请求异步回调结果通知,请大家参考官方文档做好处理,及时返回信息给微信端。其中涉及到第四次出现参数sign,其作用依然和第二次出现的sign一样,用于信息真实性的验证。
如果有疑问的欢迎询问
最后祝大家开发公众号和支付顺利完成!!!