微信小程序支付(Java后端)
一、小程序支付的交互图如下
按住ctrl点击 微信支付平台开发文档
二、准备工作
-
第一步:在pom文件中导入微信支付SDK
- 有可能自动下载不了,可以到微信支付平台下载手动导入maven仓库
- SDK下载地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
-
第二步:创造一个配置类,填入必要信息,如图
-
- 支付成功后的回调url必须是公网可以访问的
-
-
第三步:创建包结构com.github.wxpay.sdk,然后创建一个类继承WXPayConfig
- 实现抽象类中的方法,将配置类中的小程序ID、商户号、商户秘钥一一填入,getWXPayDomain()为固定写法
- 如图:
-
第四步:在启动类中注入上一步的配置类和RestTemplate,RestTemplate是用来发送请求的
-
-
第五步:添加转换工具类,作用:将输入流转换为xml字符串
-
/** * 转换工具类 */ public class ConvertUtils { /** * 输入流转换为xml字符串 * @param inputStream * @return */ public static String convertToString(InputStream inputStream) throws IOException { ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inputStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inputStream.close(); String result = new String(outSteam.toByteArray(), "utf-8"); return result; } }
-
三、实现小程序支付步骤
-
第一步:获取openid
- 登录流程图
- 在微信小程序登录时,调用登录API获取登录凭证(code),通过code获取用户登录信息,包含用户的唯一标识(openid)和本次登录的会话秘钥(session key),将openid存入缓存
- 小程序端:
//在登录方法内 wx.login({ success (res) { if (res.code) { //拿到登录凭证code //将code作为参数调用后台接口 wx.request({ //此处url是后台获取openid的接口 url: 'https://localhost:9091/wx/login/' + res.code,// success: function(res) { if(res.data.openid){ //如果响应结果包含openid,将其存入缓存 wx.setStorage({ key:"openid", data: res.data.openid }) //提示框 wx.showToast({ title: '登录成功', icon: 'success', duration: 2000 }); } } }) } else { console.log('登录失败!' + res.errMsg) } } })
- 后台:
//在controller层 @RequestMapping("/wx") @RestController public class WXPayController { @Autowired private RestTemplate restTemplate; @PostMapping("/login/{code}") public String wxLogin(@PathVariable("code") String code) { //接收到登录凭证,拼接url String url = MyWxPayConfig.get_openid_url //获取openid接口 + "?appid=" + MyWxPayConfig.appid //小程序ID + "&secret=" + MyWxPayConfig.appSecret //小程序秘钥 + "&js_code=" + code + "&grant_type=authorization_code"; //发送请求,将响应数据返回给前端 String jsonData = this.restTemplate.getForObject(url, String.class); return jsonData; } }
- 登录流程图
-
第二步:生成商户订单
- 在点击支付后,调用后台的新增订单接口,返回订单的编号(代码略…)
-
第三步:
-
生成商户订单,获取订单编号后,将订单编号和金额作为参数,传入小程序端的支付方法wxPay(),wxPay是自定义的方法
-
小程序端:
//参数:订单编号,付款金额 wxPay(orderNo,totalMoney) { console.log("统一下单接口开始执行") //从缓存中取出openid wx.getStorage({ key: 'openid', success (res) { wx.request({ //此处url是后台统一下单的接口 url: 'https://localhost:9091/wx/pay', data: { openid: res.data, orderNO: orderNo, totalMoney: tatalmoney }, header: { 'content-type': 'application/x-www-form-urlencoded' }, success: function(res) { //根据响应数据判断是否执行成功 if(res.data.status_code == '00000'){ //发起微信支付 wx.requestPayment({ provider: 'wxpay', timeStamp: res.data.object.timeStamp, nonceStr: res.data.object.nonceStr, package: res.data.object.package, signType: res.data.object.signType, paySign: res.data.object.paySign, success: function (res) { //执行成功,打印 console.log('success:' + JSON.stringify(res)); }, fail: function (err) { //执行失败,打印 console.log('fail:' + JSON.stringify(err)); } }) }else{ //失败,提示框 wx.showToast({ title: res.data.msg, icon: 'none', }); } } }) } }) }
-
后台:
-
//controller层 //后台统一下单接口 @GetMapping("/pay") @ResponseBody public Result pay(HttpServletRequest request,@RequestParam String openid, @RequestParam String orderNo, @RequestParam Double totalMoney) throws Exception { // 获取真实请求ip地址,避免获取代理ip String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } if (ip.indexOf(",") != -1) { String[] ips = ip.split(","); ip = ips[0].trim(); } return wxPayService.wxPay(openid, orderNo, totalMoney, ip); }
-
//service层 @Override public Result wxPay(String openid, String orderNo, Double totalMoney, String ip) { try { // 1. 拼接统⼀下单地址参数 Map<String, String> paraMap = new HashMap<String, String>(); paraMap.put("openid",openid);//用户标识 paraMap.put("body", "*****"); // 商品描述 paraMap.put("out_trade_no", orderNo);// 订单号 //金额转换 BigDecimal payMoney = new BigDecimal("0.01");//正式使用时传入totalMoney BigDecimal fen = payMoney.multiply(new BigDecimal("100")); //1.00 fen = fen.setScale(0, BigDecimal.ROUND_UP); paraMap.put("total_fee", fen); // ⽀付⾦额,单位分,即0.01元 paraMap.put("spbill_create_ip", ip);//终端ip paraMap.put("notify_url","http://zq32586844.qicp.vip/wx/notify");//支付结果通知地址 paraMap.put("trade_type", "JSAPI"); // 交易类型 //2.发送post请求"统⼀下单接⼝", 返回预⽀付id:prepay_id Map<String, String> map = wxPay.unifiedOrder(paraMap); String prepayId = (String) map.get("prepay_id"); //3.将数据组合,再次签名 Map<String, String> payMap = new HashMap<String, String>(); payMap.put("appId", MyWxPayConfig.appid); payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + ""); payMap.put("nonceStr", WXPayUtil.generateNonceStr()); payMap.put("signType", WXPayConstants.HMACSHA256); payMap.put("package", "prepay_id=" + prepayId); //通过appId, timeStamp, nonceStr, signType, package及商户密钥进⾏key=value形式拼接并加密 String paySign = WXPayUtil.generateSignature(payMap, MyWxPayConfig.key,WXPayConstants.SignType.HMACSHA256); payMap.put("paySign", paySign); //4.将参数传给前端 return ResultGenerator.genSuccessResult("调用统一下单接口,成功!", payMap); } catch (Exception e) { e.printStackTrace(); return ResultGenerator.genFailResult("调用统一下单接口,失败!"); } }
-
这样就可以正常支付了
-
-
第四步:接收微信推送的支付结果通知
-
请求路径为,在配置类中留的支付成功回调url
-
@RequestMapping("/notify") public void notifyLogic(HttpServletRequest request, HttpServletResponse response) { try { //1.输入流转换为字符串 String xml = ConvertUtils.convertToString(request.getInputStream()); //2.基于微信发送的通知内容,完成后续的业务逻辑处理 //使用微信支付sdk中的工具类,将xml转换成map Map<String, String> map = WXPayUtil.xmlToMap(xml); if ("SUCCESS".equals(map.get("result_code"))) { //支付成功回调 //给微信一个结果通知 response.setContentType("text/xml"); String data="<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; response.getWriter().write(data); //执行支付成功后的业务逻辑 //例如:修改订单的支付状态为已支付 } } catch (Exception e) { e.printStackTrace(); } }
-