苍穹外卖 模拟微信支付

Mock 微信支付与退款接入教程

1. 适用场景

本文档面向以下场景:

  1. 没有微信商户号
  2. 没有微信支付证书
  3. 无法接真实微信支付
  4. 需要先把下单、支付、退款、订单状态流转跑通

本文档不依赖当前项目已经改好的代码状态,按“从零开始配置 mock 微信支付 / mock 微信退款”的方式编写。


2. 最终目标

完成后应达到以下效果:

2.1 Mock 支付

  1. 用户下单后进入支付页
  2. 前端先调用 /user/order/payment
  3. 后端在 mock 模式下不调微信支付
  4. 后端直接返回假的支付参数
  5. 前端不再调用真实 wx.requestPayment
  6. 前端改为调用 /user/order/mock/success/{orderNumber}
  7. 后端把订单改成已支付
  8. 前端跳转成功页

2.2 Mock 退款

  1. 用户取消已支付订单时,不调微信退款接口
  2. 管理端拒绝已支付订单时,不调微信退款接口
  3. 管理端取消已支付订单时,不调微信退款接口
  4. 后端统一把支付状态改成 Orders.REFUND

3. 第一步:增加 mock 开关

文件

sky-server/src/main/resources/application-dev.yml

添加配置

sky:
  pay:
    mock: true

说明

  1. true 表示开发环境启用 mock 微信支付和 mock 退款
  2. false 表示恢复真实微信支付逻辑

4. 第二步:在支付服务中接入 mock 支付

文件

sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java

位置

在类属性区域增加配置注入。

添加代码

@Value("${sky.pay.mock:false}")
private boolean payMockEnabled;

位置

找到:

public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception

修改方式

在真实微信支付调用前,增加 mock 分支。

代码模板

public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
    Long userId = BaseContext.getCurrentId();

    if (payMockEnabled) {
        return OrderPaymentVO.builder()
                .timeStamp(String.valueOf(System.currentTimeMillis() / 1000))
                .nonceStr(UUID.randomUUID().toString().replace("-", ""))
                .paySign("mock-pay-sign")
                .signType("MOCK")
                .packageStr("mock-package")
                .build();
    }

    User user = userMapper.getById(userId);

    JSONObject jsonObject = weChatPayUtil.pay(
            ordersPaymentDTO.getOrderNumber(),
            new BigDecimal(0.01),
            "苍穹外卖订单",
            user.getOpenid()
    );

    if (jsonObject.getString("code") != null
            && jsonObject.getString("code").equals("ORDERPAID")) {
        throw new OrderBusinessException("该订单已支付");
    }

    OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
    vo.setPackageStr(jsonObject.getString("package"));
    return vo;
}

说明

  1. mock 模式下直接返回假的支付参数
  2. 不调用 weChatPayUtil.pay(...)
  3. 非 mock 模式下继续保留真实微信支付逻辑

5. 第三步:增加模拟支付成功接口

文件

sky-server/src/main/java/com/sky/controller/user/OrderController.java

位置

在用户端订单控制器中增加配置开关和 mock 支付成功接口。

添加代码

@Value("${sky.pay.mock:false}")
private boolean payMockEnabled;
@PostMapping("/mock/success/{orderNumber}")
@ApiOperation("模拟支付成功")
public Result<String> mockPaySuccess(@PathVariable String orderNumber) {
    if (!payMockEnabled) {
        return Result.error("mock pay is disabled");
    }
    orderService.paySuccess(orderNumber);
    return Result.success();
}

说明

  1. 这个接口只用于开发环境
  2. 前端调用后,订单直接变成已支付
  3. 它模拟的是“微信支付回调成功”

6. 第四步:前端增加 mockPaySuccess 接口

文件

mp-weixin/common/vendor.js

如果你维护的是源码项目,不要直接改编译产物,应在前端 API 源文件中增加这个接口;这里只给出接口内容本身。

添加/修改代码

