Merge remote-tracking branch 'origin/feature/mall_product' into brokerate

This commit is contained in:
owen 2023-09-22 23:15:31 +08:00
commit dc46dff62c
15 changed files with 361 additions and 127 deletions

View File

@ -10,6 +10,7 @@ CREATE TABLE `pay_wallet`
`balance` int NOT NULL DEFAULT 0 COMMENT '余额单位分',
`total_expense` int NOT NULL DEFAULT 0 COMMENT '累计支出单位分',
`total_recharge` int NOT NULL DEFAULT 0 COMMENT '累计充值单位分',
`freeze_price` int NOT NULL DEFAULT 0 COMMENT '冻结金额单位分',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@ -62,6 +63,7 @@ CREATE TABLE `pay_wallet_recharge`
`refund_pay_price` int NOT NULL DEFAULT 0 COMMENT '退款支付金额',
`refund_bonus_price` int NOT NULL DEFAULT 0 COMMENT '退款钱包赠送金额',
`refund_time` datetime NULL COMMENT '退款时间',
`refund_status` int NOT NULL DEFAULT 0 COMMENT '退款状态',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',

View File

@ -16,7 +16,7 @@ import java.util.Arrays;
@Getter
public enum DeliveryExpressChargeModeEnum implements IntArrayValuable {
PIECE(1, "按件"),
COUNT(1, "按件"),
WEIGHT(2,"按重量"),
VOLUME(3, "按体积");

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.member.api.address.AddressApi;
import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
return;
}
if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
calculateByPickUp(param, result);
calculateByPickUp(param);
} else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
calculateExpress(param, result);
}
}
private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
private void calculateByPickUp(TradePriceCalculateReqBO param) {
if (param.getPickUpStoreId() == null) {
throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
}
@ -82,12 +83,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
// 情况一全局包邮
if (isGlobalExpressFree(param, result)) {
if (isGlobalExpressFree(result)) {
return;
}
// 情况二
// 2.1 过滤出已选中的商品SKU
// 情况二快递模版
// 2.1 过滤出已选中的商品 SKU
List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap =
@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
/**
* 是否全局包邮
*
* @param param 计算信息
* @param result 计算结果
* @return 是否包邮
*/
private boolean isGlobalExpressFree(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
TradeConfigDO config = tradeConfigService.getTradeConfig();
return config != null
&& Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap,
TradePriceCalculateRespBO result) {
// 按商品运费模板来计算商品的运费相同的运费模板可能对应多条订单商品 SKU
Map<Long, List<OrderItem>> tplIdItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
Map<Long, List<OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
// 依次计算快递运费
for (Map.Entry<Long, List<OrderItem>> entry : tplIdItemMap.entrySet()) {
for (Map.Entry<Long, List<OrderItem>> entry : template2ItemMap.entrySet()) {
Long templateId = entry.getKey();
List<OrderItem> orderItems = entry.getValue();
DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId);
continue;
}
// 总件数, 总金额, 总重量 总体积
int totalCount = 0;
int totalPrice = 0;
double totalWeight = 0;
double totalVolume = 0;
for (OrderItem orderItem : orderItems) {
totalCount += orderItem.getCount();
totalPrice += orderItem.getPayPrice();
if (orderItem.getWeight() != null) {
totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();
}
if (orderItem.getVolume() != null) {
totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();
}
}
// 优先判断是否包邮. 如果包邮不计算快递运费
if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,
totalVolume, totalPrice, templateBO.getFree())) {
// 1. 优先判断是否包邮如果包邮不计算快递运费
if (isExpressTemplateFree(orderItems, templateBO.getChargeMode(), templateBO.getFree())) {
continue;
}
// 计算快递运费
calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,
templateBO.getChargeMode(), templateBO.getCharge(), orderItems);
// 2. 计算快递运费
calculateExpressFeeByChargeMode(orderItems, templateBO.getChargeMode(), templateBO.getCharge());
}
TradePriceCalculatorHelper.recountAllPrice(result);
}
@ -159,73 +141,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
/**
* 按配送方式来计算运费
*
* @param totalCount 总件数
* @param totalWeight 总重量
* @param totalVolume 总体积
* @param orderItems SKU 商品项目
* @param chargeMode 配送计费方式
* @param templateCharge 快递运费配置
* @param orderItems SKU 商品项目
*/
private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,
int chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge,
List<OrderItem> orderItems) {
private void calculateExpressFeeByChargeMode(List<OrderItem> orderItems, Integer chargeMode,
DeliveryExpressTemplateRespBO.Charge templateCharge) {
if (templateCharge == null) {
log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems);
return;
}
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) {
case PIECE: {
calculateExpressFee(totalCount, templateCharge, orderItems);
break;
}
case WEIGHT: {
calculateExpressFee(totalWeight, templateCharge, orderItems);
break;
}
case VOLUME: {
calculateExpressFee(totalVolume, templateCharge, orderItems);
break;
}
}
}
/**
* 计算 SKU 商品快递费用
*
* @param total 总件数/总重量/总体积
* @param templateCharge 快递运费配置
* @param orderItems SKU 商品项目
*/
private void calculateExpressFee(double total, DeliveryExpressTemplateRespBO.Charge templateCharge, List<OrderItem> orderItems) {
double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
// 1. 计算 SKU 商品快递费用
int deliveryPrice;
if (total <= templateCharge.getStartCount()) {
if (totalChargeValue <= templateCharge.getStartCount()) {
deliveryPrice = templateCharge.getStartPrice();
} else {
double remainWeight = total - templateCharge.getStartCount();
double remainWeight = totalChargeValue - templateCharge.getStartCount();
// 剩余重量/ 续件 = 续件的次数. 向上取整
int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
int extraPrice = templateCharge.getExtraPrice() * extraNum;
deliveryPrice = templateCharge.getStartPrice() + extraPrice;
}
// 分摊快递费用到 SKU. 退费的时候可能按照 SKU 考虑退费金额
divideDeliveryPrice(deliveryPrice, orderItems);
}
/**
* 快递运费分摊到每个 SKU 商品上
*
* @param deliveryPrice 快递运费
* @param orderItems SKU 商品
*/
private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {
// TODO @jason分摊的话是不是要按照比例呀重量价格数量等等,
// 按比例是不是有点复杂后面看看是否需要
// TODO 可以看看别的项目怎么搞的哈
int dividePrice = deliveryPrice / orderItems.size();
for (OrderItem item : orderItems) {
// 2. 分摊快递费用到 SKU. 退费的时候可能按照 SKU 考虑退费金额
int remainPrice = deliveryPrice;
for (int i = 0; i < orderItems.size(); i++) {
TradePriceCalculateRespBO.OrderItem item = orderItems.get(i);
int partPrice;
double chargeValue = getChargeValue(item, chargeMode);
if (i < orderItems.size() - 1) { // 减一的原因是因为拆分时如果按照比例可能会出现.所以最后一个使用反减
partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue));
remainPrice -= partPrice;
} else {
partPrice = remainPrice;
}
Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
// 更新快递运费
item.setDeliveryPrice(dividePrice);
item.setDeliveryPrice(partPrice);
TradePriceCalculatorHelper.recountPayPrice(item);
}
}
@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
* 检查是否包邮
*
* @param chargeMode 配送计费方式
* @param totalCount 总件数
* @param totalWeight 总重量
* @param totalVolume 总体积
* @param totalPrice 总金额
* @param templateFree 包邮配置
*/
private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight,
double totalVolume, int totalPrice, DeliveryExpressTemplateRespBO.Free templateFree) {
private boolean isExpressTemplateFree(List<OrderItem> orderItems, Integer chargeMode,
DeliveryExpressTemplateRespBO.Free templateFree) {
if (templateFree == null) {
return false;
}
double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
}
private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) {
double total = 0;
for (OrderItem orderItem : orderItems) {
total += getChargeValue(orderItem, chargeMode);
}
return total;
}
private double getChargeValue(OrderItem orderItem, Integer chargeMode) {
DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
switch (chargeModeEnum) {
case PIECE:
// 两个条件都满足才包邮
if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
case COUNT:
return orderItem.getCount();
case WEIGHT:
// freeCount 是不是应该是 double ??
// TODO @jason要不配置的时候把它的单位和商品对齐到底是 kg还是斤
// TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段
// TODO @jason那要不快递模版也改成 kg这样是不是就不用 double
if (totalWeight >= templateFree.getFreeCount()
&& totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
return orderItem.getWeight() != null ? orderItem.getWeight() * orderItem.getCount() : 0;
case VOLUME:
if (totalVolume >= templateFree.getFreeCount()
&& totalPrice >= templateFree.getFreePrice()) {
return true;
}
break;
return orderItem.getVolume() != null ? orderItem.getVolume() * orderItem.getCount() : 0;
default:
throw new IllegalArgumentException(StrUtil.format("未知的计费模式({})", chargeMode));
}
return false;
}
}

