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; /** * 订单资金边界。 * *

一个订单的核销、退款和提现锁定都会先锁定 {@code benefit_order} 的同一行,因此它们按订单串行化: * 不会出现「退了款又核销」「核销后被退款」「已经进了提现记录又撤销核销」。 * *

{@code refund_status} 由本服务的退款三个方法维护。出款已改由斗拱按结算周期自动完成, * 平台不再生成提现记录,因此订单上不再有「已进入提现记录」这一状态。 * 核销、退款、提现锁定与解锁的业务规则都收敛在这里,调用方只负责在拿到 {@link LockedOrder} 后调用 * 对应断言。 * *

{@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> 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 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; } } }