line 19951:
Object.defineProperty(exports, "__esModule", { value: true });exports.repetitionOrder = exports.mockPaySuccess = exports.paymentOrder = exports.reminderOrder = exports.cancelOrder = exports.getOrderDetail = exports.getOrderPage = exports.getShopPhone = exports.getShopStatus = exports.querySetmealDishById = exports.getAddressBookDefault = exports.oneOrderAgain = exports.queryAddressBookById = exports.delAddressBook = exports.editAddressBook = exports.addAddressBook = exports.putAddressBookDefault = exports.queryAddressBookList = exports.submitOrderSubmit = exports.queryOrderUserPage = exports.delShoppingCart = exports.newShoppingCartSub = exports.newAddShoppingCartAdd = exports.editHoppingCart = exports.getShoppingCartList = exports.querySetmeaList = exports.addShoppingCart = exports.commonDownload = exports.dishListByCategoryId = exports.getCategoryList = exports.userLogin = exports.payOrder = exports.clearOrder = exports.delDish = exports.addDish = exports.getDishList = exports.getDishDetail = exports.getList = exports.getMoreNorm = exports.getTableOrderDishList = exports.getTableState = exports.openTable = void 0;var _request = __webpack_require__(/*! ../../utils/request.js */ 25);

line 20296:
exports.paymentOrder = paymentOrder;var mockPaySuccess = function mockPaySuccess(orderNumber) {return (
    (0, _request.request)({
        url: "/user/order/mock/success/".concat(orderNumber),
        method: 'POST' }));};

