掘金 后端 ( ) • 2024-03-28 10:25
  1. 功能需求与问题描述
  • 最近练习的项目有一个功能需求是用户在小程序端下单之后需要付款,这里我选择了微信支付中的JSAPI支付。
  • 它要求我有AppID和商户号才提供使用,但是我没有,因此就需要我在了解微信支付流程与业务流程的基础上对支付相关代码进行调整,绕过支付的具体实现且不影响整个业务的正常运行。
  1. JSAPI支付流程概述

image.png

  • 步骤1、2、3

在我的项目的小程序中,用户点击“去支付”之后,小程序端就会向服务器端发送提交订单请求,服务器端就会处理此请求,创建一个订单对象,将其状态设置为待支付,并向数据库订单表中插入此对象。小程序也会进入微信支付的页面,此页面包括具体的金额以及“确认支付”按钮。

  • 步骤4、5、6、7、8

当用户点击“确认支付”时,小程序端就会向服务器端发送一个支付请求,服务器端响应处理此请求,并调用微信支付接口(也就是JSAPI下单API),向微信支付系统发送一个请求。微信支付系统会根据发送的相关参数创建一个预付单,并返回一个预支付交易会话标识(prepay_id)。服务器端会结合相关信息(包括AppID, 时间戳,随机字符串,预支付交易会话标识)并使用商户私钥进行签名。并通过JSAPI调起支付API(此接口实际上就是按照相应的形式向小程序端返回相应的数据)调起微信支付,此时小程序端应该就会出现输入密码的界面。

  • 步骤9、10、11、12(相比微信支付文档有略微简化):

用户输入密码之后,就会向微信支付系统发送支付请求,系统完成支付相关功能之后会向小程序端返回支付结果,小程序端会处理结果并显示。

  • 步骤13、14

此时微信支付系统会向我们的服务器发送一个请求(假设支付成功),也就是微信支付平台向服务器发送一个POST请求(支付结果通知API),请求的地址是我们自己之前所定义的notify_url,会在步骤5中随着其他数据一起发送到微信支付系统汇总。此参数相关的定义在文件:application.yml, application-dev.yml, WeChatProperties中。在我的项目中我定义了一个paySuccessNotify方法来处理此请求,此请求会从微信支付系统发送的支付结果通知所携带的参数中提取出订单号,并通过订单号来修改订单状态,此过程封装在OrderService中的方法paySuccess()中,在此方法中还定义了下单提醒相关功能。

  1. 解决方案实施
  • 因为我没有商户号相关信息,因此无法成功地调起JSAPI下单接口来生成预支付交易单,也无法顺利地完成支付,不进行修改的话,后续的下单提醒功能也无法实现(因为此功能定义在paySuccess()中,此方法的调起需要微信支付系统向服务器端发送支付结果通知请求)。因此需要对代码逻辑进行适当的修改,需要在步骤4之后直接跟上步骤14。

  • 首先来看微信支付相关的两个服务层方法:

    • payment()方法实现了步骤5-8。输入要求OrderNumber(订单号)与paymentMethod(支付方法),输出是JSAPI调起支付API规定的一系列数据(比如AppID,时间戳,随机字符串,prepay_id,签名类型,签名值),这些数据除了AppID,都是在生成预支付交易单相关过程中生成的,无法完成。
    • paySuccess()方法实现了步骤13-14。输入要求OrderNumber(订单号),不需要输出。
  • payment()方法是步骤4中,在用户在小程序端点击“确认支付”后,Controller层中对应的方法调起的;paySuccess()方法是步骤13中,在微信支付系统发送交易结果通知请求后,Controller层中对应的方法调起的。因此得到了思路:在用户在小程序端点击“确认支付”后对应的Controller层的方法中,不调起payment方法,按照其返回的格式伪造并向小程序端响应;之后根据订单号直接调用paySuccess方法。这样就完成了修改,从步骤4直接到14。具体代码如下:

@PutMapping("/payment")
@ApiOperation("订单支付")
public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
    log.info("订单支付:{}", ordersPaymentDTO);
    // OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
    // log.info("生成预支付交易单:{}", orderPaymentVO);

    // 1.直接调用paySuccess
    String orderNumber = ordersPaymentDTO.getOrderNumber();
    orderService.paySuccess(orderNumber);
    // 2.捏造返回值
    OrderPaymentVO orderPaymentVO = OrderPaymentVO.builder()
            .nonceStr("FoolYou!")
            .packageStr("FoolYou!")
            .timeStamp("FoolYou!")
            .signType("FoolYou!")
            .packageStr("FoolYou!")
            .build();
    return Result.success(orderPaymentVO);
}