最近在研究微信公众号的支付开发,一开始对着开发文档各种懵,也自然而然地跳入了各种坑,现在把整个开发过程简略地做个记录。
1.开发环境准备
首先要有一个微信服务号,订阅号是不能开通微信支付的。微信公众号申请微信支付后,接着申请微信支付商户平台,公众号上面已经标明“公众平台微信支付公众号支付授权目录、扫码支付回调URL配置入口已于8月1日迁移至商户平台(pay.weixin.qq.com)。迁移后,原有配置数据不会受影响,你可在商户平台查看和配置。带来的不便敬请谅解”。所以后续开发所需要的配置项基本都在商户平台中进行。
2.开发开始
首先还是得先看下微信官方提供的公众号支付开发文档,大概对整个开发的流程有个理解。主要是看API列表
开发文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
首先看到的就是统一下单,没错,支付开发的第一步就是实现统一下单。
2.1 构造统一下单对象
首先,构造一个统一下单对象,对象的字段包括里面的必填字段,有额外需要的可以自己添加。特别注意里面对请求参数的描述。
/** * 统一下单 */ @XmlRootElement(name = "xml") public class UnifiedOrder { /** * 公众账号ID */ private String appid; /** * 商户号 */ private String mch_id; /** * 附加数据(说明) */ private String attach; /** * 商品描述 */ private String body; /** * 随机串 */ private String nonce_str; /** * 通知地址 */ private String notify_url; /** * 用户标识 */ private String openid; /** * 商户订单号 */ private String out_trade_no; /** * 终端IP(用户) */ private String spbill_create_ip; /** * 总金额 */ private Integer total_fee; /** * 交易类型 */ private String trade_type; /** * 签名 */ private String sign; /** * 签名方式 */ private String signType; /** * WEB */ private String device_info; /** * 统一下单接口 */ private String prepay_id; /** * 时间戳 * @return */ private String timeStamp;后台创建一个接口,后面H5支付的时候要调用,填充这个统一下单对象,appid,mchid等可以封装成参数。微信需要接收的是xml的数据。所以我们还得把封装好的对象转化成xml。官网的提供的demo也有utils方法。demo在这下载。
2.2 获取openid,对于微信公众号支付,统一下单时openid是必须的。因此在统一下单之前还得获取openid。首先要到公众号后台进行授权配置。官网说明:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
通过
公众号可以通过微信网页授权机制,来获取用户基本信息,一般只要scope为snsapi_userinfo,也是静默授权即可。
如果使用的是默认授权,那么就不会出现用户确认的提示。如图所示
获取openid,一般是通过js调起微信授权。所以可在调起支付的页面进行js授权,url配置公众号的appid,后台回调地址,授权成功后,微信会调用这个接口,并给我们返回用户的信息。
window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=公众号的appida&redirect_uri=回调接口(浏览器能访问的接口)&response_type=code&scope=snsapi_base&state=ssssqqqq#wechat_redirect";接口调用成功后,微信调用后台接口,在这我们要根据微信返回的code来获取用户信息,并将其进行转换,然后放到session中,确保获取的是当前登录用户的信息。以下为上面所写的回调接口。url配置appid,secret为公众号秘钥即AppSecret,可在公众号后台的基本配置中进行查看。code为微信授权返回的code。
/** * 获取openid * * @return */ @RequestMapping("auth") public String auth(String code, String state, HttpServletRequest request) { HttpSession session = request.getSession(); String format = MessageFormat.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code", Constant.APPID, Constant.SECRET, code); String s = HttpUtils.get(format); Map map = JSON.parseObject(s, Map.class); //String userInfo = MessageFormat.format(url_user, Constant.APPID, Constant.SECRET, code); SecurityUser currentUser = SecurityUserUtils.getCurrentUser(request); if (currentUser != null) { session.setAttribute("openid", map.get("openid")); } return "redirect:http://www.XXX.com/index.html"; }到这里,openid就已经获取到了。然后统一下单对象setOpenid(session.getAttribute("openid")。构造一个完整的统一下单对象。
//构建统一下单对象 UnifiedOrder unifiedOrder = new UnifiedOrder(); unifiedOrder.setAppid(Constant.APPID); unifiedOrder.setDevice_info(Constant.DEVICE_INFO); unifiedOrder.setMch_id(Constant.MCHID); unifiedOrder.setNonce_str(Constant.NONCE_STR); unifiedOrder.setBody("充值" + uiorder.getTotalFee() + "元"); unifiedOrder.setAttach("自动充值"); unifiedOrder.setSpbill_create_ip(request.getRemoteAddr()); unifiedOrder.setNotify_url(Constant.NOTIFY_URL); unifiedOrder.setTrade_type(Constant.TRADE_TYPE); unifiedOrder.setOpenid((String) request.getSession().getAttribute("openid")); BigDecimal totalFee = uiorder.getTotalFee(); unifiedOrder.setTotal_fee(totalFee.multiply(new BigDecimal(100)).intValue()); unifiedOrder.setOut_trade_no(SequenceUtils.generateOrderNo(date));2.3 第一次签名
统一下单之前还得对统一下单对象设置签名,根据官方文档的说明进行签名生成。key在商户平台的api秘钥中进行设置
以下提供签名生成的方法。MD5加密等方法都可以在官网的sdk例子中找得到。
签名生成完成,这时候我们把统一下单对象打包成xml。发送给微信的接口
https://api.mch.weixin.qq.com/pay/unifiedorder
SignUtils.sign(unifiedOrder); String s = XmlParserUtil.object2Xml(unifiedOrder); //统一下单 String s1 = HttpUtils.postXML("https://api.mch.weixin.qq.com/pay/unifiedorder", s); UnifiedOrder order = XmlParserUtil.xml2Object(s1, UnifiedOrder.class);HttpUtils的postXML方法如下
public static String postXML(String url, String xml) { RequestBody body = RequestBody.create(XML, xml); Request request = new Request.Builder() .post(body) .url(url) .addHeader("Accept", "application/xml") .build(); Call call = httpClient.newCall(request); try { Response response1 = call.execute(); return response1.body().string(); } catch (IOException e) { e.printStackTrace(); } return null; }到了这里,统一下单就完成了。此时微信给我们返回的order对象就包括一个很重要的字段
prepay_id