View File

@ -91,7 +91,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
item -> item.setFreeCount(20).setFreePrice(100));
// 准备 SP 运费模板数据
templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class,
item -> item.setChargeMode(DeliveryExpressChargeModeEnum.PIECE.getType())
item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType())
.setCharge(chargeBO).setFree(freeBO));
}
@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
// 断言SKU1
assertThat(resultBO.getItems().get(0))
.extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
.containsExactly(100, 2, 0, 0, 0, 1500, 1700);
.containsExactly(100, 2, 0, 0, 0, 500, 700);
// 断言SKU2
assertThat(resultBO.getItems().get(1))
.extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
.containsExactly(200, 10, 0, 0, 0, 1500, 3500);
.containsExactly(200, 10, 0, 0, 0, 2500, 4500);
// 断言SKU3 未选中
assertThat(resultBO.getItems().get(2))
.extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")

View File

@ -46,6 +46,7 @@ public interface ErrorCodeConstants {
ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1007007002, "未找到对应的钱包交易");
ErrorCode WALLET_REFUND_AMOUNT_ERROR = new ErrorCode(1007007003, "钱包退款金额不对");
ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1007007004, "已经存在钱包退款");
ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1007007005, "钱包冻结余额不足");
// TODO @jason把钱包充值单独搞个错误码段哈
@ -54,7 +55,12 @@ public interface ErrorCodeConstants {
ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1007007007, "钱包充值更新支付状态失败,支付单编号不匹配");
ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1007007008, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态");
ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH = new ErrorCode(1007007009, "钱包充值更新支付状态失败,支付单金额不匹配");
ErrorCode WALLET_RECHARGE_REFUND_FAIL_NOT_PAID = new ErrorCode(1007900010, "钱包发起退款失败,钱包充值订单未支付");
ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUNDED = new ErrorCode(1007900011, "钱包发起退款失败,钱包充值订单已退款");
ErrorCode WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH = new ErrorCode(1007900012, "钱包发起退款失败,钱包余额不足");
ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1007900013, "钱包退款更新失败,钱包退款单编号不匹配");
ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1007900014, "钱包退款更新失败,退款订单不存在");
ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1007900015, "钱包退款更新失败,退款单金额不匹配");
// ========== 示例订单 1007900000 ==========
ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");
ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, "示例订单更新支付状态失败,订单不是【未支付】状态");

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@Tag(name = "管理后台 - 钱包充值")
@RestController
@RequestMapping("/pay/wallet-recharge")
@Validated
@Slf4j
public class PayWalletRechargeController {
@Resource
private PayWalletRechargeService walletRechargeService;
@GetMapping("/refund")
@Operation(summary = "发起钱包充值退款")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) {
walletRechargeService.refundWalletRecharge(id,getClientIP());
return success(true);
}
@PostMapping("/update-refunded")
@Operation(summary = "更新钱包充值为已退款") // pay-module 支付服务进行回调可见 PayNotifyJob
@PermitAll // 无需登录 内部校验实现
@OperateLog(enable = false) // 禁用操作日志因为没有操作人
public CommonResult<Boolean> updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
walletRechargeService.updateWalletRechargeRefunded(
Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId());
return success(true);
}
}

