Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17

# Conflicts:
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
This commit is contained in:
YunaiV
2024-09-30 09:04:03 +08:00
108 changed files with 2761 additions and 1611 deletions

View File

@ -30,6 +30,16 @@ public enum PayOrderStatusEnum implements IntArrayValuable {
return new int[0];
}
/**
* 判断是否等待支付
*
* @param status 状态
* @return 是否等待支付
*/
public static boolean isWaiting(Integer status) {
return Objects.equals(status, WAITING.getStatus());
}
/**
* 判断是否支付成功
*

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
@ -11,12 +12,14 @@ import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
@ -51,10 +54,21 @@ public class PayOrderController {
@GetMapping("/get")
@Operation(summary = "获得支付订单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@Parameters({
@Parameter(name = "id", description = "编号", required = true, example = "1024"),
@Parameter(name = "sync", description = "是否同步", example = "true")
})
@PreAuthorize("@ss.hasPermission('pay:order:query')")
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
return success(PayOrderConvert.INSTANCE.convert(orderService.getOrder(id)));
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id,
@RequestParam(value = "sync", required = false) Boolean sync) {
PayOrderDO order = orderService.getOrder(id);
// sync 仅在等待支付
if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) {
orderService.syncOrderQuietly(order.getId());
// 重新查询,因为同步后,可能会有变化
order = orderService.getOrder(id);
}
return success(BeanUtils.toBean(order, PayOrderRespVO.class));
}
@GetMapping("/get-detail")

View File

@ -1,17 +1,21 @@
package cn.iocoder.yudao.module.pay.controller.app.order;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
@ -37,12 +41,22 @@ public class AppPayOrderController {
@Resource
private PayOrderService payOrderService;
// TODO 芋艿:临时 demo技术打样。
@GetMapping("/get")
@Operation(summary = "获得支付订单")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
return success(PayOrderConvert.INSTANCE.convert(payOrderService.getOrder(id)));
@Parameters({
@Parameter(name = "id", description = "编号", required = true, example = "1024"),
@Parameter(name = "sync", description = "是否同步", example = "true")
})
public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id,
@RequestParam(value = "sync", required = false) Boolean sync) {
PayOrderDO order = payOrderService.getOrder(id);
// sync 仅在等待支付
if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) {
payOrderService.syncOrderQuietly(order.getId());
// 重新查询,因为同步后,可能会有变化
order = payOrderService.getOrder(id);
}
return success(BeanUtils.toBean(order, PayOrderRespVO.class));
}
@PostMapping("/submit")

View File

@ -24,6 +24,11 @@ public interface PayOrderExtensionMapper extends BaseMapperX<PayOrderExtensionDO
return selectList(PayOrderExtensionDO::getOrderId, orderId);
}
default List<PayOrderExtensionDO> selectListByOrderIdAndStatus(Long orderId, Integer status) {
return selectList(PayOrderExtensionDO::getOrderId, orderId,
PayOrderExtensionDO::getStatus, status);
}
default List<PayOrderExtensionDO> selectListByStatusAndCreateTimeGe(Integer status, LocalDateTime minCreateTime) {
return selectList(new LambdaQueryWrapper<PayOrderExtensionDO>()
.eq(PayOrderExtensionDO::getStatus, status)

View File

@ -16,6 +16,15 @@ public interface RedisKeyConstants {
*/
String PAY_NOTIFY_LOCK = "pay_notify:lock:%d";
/**
* 支付钱包的分布式锁
*
* KEY 格式pay_wallet:lock:%d
* VALUE 数据格式HASH // RLock.classRedisson 的 Lock 锁,使用 Hash 数据结构
* 过期时间:不固定
*/
String PAY_WALLET_LOCK = "pay_wallet:lock:%d";
/**
* 支付序号的缓存
*

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.pay.dal.redis.wallet;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Repository;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.module.pay.dal.redis.RedisKeyConstants.PAY_WALLET_LOCK;
/**
* 支付钱包的锁 Redis DAO
*
* @author 芋道源码
*/
@Repository
public class PayWalletLockRedisDAO {
@Resource
private RedissonClient redissonClient;
public <V> V lock(Long id, Long timeoutMillis, Callable<V> callable) throws Exception {
String lockKey = formatKey(id);
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
// 执行逻辑
return callable.call();
} catch (Exception e) {
throw e;
} finally {
lock.unlock();
}
}
private static String formatKey(Long id) {
return String.format(PAY_WALLET_LOCK, id);
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.pay.service.demo;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
@ -14,11 +15,11 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
@ -111,10 +112,29 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
@Override
public void updateDemoOrderPaid(Long id, Long payOrderId) {
// 校验并获得支付订单(可支付)
PayOrderRespDTO payOrder = validateDemoOrderCanPaid(id, payOrderId);
// 1.1 校验订单是否存在
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
if (order == null) {
log.error("[updateDemoOrderPaid][order({}) payOrder({}) 不存在订单,请进行处理!]", id, payOrderId);
throw exception(DEMO_ORDER_NOT_FOUND);
}
// 1.2 校验订单已支付
if (order.getPayStatus()) {
// 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) {
log.warn("[updateDemoOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId);
return;
}
// 异常:支付单号不同,说明支付单号错误
log.error("[updateDemoOrderPaid][order({}) 支付单不匹配({})请进行处理order 数据是:{}]",
order, payOrderId, toJsonString(order));
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
}
// 更新 PayDemoOrderDO 状态为已支付
// 2. 校验支付订单的合法性
PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId);
// 3. 更新 PayDemoOrderDO 状态为已支付
int updateCount = payDemoOrderMapper.updateByIdAndPayed(id, false,
new PayDemoOrderDO().setPayStatus(true).setPayTime(LocalDateTime.now())
.setPayChannelCode(payOrder.getChannelCode()));
@ -124,56 +144,35 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
}
/**
* 校验交易订单满足被支付的条件
* 校验支付订单的合法性
*
* 1. 交易订单未支付
* 2. 支付单已支付
*
* @param id 交易订单编号
* @param order 交易订单
* @param payOrderId 支付订单编号
* @return 交易订单
* @return 支付订单
*/
private PayOrderRespDTO validateDemoOrderCanPaid(Long id, Long payOrderId) {
// 1.1 校验单是否存在
PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
if (order == null) {
throw exception(DEMO_ORDER_NOT_FOUND);
}
// 1.2 校验订单未支付
if (order.getPayStatus()) {
log.error("[validateDemoOrderCanPaid][order({}) 不处于待支付状态请进行处理order 数据是:{}]",
id, toJsonString(order));
throw exception(DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
}
// 1.3 校验支付订单匹配
if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({})请进行处理order 数据是:{}]",
id, payOrderId, toJsonString(order));
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
}
// 2.1 校验支付单是否存在
private PayOrderRespDTO validatePayOrderPaid(PayDemoOrderDO order, Long payOrderId) {
// 1. 校验支付单是否存在
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
if (payOrder == null) {
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
log.error("[validatePayOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", order.getId(), payOrderId);
throw exception(PAY_ORDER_NOT_FOUND);
}
// 2.2 校验支付单已支付
// 2.1 校验支付单已支付
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 未支付请进行处理payOrder 数据是:{}]",
id, payOrderId, toJsonString(payOrder));
log.error("[validatePayOrderPaid][order({}) payOrder({}) 未支付请进行处理payOrder 数据是:{}]",
order.getId(), payOrderId, toJsonString(payOrder));
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
}
// 2.3 校验支付金额一致
// 2.1 校验支付金额一致
if (notEqual(payOrder.getPrice(), order.getPrice())) {
log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 支付金额不匹配请进行处理order 数据是:{}payOrder 数据是:{}]",
id, payOrderId, toJsonString(order), toJsonString(payOrder));
log.error("[validatePayOrderPaid][order({}) payOrder({}) 支付金额不匹配请进行处理order 数据是:{}payOrder 数据是:{}]",
order.getId(), payOrderId, toJsonString(order), toJsonString(payOrder));
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
}
// 2.4 校验支付订单匹配(二次)
if (notEqual(payOrder.getMerchantOrderId(), id.toString())) {
log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({})请进行处理payOrder 数据是:{}]",
id, payOrderId, toJsonString(payOrder));
// 2.2 校验支付订单匹配(二次)
if (notEqual(payOrder.getMerchantOrderId(), order.getId().toString())) {
log.error("[validatePayOrderPaid][order({}) 支付单不匹配({})请进行处理payOrder 数据是:{}]",
order.getId(), payOrderId, toJsonString(payOrder));
throw exception(DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
}
return payOrder;

View File

@ -139,6 +139,16 @@ public interface PayOrderService {
*/
int syncOrder(LocalDateTime minCreateTime);
/**
* 同步订单的支付状态
*
* 1. Quietly 表示,即使同步失败,也不会抛出异常
* 2. 什么时候回出现异常?因为是主动同步,可能和支付渠道的异步回调存在并发冲突,导致抛出异常
*
* @param id 订单编号
*/
void syncOrderQuietly(Long id);
/**
* 将已过期的订单,状态修改为已关闭
*

View File

@ -163,7 +163,14 @@ public class PayOrderServiceImpl implements PayOrderService {
// 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
if (unifiedOrderResp != null) {
getSelf().notifyOrder(channel, unifiedOrderResp);
try {
getSelf().notifyOrder(channel, unifiedOrderResp);
} catch (Exception e) {
// 兼容 https://gitee.com/zhijiantianya/yudao-cloud/issues/I8SM9H 场景
// 支付宝或微信扫码之后时,由于 PayClient 是直接返回支付成功,而支付也会有回调,导致存在并发更新问题,此时一般是可以 try catch 直接忽略
log.warn("[submitOrder][order({}) channel({}) 支付结果({}) 通知时发生异常,可能是并发问题]",
order, channel, unifiedOrderResp, e);
}
// 如有渠道错误码,则抛出业务异常,提示用户
if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
@ -460,6 +467,18 @@ public class PayOrderServiceImpl implements PayOrderService {
return count;
}
@Override
public void syncOrderQuietly(Long id) {
// 1. 查询待支付订单
List<PayOrderExtensionDO> orderExtensions = orderExtensionMapper.selectListByOrderIdAndStatus(id,
PayOrderStatusEnum.WAITING.getStatus());
// 2. 遍历执行
for (PayOrderExtensionDO orderExtension : orderExtensions) {
syncOrder(orderExtension);
}
}
/**
* 同步单个支付拓展单
*

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.wallet;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -113,16 +114,28 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updateWalletRechargerPaid(Long id, Long payOrderId) {
// 1.1 获取钱包充值记录
PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
if (walletRecharge == null) {
log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id);
// 1.1 校验钱包充值是否存在
PayWalletRechargeDO recharge = walletRechargeMapper.selectById(id);
if (recharge == null) {
log.error("[updateWalletRechargerPaid][recharge({}) payOrder({}) 不存在充值订单,请进行处理!]", id, payOrderId);
throw exception(WALLET_RECHARGE_NOT_FOUND);
}
// 1.2 校验钱包充值是否可以支付
PayOrderDO payOrderDO = validateWalletRechargerCanPaid(walletRecharge, payOrderId);
if (recharge.getPayStatus()) {
// 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
if (ObjectUtil.equals(recharge.getPayOrderId(), payOrderId)) {
log.warn("[updateWalletRechargerPaid][recharge({}) 已支付,且支付单号相同({}),直接返回]", recharge, payOrderId);
return;
}
// 异常:支付单号不同,说明支付单号错误
log.error("[updateWalletRechargerPaid][recharge({}) 已支付,但是支付单号不同({}),请进行处理!]", recharge, payOrderId);
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
}
// 2. 更新钱包充值的支付状态
// 2. 校验支付订单的合法性
PayOrderDO payOrderDO = validatePayOrderPaid(recharge, payOrderId);
// 3. 更新钱包充值的支付状态
int updateCount = walletRechargeMapper.updateByIdAndPaid(id, false,
new PayWalletRechargeDO().setId(id).setPayStatus(true).setPayTime(LocalDateTime.now())
.setPayChannelCode(payOrderDO.getChannelCode()));
@ -130,14 +143,14 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
}
// 3. 更新钱包余额
// 4. 更新钱包余额
// TODO @jason这样的话未来提现会不会把充值的也提现走哈。类似先充 100送 110然后提现 110
// TODO 需要钱包中加个可提现余额
payWalletService.addWalletBalance(walletRecharge.getWalletId(), String.valueOf(id),
PayWalletBizTypeEnum.RECHARGE, walletRecharge.getTotalPrice());
payWalletService.addWalletBalance(recharge.getWalletId(), String.valueOf(id),
PayWalletBizTypeEnum.RECHARGE, recharge.getTotalPrice());
// 4. 发送订阅消息
getSelf().sendWalletRechargerPaidMessage(payOrderId, walletRecharge);
// 5. 发送订阅消息
getSelf().sendWalletRechargerPaidMessage(payOrderId, recharge);
}
@Async
@ -266,43 +279,38 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
return wallet;
}
private PayOrderDO validateWalletRechargerCanPaid(PayWalletRechargeDO walletRecharge, Long payOrderId) {
// 1.1 校验充值记录的支付状态
if (walletRecharge.getPayStatus()) {
log.error("[validateWalletRechargerCanPaid][钱包({}) 不处于未支付状态! 钱包数据是:{}]",
walletRecharge.getId(), toJsonString(walletRecharge));
throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
}
// 1.2 校验支付订单匹配
if (notEqual(walletRecharge.getPayOrderId(), payOrderId)) { // 支付单号
log.error("[validateWalletRechargerCanPaid][钱包({}) 支付单不匹配({}),请进行处理! 钱包数据是:{}]",
walletRecharge.getId(), payOrderId, toJsonString(walletRecharge));
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
}
// 2.1 校验支付单是否存在
/**
* 校验支付订单的合法性
*
* @param recharge 充值订单
* @param payOrderId 支付订单编号
* @return 支付订单
*/
private PayOrderDO validatePayOrderPaid(PayWalletRechargeDO recharge, Long payOrderId) {
// 1. 校验支付单是否存在
PayOrderDO payOrder = payOrderService.getOrder(payOrderId);
if (payOrder == null) {
log.error("[validateWalletRechargerCanPaid][钱包({}) payOrder({}) 不存在,请进行处理!]",
walletRecharge.getId(), payOrderId);
log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 不存在,请进行处理!]",
recharge.getId(), payOrderId);
throw exception(PAY_ORDER_NOT_FOUND);
}
// 2.2 校验支付单已支付
// 2.1 校验支付单已支付
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
log.error("[validateWalletRechargerCanPaid][钱包({}) payOrder({}) 未支付请进行处理payOrder 数据是:{}]",
walletRecharge.getId(), payOrderId, toJsonString(payOrder));
log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 未支付请进行处理payOrder 数据是:{}]",
recharge.getId(), payOrderId, toJsonString(payOrder));
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS);
}
// 2.3 校验支付金额一致
if (notEqual(payOrder.getPrice(), walletRecharge.getPayPrice())) {
log.error("[validateDemoOrderCanPaid][钱包({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{}payOrder 数据是:{}]",
walletRecharge.getId(), payOrderId, toJsonString(walletRecharge), toJsonString(payOrder));
// 2.2 校验支付金额一致
if (notEqual(payOrder.getPrice(), recharge.getPayPrice())) {
log.error("[validatePayOrderPaid][充值订单({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{}payOrder 数据是:{}]",
recharge.getId(), payOrderId, toJsonString(recharge), toJsonString(payOrder));
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH);
}
// 2.4 校验支付订单的商户订单匹配
if (notEqual(payOrder.getMerchantOrderId(), walletRecharge.getId().toString())) {
log.error("[validateDemoOrderCanPaid][钱包({}) 支付单不匹配({})请进行处理payOrder 数据是:{}]",
walletRecharge.getId(), payOrderId, toJsonString(payOrder));
// 2.3 校验支付订单的商户订单匹配
if (notEqual(payOrder.getMerchantOrderId(), recharge.getId().toString())) {
log.error("[validatePayOrderPaid][充值订单({}) 支付单不匹配({})请进行处理payOrder 数据是:{}]",
recharge.getId(), payOrderId, toJsonString(payOrder));
throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
}
return payOrder;

View File

@ -2,17 +2,20 @@ package cn.iocoder.yudao.module.pay.service.wallet;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletPageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
import cn.iocoder.yudao.module.pay.dal.redis.wallet.PayWalletLockRedisDAO;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import cn.iocoder.yudao.module.pay.service.wallet.bo.WalletTransactionCreateReqBO;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -32,10 +35,17 @@ import static cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum.PAYM
*/
@Service
@Slf4j
public class PayWalletServiceImpl implements PayWalletService {
public class PayWalletServiceImpl implements PayWalletService {
/**
* 通知超时时间,单位:毫秒
*/
public static final long UPDATE_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS;
@Resource
private PayWalletMapper walletMapper;
@Resource
private PayWalletLockRedisDAO lockRedisDAO;
@Resource
@Lazy // 延迟加载,避免循环依赖
@ -122,76 +132,86 @@ public class PayWalletServiceImpl implements PayWalletService {
@Override
@Transactional(rollbackFor = Exception.class)
@SneakyThrows
public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
PayWalletBizTypeEnum bizType, Integer price) {
// 1. 获取钱包
PayWalletDO payWallet = getWallet(walletId);
if (payWallet == null) {
log.error("[reduceWalletBalance]用户钱包({})不存在.", walletId);
log.error("[reduceWalletBalance][用户钱包({})不存在]", walletId);
throw exception(WALLET_NOT_FOUND);
}
// 2.1 扣除余额
int updateCounts;
switch (bizType) {
case PAYMENT: {
updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price);
break;
// 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯)
return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
// 2. 扣除余额
int updateCounts;
switch (bizType) {
case PAYMENT: {
updateCounts = walletMapper.updateWhenConsumption(payWallet.getId(), price);
break;
}
case RECHARGE_REFUND: {
updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price);
break;
}
default: {
// TODO 其它类型待实现
throw new UnsupportedOperationException("待实现");
}
}
case RECHARGE_REFUND: {
updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price);
break;
if (updateCounts == 0) {
throw exception(WALLET_BALANCE_NOT_ENOUGH);
}
default: {
// TODO 其它类型待实现
throw new UnsupportedOperationException("待实现");
}
}
if (updateCounts == 0) {
throw exception(WALLET_BALANCE_NOT_ENOUGH);
}
// 2.2 生成钱包流水
Integer afterBalance = payWallet.getBalance() - price;
WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId())
.setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
.setBizType(bizType.getType()).setTitle(bizType.getDescription());
return walletTransactionService.createWalletTransaction(bo);
// 3. 生成钱包流水
Integer afterBalance = payWallet.getBalance() - price;
WalletTransactionCreateReqBO bo = new WalletTransactionCreateReqBO().setWalletId(payWallet.getId())
.setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
.setBizType(bizType.getType()).setTitle(bizType.getDescription());
return walletTransactionService.createWalletTransaction(bo);
});
}
@Override
@Transactional(rollbackFor = Exception.class)
@SneakyThrows
public PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
PayWalletBizTypeEnum bizType, Integer price) {
// 1.1 获取钱包
// 1. 获取钱包
PayWalletDO payWallet = getWallet(walletId);
if (payWallet == null) {
log.error("[addWalletBalance]用户钱包({})不存在.", walletId);
log.error("[addWalletBalance][用户钱包({})不存在]", walletId);
throw exception(WALLET_NOT_FOUND);
}
// 1.2 更新钱包金额
switch (bizType) {
case PAYMENT_REFUND: { // 退款更新
walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
break;
}
case RECHARGE: { // 充值更新
walletMapper.updateWhenRecharge(payWallet.getId(), price);
break;
}
case UPDATE_BALANCE: // 更新余额
walletMapper.updateWhenRecharge(payWallet.getId(), price);
break;
default: {
// TODO 其它类型待实现
throw new UnsupportedOperationException("待实现");
}
}
// 2. 生成钱包流水
WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
.setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
.setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
// 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯)
return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
// 2. 更新钱包金额
switch (bizType) {
case PAYMENT_REFUND: { // 退款更新
walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
break;
}
case RECHARGE: { // 充值更新
walletMapper.updateWhenRecharge(payWallet.getId(), price);
break;
}
case UPDATE_BALANCE: // 更新余额
walletMapper.updateWhenRecharge(payWallet.getId(), price);
break;
default: {
// TODO 其它类型待实现
throw new UnsupportedOperationException("待实现");
}
}
// 3. 生成钱包流水
WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
.setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
.setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
});
}
@Override

View File

@ -87,8 +87,6 @@ public class PayWalletTransactionServiceImpl implements PayWalletTransactionServ
@Override
public AppPayWalletTransactionSummaryRespVO getWalletTransactionSummary(Long userId, Integer userType, LocalDateTime[] createTime) {
PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
AppPayWalletTransactionSummaryRespVO summary = new AppPayWalletTransactionSummaryRespVO()
.setTotalExpense(1).setTotalIncome(100);
return new AppPayWalletTransactionSummaryRespVO()
.setTotalExpense(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_EXPENSE, createTime))
.setTotalIncome(payWalletTransactionMapper.selectPriceSum(wallet.getId(), TYPE_INCOME, createTime));