小程序 微信支付官方文档
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_11&index=2
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml
前提
在进行对接微信支付之前,我们首先需要将以下几点准备好:
申请APPID申请商户号小程序开通微信支付,绑定已经申请好的商户号。登录小程序后台(mp.weixin.qq.com)。点击左侧导航栏的微信支付,在页面中进行开通。(开通申请要求小程序已发布上线)注意事项
appid必须为最后拉起收银台的小程序appid;mch_id为和appid成对绑定的支付商户号,收款资金会进入该商户号;trade_type请填写JSAPI;openid为appid对应的用户标识,即使用wx.login接口获得的openid。本文主要记录后端步骤,前端步骤无非就是获取后端数据然后调用提供的API进行支付,大家可自行查看官方文档。
1. 整体流程
小程序支付的交互图如下:清晰图片可以到官网查看.
商户系统和微信支付系统主要交互:
1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2、商户server调用支付统一下单,api参见公共api【统一下单API】
3、商户server调用再次签名,api参见公共api【再次签名】
4、商户server接收支付通知,api参见公共api【支付结果通知API】
5、商户server查询支付结果,如未收到支付通知的情况,商户后台系统可调用【查询订单API】 (查单实现可参考:支付回调和查单实现指引)
2. 后端项目搭建
大致了解流程之后,我们就可以进行代码的编写了,首先大家自行创建一个Spring Boot项目即可。这里就不演示了,大家自行根据自己的需求搭建即可,需要注意的是我们需要添加微信支付的sdk以及相关依赖。
<!--微信支付请求--> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.13</version> </dependency> <dependency> <groupId>org.jdom</groupId> <artifactId>jdom</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.10</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-avro</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.6</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency>
3. 获取open_id
我们在小程序端通过login方法获取code,然后调用我们编写的接口请求微信服务端获取open_id。
该步骤具体可以查看 微信小程序对接微信登录 。
wx.login({ success (res) { if (res.code) { //发起网络请求 wx.request({ url: 'https://example.com/onLogin', data: { code: res.code } }) } else { console.log('登录失败!' + res.errMsg) } }})
4. 小程序调起支付API
小程序调起支付数据签名字段列表:
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appId | 是 | String | wxd678efh567hg6787 | 微信分配的小程序ID |
时间戳 | timeStamp | 是 | String | 1490840662 | 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间 |
随机串 | nonceStr | 是 | String | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
数据包 | package | 是 | String | prepay_id=wx2017033010242291fcfe0db70013231072 | 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072 |
签名方式 | signType | 是 | String | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 |
举例如下:
paySign = MD5(appId=wxd678efh567hg6787&nonceStr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS&package=prepay_id=wx2017033010242291fcfe0db70013231072&signType=MD5&timeStamp=1490840662&key=qazwsxedcrfvtgbyhnujmikolp111111) = 22D9B4E54AB1950F51E0649E8810ACD6
从官方文档给出的参数可以看出,小程序调用支付API,有一些参数需要我们从后端接口返回,所以我们来进行后端支付接口的编写。
5. 下单及回调接口
实现步骤
定义配置类 保存支付相关参数定义支付接口,组装数据发起请求小程序调用支付接口相关配置类,签名,随机字符串的代码在文末给出,本文就不贴出来了。
@RestController@RequestMapping(value = "/api/v1")public class WXPayController { /** * 下单 */ @RequestMapping( value = "/weixin/payment", method = RequestMethod.POST ) public Map payment() { Map<String, Object> map = new HashMap<>(); String money = "10"; String title = "商品名字"; try { OrderInfo order = new OrderInfo(); order.setAppid(Configure.getAppID()); order.setMch_id(Configure.getMch_id()); order.setNonce_str(RandomStringGenerator.getRandomStringByLength(32)); order.setBody(title); order.setOut_trade_no(RandomStringGenerator.getRandomStringByLength(32)); order.setTotal_fee(Integer.parseInt(money)); // 该金钱其实10 是 0.1元 order.setSpbill_create_ip("127.0.0.1"); order.setNotify_url(Configure.getNotify_url()); order.setTrade_type(Configure.getTrade_type()); //这里直接使用当前用户的openid order.setOpenid("ohfYG5O2mdXZLoszDAWwqtodOZRM"); order.setSign_type("MD5"); //生成签名 String sign = Signature.getSign(order); order.setSign(sign); String result = HttpRequest.sendPost(Configure.getUrl(), order); System.out.println(result); XStream xStream = new XStream(); xStream.alias("xml", OrderReturnInfo.class); OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result); // 二次签名 if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) { SignInfo signInfo = new SignInfo(); signInfo.setAppId(Configure.getAppID()); long time = System.currentTimeMillis() / 1000; signInfo.setTimeStamp(String.valueOf(time)); signInfo.setNonceStr(RandomStringGenerator.getRandomStringByLength(32)); signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id()); signInfo.setSignType("MD5"); //生成签名 String sign1 = Signature.getSign(signInfo); Map<String, String> payInfo = new HashMap<>(); payInfo.put("timeStamp", signInfo.getTimeStamp()); payInfo.put("nonceStr", signInfo.getNonceStr()); payInfo.put("package", signInfo.getRepay_id()); payInfo.put("signType", signInfo.getSignType()); payInfo.put("paySign", sign1); map.put("status", 200); map.put("msg", "统一下单成功!"); map.put("data", payInfo); // 此处可以写唤起支付前的业务逻辑 // 业务逻辑结束 回传给小程序端唤起支付 return map; } map.put("status", 500); map.put("msg", "统一下单失败!"); map.put("data", null); return map; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 微信小程序支付成功回调函数 */ @RequestMapping(value = "/weixin/callback") public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } br.close(); //sb为微信返回的xml String notityXml = sb.toString(); String resXml = ""; System.out.println("接收到的报文:" + notityXml); Map map = PayUtil.doXMLParse(notityXml); String returnCode = (String) map.get("return_code"); if ("SUCCESS".equals(returnCode)) { //验证签名是否正确 Map<String, String> validParams = PayUtil.paraFilter(map); //回调验签时需要去除sign和空值参数 String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 String sign = PayUtil.sign(validStr, Configure.getKey(), "utf-8").toUpperCase();//拼装生成服务器端验证的签名 // 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了 //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等 if (sign.equals(map.get("sign"))) { /**此处添加自己的业务逻辑代码start**/ // bla bla bla.... /**此处添加自己的业务逻辑代码end**/ //通知微信服务器已经支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { System.out.println("微信支付回调失败!签名不一致"); } } else { resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; } System.out.println(resXml); System.out.println("微信支付回调数据结束"); BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); }}
6. 项目地址
微信支付案例
具体代码大家自行查看,相关商户号没有给出,大家自行申请。