View File

@ -53,7 +53,4 @@ public class AppPayWalletRechargeController {
notifyReqDTO.getPayOrderId());
return success(true);
}
// TODO @jason管理后台是不是可以搞个发起退款
}

View File

@ -42,6 +42,11 @@ public class PayWalletDO extends BaseDO {
*/
private Integer balance;
/**
* 冻结金额单位分
*/
private Integer freezePrice;
/**
* 累计支出单位分
*/

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.wallet;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@ -99,4 +100,11 @@ public class PayWalletRechargeDO extends BaseDO {
*/
private LocalDateTime refundTime;
/**
* 退款状态
*
* 枚举 {@link PayRefundStatusEnum}
*/
private Integer refundStatus;
}

View File

@ -56,7 +56,51 @@ public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
.eq(PayWalletDO::getId, id);
return update(null, lambdaUpdateWrapper);
}
/**
* 冻结钱包部分余额
* @param id 钱包 id
* @param price 冻结金额
*/
default int freezePrice(Long id, Integer price){
LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
.setSql(" balance = balance - " + price
+ ", freeze_price = freeze_price + " + price)
.eq(PayWalletDO::getId, id)
.ge(PayWalletDO::getBalance, price); // cas 逻辑
return update(null, lambdaUpdateWrapper);
}
/**
* 解冻钱包余额
* @param id 钱包 id
* @param price 解冻金额
*/
default int unFreezePrice(Long id, Integer price){
LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
.setSql(" balance = balance + " + price
+ ", freeze_price = freeze_price - " + price)
.eq(PayWalletDO::getId, id)
.ge(PayWalletDO::getFreezePrice, price); // cas 逻辑
return update(null, lambdaUpdateWrapper);
}
/**
* 当充值退款时, 更新钱包
* @param id 钱包 id
* @param price 退款金额
*/
default int updateWhenRechargeRefund(Long id, Integer price){
LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
.setSql(" freeze_price = freeze_price - " + price
+ ", total_recharge = total_recharge - " + price)
.eq(PayWalletDO::getId, id)
.ge(PayWalletDO::getFreezePrice, price)
.ge(PayWalletDO::getTotalRecharge, price);// cas 逻辑
return update(null, lambdaUpdateWrapper);
}
}

