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);
    }
}