整个支付流程采用扫码支付的模式二:详细步骤
需要引入的依赖:
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.9.6</version> </dependency>相关的配置:
public class PayConfig implements WXPayConfig { private String appID; //公众账号ID private String mchID; //商户号 private String key; //生成签名的密钥 private int httpConnectTimeoutMs; //连接超时时间 private int httpReadTimeoutMs; //读取超时时间 private String notifyUrl;// 下单通知回调地址 @Override public InputStream getCertStream() { return null; } } @Configuration public class PayConfiguration { @Bean @ConfigurationProperties(prefix = "leyou.pay") public PayConfig payConfig() { return new PayConfig(); } @Bean public WXPay wxPay(PayConfig payConfig) { return new WXPay(payConfig, WXPayConstants.SignType.HMACSHA256); } }wxpay-sdk提供了WXPay用来生成预订单以及查询订单状态,需要传入WXPayConfig接口的实现以及签名的加密算法。
封装支付的工具类:
@Slf4j @Component public class PayHelper { @Autowired private PayConfig payConfig; @Autowired private WXPay wxPay; @Autowired private OrderDao orderDao; @Autowired private OrderStatusDao orderStatusDao; public String createPayUrl(Long orderId, Long totalPay, String desc) { try { Map<String, String> data = new HashMap<>(); // 商品描述 data.put("body", desc); // 订单号 data.put("out_trade_no", orderId.toString()); // 总金额,单位是分 data.put("total_fee", totalPay.toString()); // 调用微信支付的终端ip data.put("spbill_create_ip", "127.0.0.1"); // 回调地址 data.put("notify_url", payConfig.getNotifyUrl()); // 交易类型为扫码支付 data.put("trade_type", "NATIVE"); // 利用wxPay工具完成下单 Map<String, String> result = wxPay.unifiedOrder(data); // 判断通信标识和业务标识 isSuccess(result); // 校验签名 isValidSign(result); // 下单成功,获取支付链接 String url = result.get("code_url"); return url; } catch (Exception e) { log.error("【微信下单】 创建预交易订单异常", e); return null; } } public void isSuccess(Map<String, String> result) { // 判断通信标识 String returnCode = result.get("return_code"); if(FAIL.equals(returnCode)) { // 通信失败 log.error("【微信下单】 微信下单通信失败,失败原因:{}", result.get("return_msg")); throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL); } // 判断交易是否成功 String resultCode = result.get("result_code"); if(FAIL.equals(resultCode)) { // 交易失败 log.error("【微信下单】 微信下单交易失败,错误代码:{},错误代码描述:{}", result.get("err_code"), result.get("err_code_des")); throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL); } } public void isValidSign(Map<String, String> result) { try { // 重新生成签名 String sign1 = WXPayUtil.generateSignature(result, payConfig.getKey(), SignType.HMACSHA256); String sign2 = WXPayUtil.generateSignature(result, payConfig.getKey(), SignType.MD5); // 和传过来的签名进行比较 String sign = result.get("sign"); if(!StringUtils.equals(sign1, sign) && !StringUtils.equals(sign2, sign)) { throw new LyException(ExceptionEnum.INVALID_SIGN_ERROR); } } catch (Exception e) { throw new LyException(ExceptionEnum.INVALID_SIGN_ERROR); } } public PayStateEnum queryPayState(Long orderId) { try { Map<String, String> data = new HashMap<>(); // 订单号 data.put("out_trade_no", orderId.toString()); // 利用wxPay工具完成订单查询 Map<String, String> result = wxPay.orderQuery(data); // 判断通信标识和业务标识 isSuccess(result); // 校验签名 isValidSign(result); // 校验金额 String totalFeeStr = result.get("total_fee"); String tradeNo = result.get("out_trade_no"); if(StringUtils.isBlank(totalFeeStr) || StringUtils.isBlank(tradeNo)) { throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM); } Long totalFee = Long.valueOf(totalFeeStr); Order order = orderDao.selectByPrimaryKey(orderId); if(totalFee != /*order.getActualPay()*/ 1L) { throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM); } // 判断交易状态 String tradeState = result.get("trade_state"); // 如果为成功,更新订单状态 if(StringUtils.equals(tradeState, "SUCCESS")) { OrderStatus orderStatus = new OrderStatus(); orderStatus.setOrderId(orderId); orderStatus.setStatus(OrderStatusEnum.PAY_UP.getCode()); orderStatus.setPaymentTime(new Date()); int count = orderStatusDao.updateByPrimaryKeySelective(orderStatus); if(count != 1) { throw new LyException(ExceptionEnum.ORDER_STATUS_UPDATE_ERROR); } // 返回支付成功 return PayStateEnum.SUCCESS; } if(StringUtils.equals(tradeState, "NOTPAY") || StringUtils.equals(tradeState, "USERPAYING")) { return PayStateEnum.NOT_PAY; } // 如果为其他状态,则返回支付失败 return PayStateEnum.FAIL; } catch (Exception e) { return PayStateEnum.NOT_PAY; } } }工具类中包括了生成预订单、校验通信与业务是否成功、签名是否一致以及订单支付状态的查询。
用户支付之前需要生成支付需要的二维码链接。
/** * 生成微信支付链接 * @param orderId * @return */ @GetMapping("/url/{id}") public ResponseEntity<String> createPayUrl(@PathVariable("id") Long orderId) { return ResponseEntity.ok(orderService.createPayUrl(orderId)); } public String createPayUrl(Long orderId) { Order order = queryOrderById(orderId); // 判断订单状态 Integer status = order.getOrderStatus().getStatus(); if(status != OrderStatusEnum.UN_PAY.getCode()) { throw new LyException(ExceptionEnum.ORDER_STATUS_ERROR); } // 生成微信支付链接 Long actualPay = /*order.getActualPay()*/ 1L; String desc = order.getOrderDetails().get(0).getTitle(); String payUrl = payHelper.createPayUrl(orderId, actualPay, desc); if(payUrl == null) { throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL); } return payUrl; }当用户扫码支付成功后,微信会将消息传给我们下订单时提供的回调地址,在回调完成后需要给微信发送成功收到回调信息的响应,否则微信将会继续发送请求。如果在本地测试,需要进行外网穿透,可以使用natapp。
/** * 微信支付成功回调 * @param result * @return */ @PostMapping(value = "/pay", produces = "application/xml") public Map<String, String> handleNotify(@RequestBody Map<String, String> result) { log.info("【支付回调】 接受微信支付回调, 结果:{}", result); orderService.handleNotify(result); Map<String, String> msg = new HashMap<>(); msg.put("return_code", "SUCCESS"); msg.put("return_msg", "OK"); return msg; } public void handleNotify(Map<String, String> result) { // 1 判断通信标识和业务标识 payHelper.isSuccess(result); // 2 校验签名 payHelper.isValidSign(result); // 3 校验金额 String totalFeeStr = result.get("total_fee"); String tradeNo = result.get("out_trade_no"); if(StringUtils.isBlank(totalFeeStr) || StringUtils.isBlank(tradeNo)) { throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM); } Long totalFee = Long.valueOf(totalFeeStr); Long orderId = Long.valueOf(tradeNo); Order order = orderDao.selectByPrimaryKey(orderId); if(totalFee != /*order.getActualPay()*/ 1L) { throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM); } // 4 更新订单状态 OrderStatus orderStatus = new OrderStatus(); orderStatus.setOrderId(orderId); orderStatus.setStatus(OrderStatusEnum.PAY_UP.getCode()); orderStatus.setPaymentTime(new Date()); int count = orderStatusDao.updateByPrimaryKeySelective(orderStatus); if(count != 1) { throw new LyException(ExceptionEnum.ORDER_STATUS_UPDATE_ERROR); } log.info("【支付回调】 订单支付成功! 订单id:{}", orderId); }由于微信使用的是xml进行发送和接收,因此需要引入解析xml和序列化的依赖,@RequestBody会将接收到的xml解析为java对象,produces = "application/xml"指定了java对象最终会被序列化为xml响应回去。
生成预订单后,前端会生成支付二维码,同时会不断检测订单支付的状态,如果用户的支付状态为成功,则会跳转到支付成功的页面,因此后端需要提供订单支付状态的查询接口。
/** * 查询订单的支付状态 * @param orderId * @return */ @GetMapping("/state/{id}") public ResponseEntity<Integer> queryPayState(@PathVariable("id") Long orderId) { return ResponseEntity.ok(orderService.queryPayState(orderId).getValue()); } public PayStateEnum queryPayState(Long orderId) { OrderStatus orderStatus = orderStatusDao.selectByPrimaryKey(orderId); // 如果订单状态不是未付款状态,则微信付款已经成功 if(orderStatus.getStatus() != OrderStatusEnum.UN_PAY.getCode()) { return PayStateEnum.SUCCESS; } // 如果订单状态是未付款状态,微信付款可能已经成功,必须去微信查询支付状态 return payHelper.queryPayState(orderId); }最后,一个简单的支付基本流程就跑完了。。