exports.mockPaySuccess = mockPaySuccess;var repetitionOrder = function repetitionOrder(params) {return (

说明

  1. 这个接口专门用于通知后端“本次支付视为成功”
  2. 它不负责生成支付参数
  3. 它只负责改订单状态

7. 第五步:前端支付页改成两步式 mock

文件

mp-weixin/pages/pay/index.js

位置

找到支付按钮对应的方法,通常是:

handleSave()

原目标逻辑

原本通常是:

  1. /user/order/payment
  2. 成功后调 wx.requestPayment
  3. 支付成功后跳成功页

改造后的目标逻辑

改成:

  1. /user/order/payment
  2. 成功后调 /user/order/mock/success/{orderNumber}
  3. 弹成功提示
  4. 跳成功页

代码模板

line 224:
(0, _api.paymentOrder)(params).then(function (res) {
    if (res.code === 1) {
        (0, _api.mockPaySuccess)(_this.orderDataInfo.orderNumber).then(function () {
            wx.showModal({
                title: 'Tip',
                content: 'Payment succeeded',
                success:function(){
                    uni.redirectTo({url: '/pages/success/index?orderId=' + _this.orderId });
                }
            })
            console.log('Mock payment succeeded')
        })
        return;
        wx.requestPayment({
            nonceStr: res.data.nonceStr,
            package: res.data.packageStr,
            paySign: res.data.paySign,
            timeStamp: res.data.timeStamp,
            signType: res.data.signType,
            success:function(res){
                wx.showModal({
                    title: '提示',
                    content: '支付成功',
                    success:function(){
                        uni.redirectTo({url: '/pages/success/index?orderId=' + _this.orderId });
                    }
                })
                console.log('支付成功!')
            }
        })


        //uni.redirectTo({url: '/pages/success/index?orderId=' + _this.orderId });

    } else {

说明

  1. paymentOrder(params) 仍然保留
  2. 这样可以继续验证支付接口本身是否正常
  3. 只是把真实 wx.requestPayment 替换成了 mockPaySuccess(...)

8. 第六步:改用户取消订单退款

文件

sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java

位置

找到:

public void userCancelById(Long id) throws Exception

目标

当订单已经支付并且用户取消时:

  1. mock 模式下不调微信退款
  2. 直接把退款视为成功
  3. 支付状态改成 Orders.REFUND

代码模板

public void userCancelById(Long id) throws Exception {
    Orders ordersDB = orderMapper.getById(id);

    if (ordersDB == null) {
        throw new OrderBusinessException(MessageConstant.ORDER_NOT_FOUND);
    }

    if (ordersDB.getStatus() > 2) {
        throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
    }

    Orders orders = new Orders();
    orders.setId(ordersDB.getId());

    if (ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
        if (payMockEnabled) {
            log.info("refund result: {}", "mock-refund-success");
        } else {
            weChatPayUtil.refund(
                    ordersDB.getNumber(),
                    ordersDB.getNumber(),
                    new BigDecimal(0.01),
                    new BigDecimal(0.01));
        }
        orders.setPayStatus(Orders.REFUND);
    }

    orders.setStatus(Orders.CANCELLED);
    orders.setCancelReason("用户取消");
    orders.setCancelTime(LocalDateTime.now());
    orderMapper.update(orders);
}

9. 第七步:改管理端拒单退款

文件

sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java

位置

找到:

public void rejection(OrdersRejectionDTO ordersRejectionDTO) throws Exception

目标

当订单已支付且管理端拒单时:

  1. mock 模式下不调微信退款
  2. 直接返回本地假退款成功
  3. 设置支付状态为 Orders.REFUND

代码模板

public void rejection(OrdersRejectionDTO ordersRejectionDTO) throws Exception {
    Orders ordersDB = orderMapper.getById(ordersRejectionDTO.getId());

    if (ordersDB == null || !ordersDB.getStatus().equals(Orders.TO_BE_CONFIRMED)) {
        throw new OrderBusinessException(MessageConstant.ORDER_STATUS_ERROR);
    }

    Orders orders = new Orders();
    orders.setId(ordersDB.getId());

    Integer payStatus = ordersDB.getPayStatus();
    if (payStatus == Orders.PAID) {
        String refund;
        if (payMockEnabled) {
            refund = "mock-refund-success";
        } else {
            refund = weChatPayUtil.refund(
                    ordersDB.getNumber(),
                    ordersDB.getNumber(),
                    new BigDecimal(0.01),
                    new BigDecimal(0.01));
        }
        log.info("refund result: {}", refund);
        orders.setPayStatus(Orders.REFUND);
    }

    orders.setStatus(Orders.CANCELLED);
    orders.setRejectionReason(ordersRejectionDTO.getRejectionReason());
    orders.setCancelTime(LocalDateTime.now());
    orderMapper.update(orders);
}

注意

这个方法里只能有一份:

Orders orders = new Orders();
orders.setId(ordersDB.getId());

不要重复定义 orders


10. 第八步:改管理端取消订单退款

文件

sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java

位置

找到:

public void cancel(OrdersCancelDTO ordersCancelDTO) throws Exception

目标

当订单已支付且管理端取消订单时:

  1. mock 模式下不调微信退款
  2. 直接本地视为退款成功
  3. 设置支付状态为 Orders.REFUND

代码模板

public void cancel(OrdersCancelDTO ordersCancelDTO) throws Exception {
    Orders ordersDB = orderMapper.getById(ordersCancelDTO.getId());

    Orders orders = new Orders();
    orders.setId(ordersCancelDTO.getId());

    Integer payStatus = ordersDB.getPayStatus();
    if (payStatus == Orders.PAID) {
        String refund;
        if (payMockEnabled) {
            refund = "mock-refund-success";
        } else {
            refund = weChatPayUtil.refund(
                    ordersDB.getNumber(),
                    ordersDB.getNumber(),
                    new BigDecimal(0.01),
                    new BigDecimal(0.01));
        }
        log.info("refund result: {}", refund);
        orders.setPayStatus(Orders.REFUND);
    }

    orders.setStatus(Orders.CANCELLED);
    orders.setCancelReason(ordersCancelDTO.getCancelReason());
    orders.setCancelTime(LocalDateTime.now());
    orderMapper.update(orders);
}

注意

这里不要写:

if (payStatus == 1)

应统一写成:

if (payStatus == Orders.PAID)

11. 第九步:验证顺序

11.1 验证 Mock 支付

  1. 用户下单
  2. 进入支付页
  3. 前端调用 /user/order/payment
  4. 后端返回假支付参数
  5. 前端调用 /user/order/mock/success/{orderNumber}
  6. 跳转成功页
  7. 订单状态变为已支付

11.2 验证用户取消退款

  1. 先让订单变成已支付
  2. 用户取消订单
  3. 检查订单支付状态是否变成 REFUND

11.3 验证管理端拒单退款

  1. 先让订单变成已支付
  2. 管理端拒单
  3. 检查订单支付状态是否变成 REFUND

11.4 验证管理端取消退款

  1. 先让订单变成已支付
  2. 管理端取消订单
  3. 检查订单支付状态是否变成 REFUND

12. 统一规则

12.1 支付规则

mock 模式下:

  1. 不调 weChatPayUtil.pay(...)
  2. 直接返回假的 OrderPaymentVO
  3. 前端不走真实 wx.requestPayment

12.2 退款规则

mock 模式下:

  1. 不调 weChatPayUtil.refund(...)
  2. 直接记录:
log.info("refund result: {}", "mock-refund-success");
  1. 设置:
orders.setPayStatus(Orders.REFUND);

12.3 状态更新对象规则

在每个退款相关方法中,只创建一个 Orders orders = new Orders();

示例:

Orders orders = new Orders();
orders.setId(...);

不要在同一个方法里重复声明同名 orders

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容