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

Commit 7f74125e authored by wangyifan's avatar wangyifan

优化用户退款接口

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