SpringBoot整合微信支付(Native)【V2版本】

1.开发前的准备条件 【 微信支付 NATIVE支付模式 (V2版本)】

  • 开发环境

    1. Spring Boot Version: 2.1.17.RELEASE
    2. jdk 8
    
  • 准备工作

    • maven坐标 (version 3.5.4.B)
      <dependency>
          <groupId>com.github.binarywang</groupId>
          <artifactId>wx-java-pay-spring-boot-starter</artifactId>
          <version>${wx.pay.version}</version>
       </dependency>
      
    • 微信开发相关配置
      1.appId,mchId,mchKey [这些都需要去微信平台获取]
      2.配置一个可以直接外网访问的url 用作微信支付后微信平台回调我们的接口
      
  • 支付流程图

    支付流程图

  • 流程

     1.商户后台系统根据用户选购的商品生成订单。
     
     2.用户确认支付后调用微信支付【统一下单API】生成预支付交易;
     
     3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
     
     4.商户后台系统根据返回的code_url生成二维码。
     
     5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
     
     6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
     
     7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
     
     8.微信支付系统根据用户授权完成支付交易。
     
     9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
     
     10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
    
     11.未收到支付通知的情况,商户后台系统调用【查询订单API】。
    
     12.商户确认订单已支付后给用户发货。
    

