diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java index 9a2afbe66..3350b5877 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java @@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*; import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; @@ -23,9 +25,7 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -70,6 +70,12 @@ public class PayOrderController { @PostMapping("/submit") @Operation(summary = "提交支付订单") public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { + if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { + Map channelExtras = reqVO.getChannelExtras() == null ? new HashMap<>(8) : reqVO.getChannelExtras(); + channelExtras.put("user_id", String.valueOf(WebFrameworkUtils.getLoginUserId())); + channelExtras.put("user_type", String.valueOf(WebFrameworkUtils.getLoginUserType())); + reqVO.setChannelExtras(channelExtras); + } PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP()); return success(respVO); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java index a05b88fe5..ccacdd376 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.pay.dal.mysql.wallet; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.apache.ibatis.annotations.Mapper; @Mapper @@ -12,6 +14,55 @@ public interface PayWalletMapper extends BaseMapperX { return selectOne(PayWalletDO::getUserId, userId, PayWalletDO::getUserType, userType); } + + /** + * 当余额减少时候更新 + * + * @param bizType 业务类型 + * @param balance 当前余额 + * @param totalRecharge 当前累计充值 + * @param totalExpense 当前累计支出 + * @param price 支出的金额 + * @param id 钱包 id + */ + default int updateWhenDecBalance(PayWalletBizTypeEnum bizType, Integer balance, Long totalRecharge, + Long totalExpense, Integer price, Long id) { + PayWalletDO updateDO = new PayWalletDO().setBalance(balance - price); + if(bizType == PayWalletBizTypeEnum.PAYMENT){ + updateDO.setTotalExpense(totalExpense + price); + } + if (bizType == PayWalletBizTypeEnum.RECHARGE_REFUND) { + updateDO.setTotalRecharge(totalRecharge - price); + } + return update(updateDO, + new LambdaQueryWrapper().eq(PayWalletDO::getId, id) + .eq(PayWalletDO::getBalance, balance) + .ge(PayWalletDO::getBalance, price)); + } + + /** + * 当余额增加时候更新 + * + * @param bizType 业务类型 + * @param balance 当前余额 + * @param totalRecharge 当前累计充值 + * @param totalExpense 当前累计支出 + * @param price 金额 + * @param id 钱包 id + */ + default int updateWhenIncBalance(PayWalletBizTypeEnum bizType, Integer balance, Long totalRecharge, + Long totalExpense, Integer price, Long id) { + PayWalletDO updateDO = new PayWalletDO().setBalance(balance + price); + if (bizType == PayWalletBizTypeEnum.PAYMENT_REFUND) { + updateDO.setTotalExpense(totalExpense - price); + } + if (bizType == PayWalletBizTypeEnum.RECHARGE) { + updateDO.setTotalExpense(totalRecharge + price); + } + return update(updateDO, + new LambdaQueryWrapper().eq(PayWalletDO::getId, id) + .eq(PayWalletDO::getBalance, balance)); + } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java index 7e23f5cf7..68985965f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.pay.framework.pay.wallet; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; @@ -41,7 +43,12 @@ public class WalletPayClient extends AbstractPayClient { @Override protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { try { - PayWalletTransactionDO transaction = wallService.pay(reqDTO.getOutTradeNo(), reqDTO.getPrice()); + String userId = MapUtil.getStr(reqDTO.getChannelExtras(), "user_id"); + String userType = MapUtil.getStr(reqDTO.getChannelExtras(), "user_type"); + Assert.notEmpty(userId, "用户 id 不能为空"); + Assert.notEmpty(userType, "用户类型不能为空"); + PayWalletTransactionDO transaction = wallService.pay(Long.valueOf(userId), Integer.valueOf(userType), + reqDTO.getOutTradeNo(), reqDTO.getPrice()); return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(), transaction.getTransactionTime(), reqDTO.getOutTradeNo(), transaction); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java index 2f57fc465..13cddaeeb 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.wallet; 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.enums.member.PayWalletBizTypeEnum; /** * 钱包 Service 接口 @@ -20,15 +21,43 @@ public interface PayWalletService { PayWalletDO getPayWallet(Long userId, Integer userType); /** - * 钱包支付 + * 钱包订单支付 * * @param outTradeNo 外部订单号 * @param price 金额 */ - PayWalletTransactionDO pay(String outTradeNo, Integer price); + PayWalletTransactionDO pay(Long userId, Integer userType, String outTradeNo, Integer price); + /** - * 钱包支付退款 + * 扣减钱包余额 + * + * @param userId 用户 id + * @param userType 用户类型 + * @param bizId 业务关联 id + * @param bizType 业务关联分类 + * @param price 扣减金额 + * @return 钱包流水 + */ + PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType, + Long bizId, PayWalletBizTypeEnum bizType, Integer price); + + + /** + * 增加钱包余额 + * + * @param userId 用户 id + * @param userType 用户类型 + * @param bizId 业务关联 id + * @param bizType 业务关联分类 + * @param price 增加金额 + * @return 钱包流水 + */ + PayWalletTransactionDO addWalletBalance(Long userId, Integer userType, + Long bizId, PayWalletBizTypeEnum bizType, Integer price); + + /** + * 钱包订单支付退款 * * @param outRefundNo 外部退款号 * @param refundPrice 退款金额 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java index 6992d63f6..5ea1b4945 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.module.pay.service.wallet; +import cn.hutool.core.lang.Assert; 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.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; import lombok.extern.slf4j.Slf4j; @@ -16,9 +18,8 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.TOO_MANY_REQUESTS; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT; import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT_REFUND; @@ -60,42 +61,70 @@ public class PayWalletServiceImpl implements PayWalletService { return payWalletMapper.selectByUserIdAndType(userId, userType); } - // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);reduceWalletBalance; - // TODO @jason:最好是,明确传入哪个 userId 或者 walletId; + @Override @Transactional(rollbackFor = Exception.class) - public PayWalletTransactionDO pay(String outTradeNo, Integer price) { - // 1.1 判断支付交易拓展单是否存 + public PayWalletTransactionDO pay(Long userId, Integer userType, String outTradeNo, Integer price) { + // 判断支付交易拓展单是否存 PayOrderExtensionDO orderExtension = payOrderService.getOrderExtensionByNo(outTradeNo); if (orderExtension == null) { throw exception(ORDER_EXTENSION_NOT_FOUND); } + return reduceWalletBalance(userId, userType, orderExtension.getOrderId(), PAYMENT, price); + } + + @Override + public PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType, + Long bizId, PayWalletBizTypeEnum bizType, Integer price) { + // 1.1 判断钱包是否有效 + PayWalletDO payWallet = validatePayWallet(userId, userType); // 1.2 判断余额是否足够 - PayWalletDO payWallet = validatePayWallet(); int afterBalance = payWallet.getBalance() - price; if (afterBalance < 0) { throw exception(WALLET_BALANCE_NOT_ENOUGH); } // 2.1 扣除余额 - // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 > price,以及 - price - payWallet.setBalance(afterBalance); - payWallet.setTotalExpense(payWallet.getTotalExpense() + price); - payWalletMapper.updateById(payWallet); + int number = payWalletMapper.updateWhenDecBalance(bizType,payWallet.getBalance(), payWallet.getTotalRecharge(), + payWallet.getTotalExpense(), price, payWallet.getId()); + if (number == 0) { + throw exception(TOO_MANY_REQUESTS); + } - // 2.2 生成钱包流水 + // 2.2 生成钱包流水 TODO 根据 bizType 生成 NO String walletNo = noRedisDAO.generate(WALLET_PAY_NO_PREFIX); PayWalletTransactionDO walletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId()) - .setNo(walletNo).setAmount(price * -1).setBalance(afterBalance).setTransactionTime(LocalDateTime.now()) - .setBizId(orderExtension.getOrderId()).setBizType(PAYMENT.getType()); + .setNo(walletNo).setAmount(-price).setBalance(afterBalance).setTransactionTime(LocalDateTime.now()) + .setBizId(bizId).setBizType(bizType.getType()).setDescription(bizType.getDescription()); payWalletTransactionService.createWalletTransaction(walletTransaction); return walletTransaction; } - // TODO @jason:不要在 service 里去使用用户上下文,这样和 request 就耦合了。 - private PayWalletDO validatePayWallet() { - Long userId = getLoginUserId(); - Integer userType = getLoginUserType(); + @Override + public PayWalletTransactionDO addWalletBalance(Long userId, Integer userType, Long bizId, + PayWalletBizTypeEnum bizType, Integer price) { + // 1.1 判断钱包是否有效 + PayWalletDO payWallet = validatePayWallet(userId, userType); + + // 2.1 增加余额 + int number = payWalletMapper.updateWhenIncBalance(bizType, payWallet.getBalance(), payWallet.getTotalRecharge(), + payWallet.getTotalExpense(), price, payWallet.getId()); + if (number == 0) { + throw exception(TOO_MANY_REQUESTS); + } + + // 2.2 生成钱包流水 TODO 根据 bizType 生成 NO + String walletNo = noRedisDAO.generate(WALLET_REFUND_NO_PREFIX); + PayWalletTransactionDO newWalletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId()) + .setNo(walletNo).setAmount(price).setBalance(payWallet.getBalance()+price).setTransactionTime(LocalDateTime.now()) + .setBizId(bizId).setBizType(bizType.getType()) + .setDescription(bizType.getDescription()); + payWalletTransactionService.createWalletTransaction(newWalletTransaction); + return newWalletTransaction; + } + + + private PayWalletDO validatePayWallet(Long userId, Integer userType) { PayWalletDO payWallet = getPayWallet(userId, userType); if (payWallet == null) { log.error("[validatePayWallet] 用户 {} 钱包不存在", userId); @@ -104,7 +133,7 @@ public class PayWalletServiceImpl implements PayWalletService { return payWallet; } - // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);addWalletBalance;这样,如果后续充值,应该也是能复用这个方法的; + @Override @Transactional(rollbackFor = Exception.class) public PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason) { @@ -114,23 +143,11 @@ public class PayWalletServiceImpl implements PayWalletService { throw exception(REFUND_NOT_FOUND); } // 1.2 校验是否可以退款 - PayWalletDO payWallet = validatePayWallet(); - validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), payWallet.getId(), refundPrice); + Long walletId = validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), refundPrice); - // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 + 金额 - Integer afterBalance = payWallet.getBalance() + refundPrice; - payWallet.setBalance(afterBalance); - payWallet.setTotalExpense(payWallet.getTotalExpense() + refundPrice * -1L); - payWalletMapper.updateById(payWallet); - - // 2.2 生成钱包流水 - String walletNo = noRedisDAO.generate(WALLET_REFUND_NO_PREFIX); - PayWalletTransactionDO newWalletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId()) - .setNo(walletNo).setAmount(refundPrice).setBalance(afterBalance).setTransactionTime(LocalDateTime.now()) - .setBizId(payRefund.getId()).setBizType(PAYMENT_REFUND.getType()) - .setDescription(reason); - payWalletTransactionService.createWalletTransaction(newWalletTransaction); - return newWalletTransaction; + PayWalletDO payWallet = payWalletMapper.selectById(walletId); + Assert.notNull(payWallet, "钱包 {} 不存在", walletId); + return addWalletBalance(payWallet.getUserId(), payWallet.getUserType(),payRefund.getId(), PAYMENT_REFUND, refundPrice); } /** @@ -138,24 +155,23 @@ public class PayWalletServiceImpl implements PayWalletService { * * @param refundId 支付退款单 id * @param walletPayNo 钱包支付 no - * @param walletId 钱包 id */ - // TODO @jason:不要使用基本类型; - private void validateWalletCanRefund(long refundId, String walletPayNo, long walletId, int refundPrice) { + private Long validateWalletCanRefund(Long refundId, String walletPayNo, Integer refundPrice) { // 查询钱包支付交易 PayWalletTransactionDO payWalletTransaction = payWalletTransactionService.getWalletTransactionByNo(walletPayNo); if (payWalletTransaction == null) { throw exception(WALLET_TRANSACTION_NOT_FOUND); } // 原来的支付金额 - int amount = payWalletTransaction.getAmount() * -1; // TODO @jason:直接 - payWalletTransaction.getAmount() 即可; + int amount = - payWalletTransaction.getAmount(); if (refundPrice != amount) { throw exception(WALLET_REFUND_AMOUNT_ERROR); } - PayWalletTransactionDO refundTransaction = payWalletTransactionService.getWalletTransaction(walletId, refundId, PAYMENT_REFUND); + PayWalletTransactionDO refundTransaction = payWalletTransactionService.getWalletTransaction( + payWalletTransaction.getWalletId(), refundId, PAYMENT_REFUND); if (refundTransaction != null) { throw exception(WALLET_REFUND_EXIST); } + return payWalletTransaction.getWalletId(); } - }