View File

@ -13,6 +13,11 @@ public interface PayWalletRechargeMapper extends BaseMapperX<PayWalletRechargeDO
.eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getPayStatus, wherePayStatus));
}
default int updateByIdAndRefunded(Long id, Integer whereRefundStatus, PayWalletRechargeDO updateObj){
return update(updateObj, new LambdaQueryWrapperX<PayWalletRechargeDO>()
.eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getRefundStatus, whereRefundStatus));
}
}

View File

@ -29,4 +29,17 @@ public interface PayWalletRechargeService {
*/
void updateWalletRechargerPaid(Long id, Long payOrderId);
/**
* 发起钱包充值退款
* @param id 钱包充值编号
* @param userIp 用户 ip 地址
*/
void refundWalletRecharge(Long id, String userIp);
/**
* 更新钱包充值记录为已退款
* @param id 钱包充值 id
* @param payRefundId 退款单id
*/
void updateWalletRechargeRefunded(Long id, Long payRefundId);
}

View File

@ -1,15 +1,21 @@
package cn.iocoder.yudao.module.pay.service.wallet;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
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.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper;
import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -24,6 +30,7 @@ import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.add
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*;
/**
* 钱包充值 Service 实现类
@ -47,6 +54,8 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
private PayWalletService payWalletService;
@Resource
private PayOrderService payOrderService;
@Resource
private PayRefundService payRefundService;
@Override
@Transactional(rollbackFor = Exception.class)
@ -98,6 +107,113 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
PayWalletBizTypeEnum.RECHARGE, walletRecharge.getTotalPrice());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refundWalletRecharge(Long id, String userIp) {
// 1.1 获取钱包充值记录
PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
if (walletRecharge == null) {
log.error("[refundWalletRecharge][钱包充值记录不存在,钱包充值记录 id({})]", id);
throw exception(WALLET_RECHARGE_NOT_FOUND);
}
// 1.2 校验钱包充值是否可以发起退款
PayWalletDO wallet = validateWalletRechargeCanRefund(walletRecharge);
// 2 冻结退款的余额 暂时只处理赠送的余额也全部退回
payWalletService.freezePrice(wallet.getId(), walletRecharge.getTotalPrice());
// 3 创建退款单
String walletRechargeId = String.valueOf(id);
String refundId = walletRechargeId + "-refund";
Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
.setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
.setMerchantOrderId(walletRechargeId)
.setMerchantRefundId(refundId)
.setReason("想退钱").setPrice(walletRecharge.getPayPrice()));
// 4 更新充值记录退款单号
walletRechargeMapper.updateById(new PayWalletRechargeDO().setPayRefundId(payRefundId)
.setRefundStatus(WAITING.getStatus()).setId(walletRecharge.getId()));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateWalletRechargeRefunded(Long id, Long payRefundId) {
// 1.1 获取钱包充值记录
PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
if (walletRecharge == null) {
log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id);
throw exception(WALLET_RECHARGE_NOT_FOUND);
}
// 1.2 校验钱包充值是否可以更新已退款
PayRefundDO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id);
// 退款成功
if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
// 2.1 更新钱包余额
payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id,
PayWalletBizTypeEnum.RECHARGE_REFUND, walletRecharge.getTotalPrice());
updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime())
.setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice())
.setRefundBonusPrice(walletRecharge.getBonusPrice());
}
// 退款失败
if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
// 2.2 解冻余额
payWalletService.unFreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice());
updateObj.setRefundStatus(FAILURE.getStatus());
}
// 3. 更新钱包充值的退款字段
walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
}
private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
// 1. 校验退款订单匹配
if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) {
log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]",
walletRecharge.getId(), payRefundId, toJsonString(walletRecharge));
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
}
// 2.1 校验退款订单
PayRefundDO payRefund = payRefundService.getRefund(payRefundId);
if (payRefund == null) {
log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId);
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
}
// 2.2 校验退款金额一致
if (notEqual(payRefund.getRefundPrice(), walletRecharge.getPayPrice())) {
log.error("[validateWalletRechargeCanRefunded][钱包({}) payRefund({}) 退款金额不匹配,请进行处理!钱包数据是:{}payRefund 数据是:{}]",
walletRecharge.getId(), payRefundId, toJsonString(walletRecharge), toJsonString(payRefund));
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
}
// 2.3 校验退款订单商户订单是否匹配
if (notEqual(payRefund.getMerchantOrderId(), walletRecharge.getId().toString())) {
log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({})请进行处理payRefund 数据是:{}]",
walletRecharge.getId(), payRefundId, toJsonString(payRefund));
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
}
return payRefund;
}
private PayWalletDO validateWalletRechargeCanRefund(PayWalletRechargeDO walletRecharge) {
// 校验充值订单是否支付
if (!walletRecharge.getPayStatus()) {
throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID);
}
// 校验充值订单是否已退款
if (walletRecharge.getPayRefundId() != null) {
throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED);
}
// 校验钱包余额是否足够
PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId());
Assert.notNull(wallet, "用户钱包({}) 不存在", wallet.getId());
if (wallet.getBalance() < walletRecharge.getTotalPrice()) {
throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH);
}
return wallet;
}
private PayOrderDO validateWalletRechargerCanPaid(PayWalletRechargeDO walletRecharge, Long payOrderId) {
// 1.1 校验充值记录的支付状态
if (walletRecharge.getPayStatus()) {

View File

@ -50,15 +50,14 @@ public interface PayWalletService {
/**
* 扣减钱包余额
*
* @param userId 用户 id
* @param userType 用户类型
* @param walletId 钱包 id
* @param bizId 业务关联 id
* @param bizType 业务关联分类
* @param price 扣减金额
* @return 钱包流水
*/
PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,
Long bizId, PayWalletBizTypeEnum bizType, Integer price);
PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
PayWalletBizTypeEnum bizType, Integer price);
/**
* 增加钱包余额
@ -72,4 +71,18 @@ public interface PayWalletService {
PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
PayWalletBizTypeEnum bizType, Integer price);
/**
* 冻结钱包部分余额
*
* @param id 钱包编号
* @param price 冻结金额
*/
void freezePrice(Long id, Integer price);
/**
* 解冻钱包余额
* @param id 钱包编号
* @param price 解冻金额
*/
void unFreezePrice(Long id, Integer price);
}

