Mock 微信支付与退款接入教程
1. 适用场景
本文档面向以下场景:
- 没有微信商户号
- 没有微信支付证书
- 无法接真实微信支付
- 需要先把下单、支付、退款、订单状态流转跑通
本文档不依赖当前项目已经改好的代码状态,按“从零开始配置 mock 微信支付 / mock 微信退款”的方式编写。
2. 最终目标
完成后应达到以下效果:
2.1 Mock 支付
- 用户下单后进入支付页
- 前端先调用
/user/order/payment - 后端在 mock 模式下不调微信支付
- 后端直接返回假的支付参数
- 前端不再调用真实
wx.requestPayment - 前端改为调用
/user/order/mock/success/{orderNumber} - 后端把订单改成已支付
- 前端跳转成功页
2.2 Mock 退款
- 用户取消已支付订单时,不调微信退款接口
- 管理端拒绝已支付订单时,不调微信退款接口
- 管理端取消已支付订单时,不调微信退款接口
- 后端统一把支付状态改成
Orders.REFUND
3. 第一步:增加 mock 开关
文件
sky-server/src/main/resources/application-dev.yml
添加配置
sky:
pay:
mock: true
说明
-
true表示开发环境启用 mock 微信支付和 mock 退款 -
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;
}
说明
- mock 模式下直接返回假的支付参数
- 不调用
weChatPayUtil.pay(...) - 非 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();
}
说明
- 这个接口只用于开发环境
- 前端调用后,订单直接变成已支付
- 它模拟的是“微信支付回调成功”
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 (
说明
- 这个接口专门用于通知后端“本次支付视为成功”
- 它不负责生成支付参数
- 它只负责改订单状态
7. 第五步:前端支付页改成两步式 mock
文件
mp-weixin/pages/pay/index.js
位置
找到支付按钮对应的方法,通常是:
handleSave()
原目标逻辑
原本通常是:
- 调
/user/order/payment - 成功后调
wx.requestPayment - 支付成功后跳成功页
改造后的目标逻辑
改成:
- 调
/user/order/payment - 成功后调
/user/order/mock/success/{orderNumber} - 弹成功提示
- 跳成功页
代码模板
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 {
说明
-
paymentOrder(params)仍然保留 - 这样可以继续验证支付接口本身是否正常
- 只是把真实
wx.requestPayment替换成了mockPaySuccess(...)
8. 第六步:改用户取消订单退款
文件
sky-server/src/main/java/com/sky/service/impl/OrderServiceImpl.java
位置
找到:
public void userCancelById(Long id) throws Exception
目标
当订单已经支付并且用户取消时:
- mock 模式下不调微信退款
- 直接把退款视为成功
- 支付状态改成
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
目标
当订单已支付且管理端拒单时:
- mock 模式下不调微信退款
- 直接返回本地假退款成功
- 设置支付状态为
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
目标
当订单已支付且管理端取消订单时:
- mock 模式下不调微信退款
- 直接本地视为退款成功
- 设置支付状态为
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 支付
- 用户下单
- 进入支付页
- 前端调用
/user/order/payment - 后端返回假支付参数
- 前端调用
/user/order/mock/success/{orderNumber} - 跳转成功页
- 订单状态变为已支付
11.2 验证用户取消退款
- 先让订单变成已支付
- 用户取消订单
- 检查订单支付状态是否变成
REFUND
11.3 验证管理端拒单退款
- 先让订单变成已支付
- 管理端拒单
- 检查订单支付状态是否变成
REFUND
11.4 验证管理端取消退款
- 先让订单变成已支付
- 管理端取消订单
- 检查订单支付状态是否变成
REFUND
12. 统一规则
12.1 支付规则
mock 模式下:
- 不调
weChatPayUtil.pay(...) - 直接返回假的
OrderPaymentVO - 前端不走真实
wx.requestPayment
12.2 退款规则
mock 模式下:
- 不调
weChatPayUtil.refund(...) - 直接记录:
log.info("refund result: {}", "mock-refund-success");
- 设置:
orders.setPayStatus(Orders.REFUND);
12.3 状态更新对象规则
在每个退款相关方法中,只创建一个 Orders orders = new Orders();
示例:
Orders orders = new Orders();
orders.setId(...);
不要在同一个方法里重复声明同名 orders。