2.开发步骤

  • yml配置文件 (替换自己的appId,mchId,mchKey,回调地址一定要可以直接外网访问通,不能有查询参数限制,不然微信无法通知自己的业务系统)
    wx:
      pay:
        appId: wx538891b3add78wew
        mchId: 1644361212
        mchKey: 5b2b6687cfdde9cb5e2d0b36d95f6548
        subAppId:
        subMchId:
        keyPath: 
        notifyUrl: https://payapi.wx.com/wx/order/notify
    
  • 编写配置文件
    • properties
      /**
       * @author:kyrie
       * @date:2023/5/16 13:21
       * @Description: 微信支付配置属性
       **/
      @Data
      @Component
      @ConfigurationProperties(prefix = "wx.pay")
      public class WxPayProperties {
          /**
           * 设置微信公众号或者小程序等的appid.
           */
          private String appId;
      
          /**
           * 微信支付商户号.
           */
          private String mchId;
      
          /**
           * 微信支付商户密钥.
           */
          private String mchKey;
      
          /**
           * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除.
           */
          private String subAppId;
      
          /**
           * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除.
           */
          private String subMchId;
      
          /**
           * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定.
           */
          private String keyPath;
      
          private String notifyUrl;
      
      }
      
    • javaConfig
      /**
       * @author: kyrie
       * @date:2023/5/11 13:02
       * @Description: 微信支付配置类
       **/
      @Configuration
      public class WxPayConfiguration {
      
          @Autowired
          private WxPayProperties payProperties;
      
          @Bean
          public WxPayService wxPayService() {
              WxPayServiceImpl wxPayService = new WxPayServiceImpl();
      
              WxPayConfig payConfig = new WxPayConfig();
      
              payConfig.setAppId(payProperties.getAppId());
              payConfig.setMchId(payProperties.getMchId());
              payConfig.setMchKey(payProperties.getMchKey());
              payConfig.setKeyPath(payProperties.getKeyPath());
              payConfig.setNotifyUrl(payProperties.getNotifyUrl());
      
              wxPayService.setConfig(payConfig);
      
              return wxPayService;
          }
      
      }
      
  • 编写业务
    • 创建订单
      --------------------controller--------------------
      /**
       * 创建订单【微信】
       *
       * @param orderRequest 请求对象
       * @return result
       */
      @ApiOperation("创建订单【微信】")
      @Log(title = "创建订单【微信】", businessType = BusinessTypeEnum.INSERT)
      @PostMapping("/native")
      @ApiTimeStatistics
      public AjaxResult createNativeOrder(@RequestBody @Validated OrderNativeCreatRequest  orderRequest) {
          OrderNativeCreatedResponse response = orderService.handlerNativeCreateOrder(orderRequest);
          return AjaxResult.success(response);
      } 
      --------------------dto--------------------
      /**
       * @author:kyrie
       * @date:2023/5/17 16:25
       * @Description: 请求对象
       **/
      @Data
      public class OrderNativeCreatRequest implements Serializable {
      
          private static final long serialVersionUID = -7961794957591750053L;
      
          @NotBlank(message = "订单号不能为空")
          private String outTradeNo;
      }
      --------------------serviceImpl--------------------
      /**
       * 微信创建订单
       *
       * @param orderRequest 请求对象
       * @return 返回对象
       */
      @Transactional(rollbackFor = Exception.class)
      @Override
      public OrderNativeCreatedResponse handlerNativeCreateOrder(OrderNativeCreatRequest orderRequest) {
      	 // 我这的outTradeNo是我前置的服务创建的,就是先创建了自己业务的一个订单对象,这个no要确保唯一,我之前使用的是MongoDB的Id生成策略生成的,这里可以自己自定义 
          OrderNativeCreatedResponse result;
          String outTradeNo = orderRequest.getOutTradeNo();
          Order order = null;
      	// 单服务的话这里可以直接使用RT自旋锁解决并发问题(ReentrantLock)
      	// 没必要采用这种Redisson的分布式lock
          locker.lock(outTradeNo, Constants.REDIS_TIMEOUT);
          try {
          	// 前置校验查询订单是否已经存在 就是一个getById()
              order = selectOrderByOrderNo(outTradeNo);
              Assert.notNull(order, "创建订单异常:订单不存在");
      		// 构建下单请求对象 这里我使用的是wxjava工具包 直接封装好了这个对象 sign和时间戳无需自己生成
              WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest();
              wxPayUnifiedOrderRequest.setBody(order.getProductDescription() + "_" + order.getEnterpriseName());
              wxPayUnifiedOrderRequest.setOutTradeNo(outTradeNo);
              // 金额需要转成分 微信是认分的所以不能有小数点 直接简单点*100
              wxPayUnifiedOrderRequest.setTotalFee(Integer.valueOf(MoneyUtil.getMoney(order.getOrderMoney().toString())));
              // 获取真实的ip 直接找个工具类就行
              wxPayUnifiedOrderRequest.setSpbillCreateIp(IpUtils.getRealIP(ServletUtils.getRequest()));
              wxPayUnifiedOrderRequest.setTradeType(com.github.binarywang.wxpay.constant.WxPayConstants.TradeType.NATIVE);
              // 自己商品的id 这里我是瞎写的 可以自己根据自己业务修改
              wxPayUnifiedOrderRequest.setProductId("12235413214070356458058");
              // 这个我一开始没写 按照wxjava的配置 没有设置好像会去加载config的默认配置【我觉得没必要写】
              wxPayUnifiedOrderRequest.setNotifyUrl(notifyUrl);
              // 调用封装的wxPayService的方法即可【v2版本的都是xml交互,v3是json交互 ,所以不一样,这里wxjava已经写好了现成的bean转xml,直接用即可】
              WxPayUnifiedOrderResult wxOrderResponse = wxPayService.unifiedOrder(wxPayUnifiedOrderRequest);
              // 可能是啥情况error了直接订单失败 抛异常
              if (ObjectUtil.isNull(wxOrderResponse)) {
                  order.setOrderStatus(WxPayStatusEnum.PAY_FAIL.getPayStatusCode());
                  updateById(order);
                  throw WxPayException.newBuilder().returnMsg("创建订单异常:无返回数据").build();
              } else {
                  // 预支付id【可以不存,因为好多微信支付接口都可以用你自己的那个no去调用,就相当于你的orderNo和微信的内部的一个id是对应的,这样双方就可以通了】
                  order.setPrePayId(wxOrderResponse.getPrepayId());
                  order.setOrderStatus(WxPayStatusEnum.PAY_ING.getPayStatusCode());
                  updateById(order);
              }
      		// 返回前端展示的数据而已 【没啥】
              result = new OrderNativeCreatedResponse();
              result.setPrepayId(wxOrderResponse.getPrepayId());
              result.setCodeUrl(wxOrderResponse.getCodeURL());
              // hutool 工具类生成base64的字符串
              String qrCode = QrCodeUtil.generateAsBase64(wxOrderResponse.getCodeURL(), QrConfig.create(), "jpg");
              result.setQrCode(qrCode);
              result.setReturnUrl(order.getReturnUrl());
              result.setBasiUrl(order.getBasiUrl());
              result.setOrderId(order.getOrderId());
              result.setOrderNo(order.getOrderNo());
              result.setProductDescription(order.getProductDescription());
              result.setEnterpriseName(order.getEnterpriseName());
          } catch (WxPayException e) {
          	// 异常log
              log.error("OrderServiceImpl--handlerNativeCreateOrder===error---->{}", e.getMessage());
              if (ObjectUtil.isNotNull(order)) {
                  order.setOrderStatus(WxPayStatusEnum.PAY_FAIL.getPayStatusCode());
                  updateById(order);
              }
              throw new CustomException("创建订单异常:" + e.getReturnMsg());
          } finally {
              // 释放锁
              locker.unlock(outTradeNo);
          }
      	// return
          return result;
      }
      
    • 查询订单
      --------------------controller--------------------
      /**
       * 根据订单no查询订单号
       *
       * @param orderQueryRequest 请求对象
       * @return result
       */
      @ApiOperation("查询订单")
      @Log(title = "查询订单", businessType = BusinessTypeEnum.UPDATE)
      @PostMapping("/query")
      @ApiTimeStatistics
      public AjaxResult queryOrder(@RequestBody @Validated OrderQueryRequest orderQueryRequest) {
          OrderQueryResponse response = orderService.queryOrder(orderQueryRequest);
          return AjaxResult.success(response);
      }
      
      --------------------dto--------------------
      /**
       * @author:kyrie
       * @date:2023/5/17 16:25
       * @Description: 请求对象
       **/
      @Data
      public class OrderQueryRequest implements Serializable {
      
          private static final long serialVersionUID = 2516981824084563768L;
      
          @NotBlank(message = "订单号不能为空")
          private String outTradeNo;
      }
      --------------------serviceImpl--------------------
      /**
       * 查询订单
       *
       * @param orderQueryRequest 订单号
       * @return 返回对象
       */
      @Override
      public OrderQueryResponse queryOrder(OrderQueryRequest orderQueryRequest) {
          OrderQueryResponse response = new OrderQueryResponse();
      
          String outTradeNo = orderQueryRequest.getOutTradeNo();
          locker.lock(outTradeNo, Constants.REDIS_TIMEOUT);
          try {
              // 再次去查一下order的状态
              Order order = selectOrderByOrderNo(outTradeNo);
              response.setOrderId(order.getOrderId());
              response.setOrderNo(order.getOrderNo());
              response.setReturnUrl(order.getReturnUrl());
              response.setBasiUrl(order.getBasiUrl());
              response.setValidityDateNow(order.getValidityDateNow());
              response.setValidityDateAfter(order.getValidityDateAfter());
              response.setProductDescription(order.getProductDescription());
              response.setMoney(order.getOrderMoney());
              // 自己业务系统中已经成功或者事变  直接返回就行 没必要调微信接口
              if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus()) || WxPayStatusEnum.PAY_FAIL.getPayStatusCode().equals(order.getOrderStatus()) || WxPayStatusEnum.CLOSED.getPayStatusCode().equals(order.getOrderStatus())) {
                  response.setOrderStatus(order.getOrderStatus());
              } else {
              	// 这就是拿那个订单号去调微信的查询接口
                  WxPayOrderQueryResult result = wxPayService.queryOrder(null, outTradeNo);
                  if (ObjectUtil.isNotNull(result)) {
                  	// 返回值
                      String tradeState = result.getTradeState();
                      String status = null;
                      // 判断
                      if (StrUtil.isNotBlank(tradeState)) {
                          if (WxPayConstants.NOT_PAY.equals(tradeState)) {
      //                            status = WxPayStatusEnum.TO_BE_PAID.getPayStatusCode();
                              } else if (WxPayConstants.PAY_SUCCESS.equals(tradeState)) {
                                  status = WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode();
                              } else if (WxPayConstants.PAY_CLOSED.equals(tradeState)) {
                                  status = WxPayStatusEnum.CLOSED.getPayStatusCode();
                              } else if (WxPayConstants.PAY_REFUND.equals(tradeState)) {
      //                            status = WxPayStatusEnum.PAY_REFUND.getPayStatusCode();
                              } else if (WxPayConstants.PAY_REVOKED.equals(tradeState)) {
      //                            status = WxPayStatusEnum.CANCEL.getPayStatusCode();
                              } else if (WxPayConstants.PAY_ING.equals(tradeState)) {
      //                            status = WxPayStatusEnum.PAY_ING.getPayStatusCode();
                              } else if (WxPayConstants.PAY_ERROR.equals(tradeState)) {
      //                            status = WxPayStatusEnum.PAY_FAIL.getPayStatusCode();
                              }
                              order.setOrderStatus(status);
                              response.setOrderStatusDesc(result.getTradeStateDesc());
                          }
                          if (StrUtil.isNotBlank(status)) {
                              updateById(order);
                              response.setOrderStatus(order.getOrderStatus());
                              // 发送通知给别的系统
                              sendNotifyToOtherSystem(order);
                          }
                      } else {
                          removeById(order.getOrderId());
                      }
                  }
              } catch (WxPayException e) {
                  log.error("OrderServiceImpl--[queryOrder]===error---->{}", e.getMessage());
                  throw new CustomException("查询订单异常:" + e.getErrCodeDes());
              } finally {
                  locker.unlock(outTradeNo);
              }
              return response;
          }
      
    • 关闭订单
      --------------------controller--------------------
      /**
       * 根据订单no关闭订单
       *
       * @param closeRequest 请求对象
       * @return result
       */
      @ApiOperation("关闭订单")
      @Log(title = "关闭订单", businessType = BusinessTypeEnum.UPDATE)
      @PostMapping("/close")
      @ApiTimeStatistics
      public AjaxResult closeOrder(@RequestBody @Validated OrderCloseRequest closeRequest) {
          WxPayOrderCloseResult result = orderService.closeOrder(closeRequest);
          return AjaxResult.success(result);
      }
      
      --------------------dto--------------------
      /**
       * @author:kyrie
       * @date:2023/5/17 16:25
       * @Description: 请求对象
       **/
      @Data
      public class OrderCloseRequest implements Serializable {
      
          private static final long serialVersionUID = 2516981824084563768L;
      
          @NotBlank(message = "订单号不能为空")
          private String outTradeNo;
      }
      
      --------------------serviceImpl--------------------
       /**
       * 关闭订单
       *
       * @param closeRequest 关闭对象
       * @return closeRequest
       */
      @Override
      public WxPayOrderCloseResult closeOrder(OrderCloseRequest closeRequest) {
          WxPayOrderCloseResult result;
          String outTradeNo = closeRequest.getOutTradeNo();
          locker.lock(outTradeNo, Constants.REDIS_TIMEOUT);
          try {
              // 根据订单号查询订单
              Order order = selectOrderByOrderNo(outTradeNo);
              if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus())) {
                  throw WxPayException.newBuilder().returnMsg(outTradeNo + "订单已支付成功,不允许关闭!").build();
              }
              result = wxPayService.closeOrder(outTradeNo);
              if (ObjectUtil.isNotNull(result)) {
                  order.setOrderStatus(WxPayStatusEnum.CLOSED.getPayStatusCode());
                  updateById(order);
              }
          } catch (WxPayException e) {
              log.error("OrderServiceImpl--[{}]===error---->{}", "closeOrder", e.getMessage());
              throw new CustomException("关闭订单异常:" + e.getReturnMsg());
          } finally {
              locker.unlock(outTradeNo);
          }
          return result;
      } 
      
    • 支付后回调 (支付后微信回通过回调地址调用我们自己的业务,需要给微信返回一个成功code和message 不然它会一直尝试调用 【好像会重试24h】
       /**
       * 微信支付回调处理
       *
       * @param paramsMap 参数
       * @return 返回结果
       */
      @PostMapping(value = "/notify", produces = "application/xml")
      @ApiTimeStatistics
      public Map<String, String> wxNotify(@RequestBody Map<String, String> paramsMap) {
          orderService.wxNotify(paramsMap);
          // 返回信息给微信
          Map<String, String> resultMap = new HashMap<>(2);
          resultMap.put("return_code", "SUCCESS");
          resultMap.put("return_msg", "OK");
          return resultMap;
      }
      --------------------serviceImpl--------------------
       /**
       * 微信支付后回调
       *
       * @param paramsMap 参数
       */
      @Override
      public void wxNotify(Map<String, String> paramsMap) {
          log.info("【微信支付回调】参数--->{}", paramsMap);
      
          // 获取订单号和支付金额
          String orderNo = paramsMap.get("out_trade_no");
          long totalFee = Long.parseLong(paramsMap.get("total_fee"));
      
          locker.lock(orderNo, Constants.REDIS_TIMEOUT);
          try {
              // 根据订单号查询订单
              Order order = selectOrderByOrderNo(orderNo);
              // 判断支付金额是否一致
              if (Long.parseLong(MoneyUtil.getMoney(order.getOrderMoney().toString())) != totalFee) {
                  log.error("【微信支付回调】支付金额不一致!");
                  throw new CustomException("支付金额不一致");
              }
              // 判断订单状态如果已经支付过了,无需支付了
              if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus())) {
                  log.error("【微信支付回调】订单已经支付,无需重复支付!");
                  throw new CustomException("订单已经支付,无需重复支付");
              }
              // 判断订单状态
              if (WxPayStatusEnum.PAY_FAIL.getPayStatusCode().equals(order.getOrderStatus())) {
                  log.error("【微信支付回调】订单已关闭,无发支付!");
                  throw new CustomException("订单已关闭,无发支付");
              }
              order.setOrderStatus(WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode());
              order.setPayTime(DateUtil.date());
              updateById(order);
              log.info("【微信支付回调】订单状态更新成功");
      
              sendNotifyToOtherSystem(order);
          } catch (Exception e) {
              log.error("【微信支付回调】订单状态更新失败-->{}", e.getMessage());
          } finally {
              locker.unlock(orderNo);
          }
      }
      
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值