View File

@ -68,8 +68,9 @@ public class PayWalletServiceImpl implements PayWalletService {
if (orderExtension == null) {
throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
}
PayWalletDO wallet = getOrCreateWallet(userId, userType);
// 2. 扣减余额
return reduceWalletBalance(userId, userType, orderExtension.getOrderId(), PAYMENT, price);
return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price);
}
@Override
@ -116,10 +117,14 @@ public class PayWalletServiceImpl implements PayWalletService {
}
@Override
public PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,
Long bizId, PayWalletBizTypeEnum bizType, Integer price) {
public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
PayWalletBizTypeEnum bizType, Integer price) {
// 1. 获取钱包
PayWalletDO payWallet = getOrCreateWallet(userId, userType);
PayWalletDO payWallet = getWallet(walletId);
if (payWallet == null) {
log.error("[reduceWalletBalance],用户钱包({})不存在.", walletId);
throw exception(WALLET_NOT_FOUND);
}
// 2.1 扣除余额
int updateCounts = 0 ;
@ -129,9 +134,13 @@ public class PayWalletServiceImpl implements PayWalletService {
break;
}
case RECHARGE_REFUND: {
// TODO
updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price);
break;
}
default: {
// TODO 其它类型待实现
throw new UnsupportedOperationException("待实现");
}
}
if (updateCounts == 0) {
throw exception(WALLET_BALANCE_NOT_ENOUGH);
@ -163,7 +172,10 @@ public class PayWalletServiceImpl implements PayWalletService {
walletMapper.updateWhenRecharge(payWallet.getId(), price);
break;
}
// TODO 其它类型这里可以先跑异常避免有业务搞错
default: {
// TODO 其它类型待实现
throw new UnsupportedOperationException("待实现");
}
}
// 2. 生成钱包流水
@ -173,4 +185,20 @@ public class PayWalletServiceImpl implements PayWalletService {
return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
}
@Override
public void freezePrice(Long id, Integer price) {
int updateCounts = walletMapper.freezePrice(id, price);
if (updateCounts == 0) {
throw exception(WALLET_BALANCE_NOT_ENOUGH);
}
}
@Override
public void unFreezePrice(Long id, Integer price) {
int updateCounts = walletMapper.unFreezePrice(id, price);
if (updateCounts == 0) {
throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH);
}
}
}