记得上下班打卡 | git大法好,push需谨慎

Commit 7f74125e authored by wangyifan's avatar wangyifan

优化用户退款接口

parent bdbcf1f6
...@@ -243,9 +243,16 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService { ...@@ -243,9 +243,16 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
return ResponseDto.success(true); return ResponseDto.success(true);
} }
/**
* 用户发起退款申请。
* <p>
* 发货前(status=2):整单自动退款,含运费,直接调支付网关。
* 发货后(status=3/4):可选整单或指定SKU,生成待审核退款单,不含运费。
*/
@Override @Override
public ResponseDto<Boolean> applyRefund(GoblinAppOrderRefundParam param) { public ResponseDto<Boolean> applyRefund(GoblinAppOrderRefundParam param) {
// ---- 准备 SQL 容器 ----
LinkedList<String> sqls = CollectionUtil.linkedListString(); LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_order.user.applyRefundAuto")); sqls.add(SqlMapping.get("goblin_order.user.applyRefundAuto"));
sqls.add(SqlMapping.get("goblin_order.store.orderStatus")); sqls.add(SqlMapping.get("goblin_order.store.orderStatus"));
...@@ -253,30 +260,30 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService { ...@@ -253,30 +260,30 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
sqls.add(SqlMapping.get("goblin_order.store.refundLog")); sqls.add(SqlMapping.get("goblin_order.store.refundLog"));
sqls.add(SqlMapping.get("candy_user_coupon.update_apply_refund")); sqls.add(SqlMapping.get("candy_user_coupon.update_apply_refund"));
LinkedList<Object[]> applyRefund = CollectionUtil.linkedListObjectArr(); LinkedList<Object[]> applyRefundObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> orderStatus = CollectionUtil.linkedListObjectArr(); LinkedList<Object[]> orderStatusObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> orderSkuStatus = CollectionUtil.linkedListObjectArr(); LinkedList<Object[]> orderSkuStatusObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> refundLog = CollectionUtil.linkedListObjectArr(); LinkedList<Object[]> refundLogObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> updateCandyUserCouponObjs = CollectionUtil.linkedListObjectArr(); LinkedList<Object[]> updateCouponObjs = CollectionUtil.linkedListObjectArr();
String uid = CurrentUtil.getCurrentUid(); String uid = CurrentUtil.getCurrentUid();
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
String nowStr = DateUtil.getNowTime(); String nowStr = DateUtil.getNowTime();
// ---- 1. 订单基础校验 ----
GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(param.getOrderId()); GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(param.getOrderId());
if (orderVo == null) { if (orderVo == null) {
return ResponseDto.failure("不可操作"); return ResponseDto.failure("不可操作");
} }
// if (!orderVo.getUserId().equals(uid)) {
// return ResponseDto.failure("无权操作");
// }
// 分布式锁防并发重复退款 // ---- 2. 分布式锁防并发重复退款 ----
String lockKey = GoblinRedisConst.REFUND_ORDER_LOCK + param.getOrderId(); String lockKey = GoblinRedisConst.REFUND_ORDER_LOCK + param.getOrderId();
if (!redisUtils.redisUtil.lock(lockKey, 1, 30)) { if (!redisUtils.redisUtil.lock(lockKey, 1, 30)) {
return ResponseDto.failure("退款处理中,请勿重复操作"); return ResponseDto.failure("退款处理中,请勿重复操作");
} }
try { try {
// ---- 3. 退款前置校验 ----
// 3a. 是否有处理中的退款单
List<String> backOrderIds = redisUtils.getBackOrderByOrderId(param.getOrderId()); List<String> backOrderIds = redisUtils.getBackOrderByOrderId(param.getOrderId());
for (String backOrderId : backOrderIds) { for (String backOrderId : backOrderIds) {
GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId); GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
...@@ -284,29 +291,150 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService { ...@@ -284,29 +291,150 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
return ResponseDto.failure("申请失败"); return ResponseDto.failure("申请失败");
} }
} }
// 3b. 订单状态:仅允许待发货(2) / 待收货(3) / 已完成(4)
if (!(orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue() || int orderStatus = orderVo.getStatus();
orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_3.getValue() || if (!(orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue() || orderStatus == GoblinStatusConst.Status.ORDER_STATUS_3.getValue() || orderStatus == GoblinStatusConst.Status.ORDER_STATUS_4.getValue())) {
orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue())) {
return ResponseDto.failure("不可操作"); return ResponseDto.failure("不可操作");
} }
//判断7天 // 3c. 7天可退窗口
LocalDateTime canRefundTime = getCanRefundTime(orderVo); LocalDateTime canRefundTime = getCanRefundTime(orderVo);
if (canRefundTime == null) { if (canRefundTime == null || LocalDateTime.now().isAfter(canRefundTime)) {
return ResponseDto.failure("申请失败"); return ResponseDto.failure("申请失败");
}
// 3d. 用券订单仅支持整单退款
if (orderHasUsedCoupon(orderVo) && param.getOrderSkuId() != null) {
return ResponseDto.failure("用券仅支持整单退款");
}
// 3e. 发货前仅支持整单退款
if (orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue() && StringUtil.isNotBlank(param.getOrderSkuId())) {
return ResponseDto.failure("发货前仅支持整单退款");
}
// ---- 4. 构建退款单骨架(不含SKU明细和最终金额) ----
GoblinBackOrder backOrder = buildBackOrderSkeleton(orderVo, param, now);
// ---- 5. 构建退款单 VO ----
GoblinBackOrderVo vo = GoblinBackOrderVo.getNew();
BeanUtils.copyProperties(backOrder, vo);
vo.setCreatedAt(nowStr);
// ---- 6. 处理退款SKU列表 ----
List<GoblinBackOrderSkuVo> orderSkuVoList = ObjectUtil.goblinBackOrderSkuVoArrayList();
BigDecimal skuRefundPrice = BigDecimal.ZERO;
boolean isUnshipped = orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue();
boolean hasSpecificSku = StringUtil.isNotBlank(param.getOrderSkuId());
if (isUnshipped) {
// 发货前:仅支持整单退款,遍历所有SKU
orderStatusObjs.add(new Object[]{orderVo.getStatus(), orderVo.getZhengzaiStatus(), now,
orderVo.getOrderId(), now, now});
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
BigDecimal refundPrice = buildAndPersistRefundSku(orderSkuId, orderSkuVo, false,
nowStr, now, orderSkuVoList, orderSkuStatusObjs);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderLog(orderVo, orderSkuVo, now);
}
} else if (hasSpecificSku) {
// 发货后 + 指定SKU:仅退选中的单个商品
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(param.getOrderSkuId());
if (orderSkuVo == null || !orderVo.getOrderId().equals(orderSkuVo.getOrderId())) {
return ResponseDto.failure("不存在");
}
// 券类商品校验:已使用则拒绝,未使用则置为无效
String couponError = validateAndDisableCouponForRefund(orderSkuVo, orderVo.getUserId(), now, updateCouponObjs);
if (couponError != null) {
return ResponseDto.failure(couponError);
}
BigDecimal refundPrice = buildAndPersistRefundSku(param.getOrderSkuId(), orderSkuVo, true,
nowStr, now, orderSkuVoList, orderSkuStatusObjs);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderLog(orderVo, orderSkuVo, now);
} else { } else {
if (LocalDateTime.now().isAfter(canRefundTime)) { // 发货后 + 整单:遍历所有SKU
return ResponseDto.failure("申请失败"); for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
String couponError = validateAndDisableCouponForRefund(orderSkuVo, orderVo.getUserId(), now, updateCouponObjs);
if (couponError != null) {
return ResponseDto.failure(couponError);
}
BigDecimal refundPrice = buildAndPersistRefundSku(orderSkuId, orderSkuVo, true,
nowStr, now, orderSkuVoList, orderSkuStatusObjs);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderLog(orderVo, orderSkuVo, now);
} }
} }
//判断券 // ---- 7. 最终金额与SKU汇总校验 ----
if ((!(orderVo.getUcouponId() == null || orderVo.getUcouponId().equals("")) || !(orderVo.getStoreCouponId() == null || orderVo.getStoreCouponId().equals(""))) if (skuRefundPrice.compareTo(BigDecimal.ZERO) <= 0) {
&& param.getOrderSkuId() != null) { return ResponseDto.failure("退款价格超过商品可退价格");
return ResponseDto.failure("用券仅支持整单退款"); }
fillSkuIdNumsIfBlank(backOrder, orderSkuVoList);
setFinalRefundPrice(backOrder, orderVo, skuRefundPrice);
syncBackOrderVoFields(vo, backOrder, orderSkuVoList, nowStr);
// ---- 8. 操作日志 ----
GoblinBackOrderLog backOrderLog = initBackLog(param.getOrderId(), uid, now);
backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_21.getValue());
backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_1.getValue());
backOrderLog.setMessage("用户发起发起:" + JsonUtils.toJson(param));
// ---- 9. 持久化(Redis + Mongo + MySQL) ----
applyRefundObjs.add(new Object[]{
backOrder.getBackOrderId(), backOrder.getBackCode(), backOrder.getOrderId(),
backOrder.getOrderCode(), backOrder.getStoreId(), backOrder.getUserId(),
backOrder.getSkuIdNums(), backOrder.getType(), backOrder.getReason(),
backOrder.getDescribes(), backOrder.getRealBackPrice(), backOrder.getBackPriceExpress(), backOrder.getStatus(),
backOrder.getLogisCompanyName(), backOrder.getMailNo(), backOrder.getPics(), backOrder.getCreatedAt(),
backOrder.getAuditAt(), backOrder.getErrorReason()
});
refundLogObjs.add(new Object[]{
backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
});
persistRefundOrder(orderVo, backOrder, vo,
sqls, applyRefundObjs, orderStatusObjs, orderSkuStatusObjs, refundLogObjs, updateCouponObjs);
// ---- 10. 发货前自动调支付网关退款 ----
if (isUnshipped) {
return executeAutoRefund(orderVo, backOrder, vo, now);
}
return ResponseDto.success();
} finally {
redisUtils.redisUtil.uLock(lockKey);
} }
}
// ======================== 私有辅助方法 ========================
//退款订单生成 /**
* 判断订单是否使用了优惠券(用户券或店铺券)。
*/
private boolean orderHasUsedCoupon(GoblinStoreOrderVo orderVo) {
boolean hasUserCoupon = orderVo.getUcouponId() != null && !orderVo.getUcouponId().isEmpty();
boolean hasStoreCoupon = orderVo.getStoreCouponId() != null && !orderVo.getStoreCouponId().isEmpty();
return hasUserCoupon || hasStoreCoupon;
}
/**
* 构建退款单骨架,填充与SKU无关的基础字段。
* <p>
* 发货前(status=2):状态=自动退款(0),含运费,必须整单。
* 发货后(status=3/4):状态=待审核(1),不含运费,可选整单或指定SKU。
*/
private GoblinBackOrder buildBackOrderSkeleton(GoblinStoreOrderVo orderVo,
GoblinAppOrderRefundParam param,
LocalDateTime now) {
GoblinBackOrder backOrder = GoblinBackOrder.getNew(); GoblinBackOrder backOrder = GoblinBackOrder.getNew();
backOrder.setBackOrderId(IDGenerator.nextTimeId2()); backOrder.setBackOrderId(IDGenerator.nextTimeId2());
backOrder.setBackCode(IDGenerator.storeRefundCode(orderVo.getMasterOrderCode())); backOrder.setBackCode(IDGenerator.storeRefundCode(orderVo.getMasterOrderCode()));
...@@ -319,10 +447,10 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService { ...@@ -319,10 +447,10 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
backOrder.setType(GoblinStatusConst.Type.BACK_TYPE_1.getValue()); backOrder.setType(GoblinStatusConst.Type.BACK_TYPE_1.getValue());
backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_1.getValue()); backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_1.getValue());
backOrder.setCreatedAt(now); backOrder.setCreatedAt(now);
if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {//未发货
if (StringUtil.isNotBlank(param.getOrderSkuId())) { int orderStatus = orderVo.getStatus();
return ResponseDto.failure("发货前仅支持整单退款"); if (orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {
} // 未发货:仅支持整单退,含运费,自动退款(整单校验已在主方法完成)
backOrder.setBackPriceExpress(orderVo.getPriceExpress() == null ? BigDecimal.ZERO : orderVo.getPriceExpress()); backOrder.setBackPriceExpress(orderVo.getPriceExpress() == null ? BigDecimal.ZERO : orderVo.getPriceExpress());
backOrder.setReason(param.getReason()); backOrder.setReason(param.getReason());
backOrder.setPics(param.getPics()); backOrder.setPics(param.getPics());
...@@ -330,240 +458,231 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService { ...@@ -330,240 +458,231 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue()); backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue());
backOrder.setAuditAt(now); backOrder.setAuditAt(now);
backOrder.setSkuIdNums(Joiner.on(",").join(orderVo.getOrderSkuVoIds())); backOrder.setSkuIdNums(Joiner.on(",").join(orderVo.getOrderSkuVoIds()));
} else if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_3.getValue() ||
orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue()) {//已发货/已完成
if (StringUtil.isNotBlank(param.getOrderSkuId())) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(param.getOrderSkuId());
if (orderSkuVo == null || !orderVo.getOrderId().equals(orderSkuVo.getOrderId())) {
return ResponseDto.failure("不存在");
}
BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(orderSkuVo.getOrderSkuId());
backOrder.setRealBackPrice(GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice));
backOrder.setBackPriceExpress(BigDecimal.ZERO);
} else { } else {
// 已发货/已完成(status=3/4):不含运费,生成待审核退款单
backOrder.setBackPriceExpress(BigDecimal.ZERO); backOrder.setBackPriceExpress(BigDecimal.ZERO);
}
backOrder.setReason(param.getReason()); backOrder.setReason(param.getReason());
backOrder.setPics(param.getPics()); backOrder.setPics(param.getPics());
backOrder.setDescribes(param.getDescribes()); backOrder.setDescribes(param.getDescribes());
backOrder.setType(param.getRefundType() == null ? GoblinStatusConst.Type.BACK_TYPE_1.getValue() : param.getRefundType()); backOrder.setType(param.getRefundType() == null ? GoblinStatusConst.Type.BACK_TYPE_1.getValue() : param.getRefundType());
backOrder.setSkuIdNums(param.getOrderSkuId()); backOrder.setSkuIdNums(param.getOrderSkuId());
} else {
return ResponseDto.failure("不可申请");
}
GoblinBackOrderVo vo = GoblinBackOrderVo.getNew();
BeanUtils.copyProperties(backOrder, vo);
vo.setCreatedAt(nowStr);
List<GoblinBackOrderSkuVo> orderSkuVoList = ObjectUtil.goblinBackOrderSkuVoArrayList();
BigDecimal skuRefundPrice = BigDecimal.ZERO;
if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {
//订单状态修改
// orderVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
orderStatus.add(new Object[]{
orderVo.getStatus(), orderVo.getZhengzaiStatus(), now,
orderVo.getOrderId(), now, now
});
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
//订单款式状态修改
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew();
backOrderSkuVo.setOrderSkuId(orderSkuId);
backOrderSkuVo.setSpuId(orderSkuVo.getSpuId());
backOrderSkuVo.setSpuName(orderSkuVo.getSpuName());
backOrderSkuVo.setSkuId(orderSkuVo.getSkuId());
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage());
backOrderSkuVo.setSkuName(orderSkuVo.getSkuName());
backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs());
BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(orderSkuId);
BigDecimal refundPrice = GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderSkuVo.setRefundPrice(refundPrice);
backOrderSkuVo.setCreatedAt(nowStr);
orderSkuVoList.add(backOrderSkuVo);
backOrderLog(orderVo, orderSkuVo, now);
//redis
redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
//mongo
mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
orderSkuStatus.add(new Object[]{
orderSkuVo.getStatus(), now,
orderSkuVo.getOrderSkuId(), now, now
});
} }
} else if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_3.getValue() || return backOrder;
orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue()) { }
if (StringUtil.isNotBlank(param.getOrderSkuId())) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(param.getOrderSkuId()); /**
if (orderSkuVo == null || !orderVo.getOrderId().equals(orderSkuVo.getOrderId())) { * 券类商品退款前的校验与状态处理:
return ResponseDto.failure("不存在"); * <ul>
* <li>非券类商品(skuType != 2):直接通过</li>
* <li>券状态=5(已使用):拒绝退款</li>
* <li>券状态其他:置为2(无效),防止用户在审核期间消费该券</li>
* </ul>
*
* @return 错误提示,null 表示校验通过
*/
private String validateAndDisableCouponForRefund(GoblinOrderSkuVo orderSkuVo,
String userId,
LocalDateTime now,
LinkedList<Object[]> updateCouponObjs) {
if (!Objects.equals(orderSkuVo.getSkuType(), 2)) {
return null; // 非券类商品,无需校验
} }
if (Objects.equals(orderSkuVo.getSkuType(), 2)) {// 券类商品-校验发放的券是否已使用
// 券类商品默认一个商品对应一个券,下单只可购买一张 // 券类商品默认一个商品对应一个券,下单只可购买一张
String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(orderVo.getUserId()); String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(userId);
List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisUtils.get(ucKey); List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisUtils.get(ucKey);
if (vos == null || vos.isEmpty()) {
log.warn("券类商品订单申请退款,未找到用户券列表[orderSkuId={},uid={}]",
orderSkuVo.getOrderSkuId(), userId);
return null;
}
int idx = IntStream.range(0, vos.size()) int idx = IntStream.range(0, vos.size())
.filter(i -> vos.get(i).getUcouponId().equals(orderSkuVo.getOrderSkuId())).findFirst().orElse(-1); .filter(i -> vos.get(i).getUcouponId().equals(orderSkuVo.getOrderSkuId()))
.findFirst().orElse(-1);
if (-1 != idx) { if (-1 != idx) {
CandyUserCouponBasicDto basicDto = vos.get(idx); CandyUserCouponBasicDto basicDto = vos.get(idx);
if (5 == basicDto.getState()) {// 券状态为'5-已使用',则不可退款 if (5 == basicDto.getState()) {
return ResponseDto.failure("券已使用,不可申请"); return "券已使用,不可申请"; // 券已使用,不可退款
} }
basicDto.setState(2);// 置为'2-无效',防止用户在后台审核过程中使用券,造成券已使用,订单也退款 // 置为无效,防止用户在后台审核过程中使用券
basicDto.setState(2);
vos.set(idx, basicDto); vos.set(idx, basicDto);
redisUtils.redisUtil.set(ucKey, vos); redisUtils.redisUtil.set(ucKey, vos);
updateCandyUserCouponObjs.add(new Object[]{basicDto.getState(), orderVo.getUserId(), now, basicDto.getUcouponId()}); updateCouponObjs.add(new Object[]{basicDto.getState(), userId, now, basicDto.getUcouponId()});
} else { } else {
log.warn("券类商品订单申请退款,未找到对应券[orderSkuId={},uid={}]", orderSkuVo.getOrderSkuId(), orderVo.getUserId()); log.warn("券类商品订单申请退款,未找到对应券[orderSkuId={},uid={}]",
} orderSkuVo.getOrderSkuId(), userId);
} }
//订单款式状态修改 return null;
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue()); }
GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew();
backOrderSkuVo.setOrderSkuId(param.getOrderSkuId()); /**
backOrderSkuVo.setSpuId(orderSkuVo.getSpuId()); * 为单个SKU:计算可退金额、构建 BackOrderSkuVo、更新SKU状态到Redis/Mongo、生成SQL参数。
backOrderSkuVo.setSpuName(orderSkuVo.getSpuName()); *
backOrderSkuVo.setSkuId(orderSkuVo.getSkuId()); * @param orderSkuId SKU子订单ID
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage()); * @param orderSkuVo 对应的SKU VO(调用方已从Redis获取)
backOrderSkuVo.setSkuName(orderSkuVo.getSkuName()); * @param setSkuType 是否设置skuType(发货后需要,发货前不需要)
BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(param.getOrderSkuId()); * @return 该SKU的可退金额(remainRefundPrice 结果)
*/
private BigDecimal buildAndPersistRefundSku(String orderSkuId,
GoblinOrderSkuVo orderSkuVo,
boolean setSkuType,
String nowStr,
LocalDateTime now,
List<GoblinBackOrderSkuVo> orderSkuVoList,
LinkedList<Object[]> orderSkuStatusObjs) {
// 计算可退金额 = skuPriceActual - priceRefund - 申请中的退款额
BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(orderSkuId);
BigDecimal refundPrice = GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice); BigDecimal refundPrice = GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格"); // 更新SKU状态为"退款中"
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderSkuVo.setRefundPrice(refundPrice);
backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs());
backOrderSkuVo.setCreatedAt(nowStr);
backOrderSkuVo.setSkuType(orderSkuVo.getSkuType());
orderSkuVoList.add(backOrderSkuVo);
backOrderLog(orderVo, orderSkuVo, now);
//redis
redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
//mongo
mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
orderSkuStatus.add(new Object[]{
orderSkuVo.getStatus(), now,
orderSkuVo.getOrderSkuId(), now, now
});
} else {
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
//订单款式状态修改
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
if (Objects.equals(orderSkuVo.getSkuType(), 2)) {// 券类商品-校验发放的券是否已使用
// 券类商品默认一个商品对应一个券,下单只可购买一张
String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(orderVo.getUserId());
List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisUtils.get(ucKey);
int idx = IntStream.range(0, vos.size()).filter(i -> vos.get(i).getUcouponId().equals(orderSkuVo.getOrderSkuId())).findFirst().orElse(-1);
if (-1 != idx) {
CandyUserCouponBasicDto basicDto = vos.get(idx);
if (5 == basicDto.getState()) {// 券状态为'5-已使用',则不可退款
return ResponseDto.failure("券已使用,不可申请");
}
basicDto.setState(2);// 置为'2-无效',防止用户在后台审核过程中使用券,造成券已使用,订单也退款
vos.set(idx, basicDto);
redisUtils.redisUtil.set(ucKey, vos);
updateCandyUserCouponObjs.add(new Object[]{basicDto.getState(), orderVo.getUserId(), now, basicDto.getUcouponId()});
} else {
log.warn("券类商品订单申请退款,未找到对应券[orderSkuId={},uid={}]", orderSkuVo.getOrderSkuId(), orderVo.getUserId());
}
}
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue()); orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
// 构建 BackOrderSkuVo
GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew(); GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew();
backOrderSkuVo.setOrderSkuId(orderSkuId); backOrderSkuVo.setOrderSkuId(orderSkuId);
backOrderSkuVo.setSpuId(orderSkuVo.getSpuId()); backOrderSkuVo.setSpuId(orderSkuVo.getSpuId());
backOrderSkuVo.setSpuName(orderSkuVo.getSpuName()); backOrderSkuVo.setSpuName(orderSkuVo.getSpuName());
backOrderSkuVo.setSkuId(orderSkuVo.getSkuId()); backOrderSkuVo.setSkuId(orderSkuVo.getSkuId());
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage());
backOrderSkuVo.setSkuName(orderSkuVo.getSkuName()); backOrderSkuVo.setSkuName(orderSkuVo.getSkuName());
backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs()); backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs());
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage());
BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(orderSkuId);
BigDecimal refundPrice = GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderSkuVo.setRefundPrice(refundPrice); backOrderSkuVo.setRefundPrice(refundPrice);
backOrderSkuVo.setCreatedAt(nowStr); backOrderSkuVo.setCreatedAt(nowStr);
if (setSkuType) {
backOrderSkuVo.setSkuType(orderSkuVo.getSkuType()); backOrderSkuVo.setSkuType(orderSkuVo.getSkuType());
}
orderSkuVoList.add(backOrderSkuVo); orderSkuVoList.add(backOrderSkuVo);
backOrderLog(orderVo, orderSkuVo, now);
//redis // 持久化SKU状态:Redis + Mongo
redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo); redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
//mongo
mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo); mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
orderSkuStatus.add(new Object[]{
// 生成MySQL更新参数
orderSkuStatusObjs.add(new Object[]{
orderSkuVo.getStatus(), now, orderSkuVo.getStatus(), now,
orderSkuVo.getOrderSkuId(), now, now orderSkuVo.getOrderSkuId(), now, now
}); });
return refundPrice;
} }
/**
* 如果 skuIdNums 为空(发货后整单退场景),从 orderSkuVoList 中收集填充。
*/
private void fillSkuIdNumsIfBlank(GoblinBackOrder backOrder, List<GoblinBackOrderSkuVo> orderSkuVoList) {
if (StringUtil.isNotBlank(backOrder.getSkuIdNums())) {
return;
} }
} else {
return ResponseDto.failure("不可申请");
}
if (skuRefundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
if (StringUtil.isBlank(backOrder.getSkuIdNums())) {
List<String> refundOrderSkuIds = new LinkedList<>(); List<String> refundOrderSkuIds = new LinkedList<>();
for (GoblinBackOrderSkuVo backOrderSkuVo : orderSkuVoList) { for (GoblinBackOrderSkuVo vo : orderSkuVoList) {
refundOrderSkuIds.add(backOrderSkuVo.getOrderSkuId()); refundOrderSkuIds.add(vo.getOrderSkuId());
} }
backOrder.setSkuIdNums(Joiner.on(",").join(refundOrderSkuIds)); backOrder.setSkuIdNums(Joiner.on(",").join(refundOrderSkuIds));
} }
/**
* 计算并设置最终退款金额。
* 发货前:SKU总额 + 运费;发货后:仅SKU总额。
*/
private void setFinalRefundPrice(GoblinBackOrder backOrder, GoblinStoreOrderVo orderVo, BigDecimal skuRefundPrice) {
if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) { if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {
backOrder.setRealBackPrice(GoblinRefundHelper.beforeShipRefundPrice(skuRefundPrice, backOrder.getBackPriceExpress())); backOrder.setRealBackPrice(GoblinRefundHelper.beforeShipRefundPrice(skuRefundPrice, backOrder.getBackPriceExpress()));
} else { } else {
backOrder.setRealBackPrice(skuRefundPrice); backOrder.setRealBackPrice(skuRefundPrice);
backOrder.setBackPriceExpress(BigDecimal.ZERO); backOrder.setBackPriceExpress(BigDecimal.ZERO);
} }
}
/**
* 将 backOrder 中的最终字段同步到 vo 中。
*/
private void syncBackOrderVoFields(GoblinBackOrderVo vo, GoblinBackOrder backOrder,
List<GoblinBackOrderSkuVo> orderSkuVoList, String nowStr) {
vo.setRealBackPrice(backOrder.getRealBackPrice()); vo.setRealBackPrice(backOrder.getRealBackPrice());
vo.setBackPriceExpress(backOrder.getBackPriceExpress()); vo.setBackPriceExpress(backOrder.getBackPriceExpress());
vo.setStatus(backOrder.getStatus()); vo.setStatus(backOrder.getStatus());
vo.setAuditAt(backOrder.getAuditAt() == null ? null : nowStr); vo.setAuditAt(backOrder.getAuditAt() == null ? null : nowStr);
vo.setBackOrderSkuVos(orderSkuVoList); vo.setBackOrderSkuVos(orderSkuVoList);
vo.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_1.getValue()); vo.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_1.getValue());
//添加日志 }
GoblinBackOrderLog backOrderLog = initBackLog(param.getOrderId(), uid, now);
backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_21.getValue()); /**
backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_1.getValue()); * 持久化退款单到 Redis + Mongo + MySQL(MQ异步)。
backOrderLog.setMessage("用户发起发起:" + JsonUtils.toJson(param)); */
//redis private void persistRefundOrder(GoblinStoreOrderVo orderVo,
GoblinBackOrder backOrder,
GoblinBackOrderVo vo,
LinkedList<String> sqls,
LinkedList<Object[]> applyRefundObjs,
LinkedList<Object[]> orderStatusObjs,
LinkedList<Object[]> orderSkuStatusObjs,
LinkedList<Object[]> refundLogObjs,
LinkedList<Object[]> updateCouponObjs) {
// Redis
redisUtils.setBackOrderVo(backOrder.getBackOrderId(), vo); redisUtils.setBackOrderVo(backOrder.getBackOrderId(), vo);
redisUtils.setGoblinOrder(orderVo.getOrderId(), orderVo); redisUtils.setGoblinOrder(orderVo.getOrderId(), orderVo);
redisUtils.addBackOrderByOrderId(orderVo.getOrderId(), backOrder.getBackOrderId()); redisUtils.addBackOrderByOrderId(orderVo.getOrderId(), backOrder.getBackOrderId());
//mongo // Mongo
mongoUtils.insertGoblinBackOrderVo(vo); mongoUtils.insertGoblinBackOrderVo(vo);
mongoUtils.updateGoblinStoreOrderVo(orderVo.getOrderId(), orderVo); mongoUtils.updateGoblinStoreOrderVo(orderVo.getOrderId(), orderVo);
//mysql // MySQL (MQ异步)
applyRefund.add(new Object[]{ queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_USER_ORDER_OPERA.getKey(),
backOrder.getBackOrderId(), backOrder.getBackCode(), backOrder.getOrderId(), SqlMapping.gets(sqls, applyRefundObjs, orderStatusObjs, orderSkuStatusObjs, refundLogObjs, updateCouponObjs));
backOrder.getOrderCode(), backOrder.getStoreId(), backOrder.getUserId(), }
backOrder.getSkuIdNums(), backOrder.getType(), backOrder.getReason(),
backOrder.getDescribes(), backOrder.getRealBackPrice(), backOrder.getBackPriceExpress(), backOrder.getStatus(), /**
backOrder.getLogisCompanyName(), backOrder.getMailNo(), backOrder.getPics(), backOrder.getCreatedAt(), * 发货前自动退款:调用支付网关,失败则回滚状态。
backOrder.getAuditAt(), backOrder.getErrorReason() */
}); private ResponseDto<Boolean> executeAutoRefund(GoblinStoreOrderVo orderVo,
//添加日志 GoblinBackOrder backOrder,
refundLog.add(new Object[]{ GoblinBackOrderVo vo,
backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(), LocalDateTime now) {
backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now String returnString;
}); try {
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_USER_ORDER_OPERA.getKey(), SqlMapping.gets(sqls, applyRefund, orderStatus, orderSkuStatus, refundLog, updateCandyUserCouponObjs)); returnString = refundHelper.initRefund(orderVo, backOrder.getRealBackPrice(), backOrder.getBackCode());
if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) { } catch (Exception e) {
String returnString = refundHelper.initRefund(orderVo, backOrder.getRealBackPrice(), backOrder.getBackCode()); log.error("REFUND EXCEPTION, orderId={}", orderVo.getOrderId(), e);
rollbackAutoRefund(orderVo, backOrder, vo, now, "退款异常:" + e.getMessage());
return ResponseDto.failure("退款失败:退款异常:" + e.getMessage());
}
if (!refundHelper.isRefundSuccess(returnString)) { if (!refundHelper.isRefundSuccess(returnString)) {
// 退款失败,回滚退款单及SKU状态
String message = refundHelper.getRefundMessage(returnString); String message = refundHelper.getRefundMessage(returnString);
log.error("REFUND DATA = " + returnString);
rollbackAutoRefund(orderVo, backOrder, vo, now, "失败原因:" + message);
return ResponseDto.failure("退款失败:失败原因:" + message);
}
// 支付宝micropay需主动查询结果
if (DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY.equals(orderVo.getPaymentType())) {
refundHelper.alipayCallBack(orderVo, backOrder.getBackCode());
}
return ResponseDto.success();
}
/**
* 发货前自动退款失败/异常时,回滚退款单和SKU状态。
*/
private void rollbackAutoRefund(GoblinStoreOrderVo orderVo,
GoblinBackOrder backOrder,
GoblinBackOrderVo vo,
LocalDateTime now,
String errorReason) {
backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue()); backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue());
backOrder.setErrorReason("失败原因:" + message); backOrder.setErrorReason(errorReason);
vo.setStatus(backOrder.getStatus()); vo.setStatus(backOrder.getStatus());
vo.setErrorReason(backOrder.getErrorReason()); vo.setErrorReason(backOrder.getErrorReason());
log.error("REFUND DATA = " + returnString);
redisUtils.setBackOrderVo(backOrder.getBackOrderId(), vo); redisUtils.setBackOrderVo(backOrder.getBackOrderId(), vo);
mongoUtils.updateGoblinBackOrderVo(backOrder.getBackOrderId(), vo); mongoUtils.updateGoblinBackOrderVo(backOrder.getBackOrderId(), vo);
// 回滚SKU状态到订单原始状态
int rollbackStatus = orderVo.getStatus();
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
if (orderSkuVo != null) {
orderSkuVo.setStatus(rollbackStatus);
redisUtils.setGoblinOrderSku(orderSkuId, orderSkuVo);
mongoUtils.updateGoblinOrderSkuVo(orderSkuId, orderSkuVo);
}
}
queueUtils.sendMsgByRedis( queueUtils.sendMsgByRedis(
MQConst.GoblinQueue.GOBLIN_USER_ORDER_OPERA.getKey(), MQConst.GoblinQueue.GOBLIN_USER_ORDER_OPERA.getKey(),
SqlMapping.get("goblin_order.store.applyRefund", SqlMapping.get("goblin_order.store.applyRefund",
...@@ -571,16 +690,6 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService { ...@@ -571,16 +690,6 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
backOrder.getBackOrderId(), now, now backOrder.getBackOrderId(), now, now
) )
); );
return ResponseDto.failure("退款失败:" + backOrder.getErrorReason());
}
if (DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY.equals(orderVo.getPaymentType())) {
refundHelper.alipayCallBack(orderVo, backOrder.getBackCode());
}
}
return ResponseDto.success();
} finally {
redisUtils.redisUtil.uLock(lockKey);
}
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment