package com.talentcard.server.business.order;
|
|
import java.util.List;
|
import java.util.Map;
|
|
import com.talentcard.server.common.BusinessException;
|
import com.talentcard.server.common.ErrorCode;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Transactional;
|
|
/**
|
* 订单资金边界。
|
*
|
* <p>一个订单的核销、退款和提现锁定都会先锁定 {@code benefit_order} 的同一行,因此它们按订单串行化:
|
* 不会出现「退了款又核销」「核销后被退款」「已经进了提现记录又撤销核销」。
|
*
|
* <p>{@code refund_status} 由本服务的退款三个方法维护。出款已改由斗拱按结算周期自动完成,
|
* 平台不再生成提现记录,因此订单上不再有「已进入提现记录」这一状态。
|
* 核销、退款、提现锁定与解锁的业务规则都收敛在这里,调用方只负责在拿到 {@link LockedOrder} 后调用
|
* 对应断言。
|
*
|
* <p>{@link #lock(long)} 要求已经存在数据库事务:行锁只在事务内有效,脱离事务调用会直接失败,
|
* 而不是静默地失去串行化能力。其它方法都是单条条件更新,本身就是原子的。
|
*/
|
@Service
|
public class OrderFundBoundaryService {
|
|
/** 订单退款状态:没有生效中的退款。 */
|
public static final long REFUND_NONE = 0;
|
/** 订单退款状态:退款已受理、结果未定。 */
|
public static final long REFUND_IN_PROGRESS = 1;
|
/** 订单退款状态:退款成功(部分退款也算整单退出结算)。 */
|
public static final long REFUNDED = 2;
|
|
private final JdbcTemplate jdbc;
|
|
public OrderFundBoundaryService(JdbcTemplate jdbc) {
|
this.jdbc = jdbc;
|
}
|
|
/**
|
* 锁定订单行并返回它的资金状态。拿到的状态在整个事务内不会被子资金状态变更改动,
|
* 因此所有判断都必须基于本方法的返回值。
|
*/
|
@Transactional(propagation = Propagation.MANDATORY)
|
public LockedOrder lock(long orderId) {
|
List<Map<String, Object>> rows = jdbc.queryForList("""
|
select id, order_status, verify_status, refund_status
|
from benefit_order
|
where id = ? and deleted = 0
|
limit 1 for update
|
""", orderId);
|
if (rows.isEmpty()) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "订单不存在");
|
}
|
return LockedOrder.fromRow(rows.get(0));
|
}
|
|
/** 退款受理成功:本笔退款结清前订单不可核销,也不参与提现汇总。 */
|
public void markRefundInProgress(long orderId) {
|
int updated = jdbc.update("""
|
update benefit_order set refund_status = 1
|
where id = ? and deleted = 0 and refund_status = 0
|
""", orderId);
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "订单已提交退款申请");
|
}
|
}
|
|
/** 退款成功:整单退出可核销与可提现范围,部分退款同样按整单处理。 */
|
public void markRefunded(long orderId) {
|
jdbc.update("update benefit_order set refund_status = 2 where id = ? and deleted = 0 and refund_status <> 2", orderId);
|
}
|
|
/** 退款失败:没有资金变动,订单回到未退款状态。已有成功退款的订单不会被退回。 */
|
public void markRefundFailed(long orderId) {
|
jdbc.update("""
|
update benefit_order o
|
set o.refund_status = 0
|
where o.id = ? and o.deleted = 0 and o.refund_status = 1
|
and not exists (select 1 from order_refund r
|
where r.order_id = o.id and r.deleted = 0 and r.refund_status = 2)
|
""", orderId);
|
}
|
|
/** 一条已被行锁固定的订单资金状态。 */
|
public record LockedOrder(long orderId, long orderStatus, long verifyStatus, long refundStatus) {
|
|
static LockedOrder fromRow(Map<String, Object> row) {
|
return new LockedOrder(
|
number(row.get("id")),
|
number(row.get("order_status")),
|
number(row.get("verify_status")),
|
number(row.get("refund_status")));
|
}
|
|
/** 未支付、已核销、已退款或退款中的订单都不能再核销。 */
|
public void requireVerifiable() {
|
if (orderStatus != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "订单未支付,不能核销");
|
}
|
if (verifyStatus == 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "订单已核销");
|
}
|
if (refundStatus != REFUND_NONE) {
|
throw new BusinessException(ErrorCode.CONFLICT, "订单已退款或退款中,不能核销");
|
}
|
}
|
|
/** 未支付、已退款或退款中的订单都不能再退款。 */
|
public void requireRefundable() {
|
if (orderStatus != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "订单当前状态不可退款");
|
}
|
if (refundStatus == REFUND_IN_PROGRESS) {
|
throw new BusinessException(ErrorCode.CONFLICT, "订单退款处理中,不能重复退款");
|
}
|
if (refundStatus == REFUNDED) {
|
throw new BusinessException(ErrorCode.CONFLICT, "订单已退款");
|
}
|
}
|
|
private static long number(Object value) {
|
return value instanceof Number number ? number.longValue() : 0L;
|
}
|
}
|
}
|