diff --git a/backend/services/trading-service/src/application/services/c2c.service.ts b/backend/services/trading-service/src/application/services/c2c.service.ts index 9b6ae264..99b5f3f0 100644 --- a/backend/services/trading-service/src/application/services/c2c.service.ts +++ b/backend/services/trading-service/src/application/services/c2c.service.ts @@ -723,6 +723,7 @@ export class C2cService { } const quantityDecimal = new Decimal(freshOrder.quantity); + const shouldRestore = freshOrder.status === C2C_ORDER_STATUS.MATCHED || freshOrder.status === C2C_ORDER_STATUS.PAID; // 解冻卖方的积分值(C2C交易的是积分值,买方不冻结) if (freshOrder.status === C2C_ORDER_STATUS.PENDING) { @@ -736,8 +737,12 @@ export class C2cService { if (freshOrder.takerAccountSequence) { await this.tradingAccountRepository.unfreezeCash(freshOrder.takerAccountSequence, quantityDecimal); } + } else if (shouldRestore) { + // MATCHED/PAID SELL订单将恢复为新PENDING:保持maker的冻结不变 + // 冻结资金直接转给新PENDING订单,避免 解冻→重新冻结 的竞态窗口 + this.logger.log(`SELL订单 ${freshOrder.orderNo} 将恢复,保持冻结 ${quantityDecimal}`); } else { - // MATCHED/PAID: SELL订单解冻maker(卖方)的积分值 + // MATCHED/PAID SELL订单不恢复时解冻maker(卖方)的积分值 await this.tradingAccountRepository.unfreezeCash(freshOrder.makerAccountSequence, quantityDecimal); } @@ -749,7 +754,7 @@ export class C2cService { this.logger.log(`C2C订单已过期: ${freshOrder.orderNo}, 原状态: ${freshOrder.status}`); // MATCHED/PAID 订单过期后,将数量退还为新的 PENDING 订单(恢复到市场) - if (freshOrder.status === C2C_ORDER_STATUS.MATCHED || freshOrder.status === C2C_ORDER_STATUS.PAID) { + if (shouldRestore) { await this.restoreExpiredOrder(freshOrder, quantityDecimal); } } finally { @@ -760,26 +765,17 @@ export class C2cService { /** * 过期订单恢复:为 maker 重新创建一个 PENDING 订单 * 将过期的成交数量退还到市场,让其他用户可以继续接单 + * + * 注意:SELL 订单的冻结在 expireOrder 中已保持不变(未解冻), + * 直接转给新的 PENDING 订单,避免 解冻→重新冻结 的竞态条件。 */ private async restoreExpiredOrder(expiredOrder: C2cOrderEntity, quantityDecimal: Decimal): Promise { try { const priceDecimal = new Decimal(expiredOrder.price); const totalAmountDecimal = priceDecimal.mul(quantityDecimal); - // SELL 订单需要重新冻结 maker 的积分值 - if (expiredOrder.type === C2C_ORDER_TYPE.SELL) { - const makerAccount = await this.tradingAccountRepository.findByAccountSequence(expiredOrder.makerAccountSequence); - if (!makerAccount) { - this.logger.warn(`过期恢复: maker ${expiredOrder.makerAccountSequence} 账户不存在,跳过恢复`); - return; - } - const quantityMoney = new Money(quantityDecimal); - if (makerAccount.availableCash.isLessThan(quantityMoney)) { - this.logger.warn(`过期恢复: maker ${expiredOrder.makerAccountSequence} 可用余额不足 (需要 ${quantityDecimal}, 可用 ${makerAccount.availableCash}),跳过恢复`); - return; - } - await this.tradingAccountRepository.freezeCash(expiredOrder.makerAccountSequence, quantityDecimal); - } + // SELL 订单:冻结已在 expireOrder 中保留,无需重新冻结 + // BUY 订单:无冻结,直接创建新 PENDING 订单 // 创建新的 PENDING 订单 const restoreOrderNo = this.generateOrderNo(); @@ -797,14 +793,22 @@ export class C2cService { paymentAccount: expiredOrder.type === C2C_ORDER_TYPE.SELL ? (expiredOrder.paymentAccount ?? undefined) : undefined, paymentQrCode: expiredOrder.type === C2C_ORDER_TYPE.SELL ? (expiredOrder.paymentQrCode ?? undefined) : undefined, paymentRealName: expiredOrder.type === C2C_ORDER_TYPE.SELL ? (expiredOrder.paymentRealName ?? undefined) : undefined, - sellerKavaAddress: expiredOrder.sellerKavaAddress, + sellerKavaAddress: expiredOrder.type === C2C_ORDER_TYPE.SELL ? (expiredOrder.sellerKavaAddress ?? undefined) : undefined, remark: expiredOrder.remark ?? undefined, }); this.logger.log(`过期恢复: 订单 ${expiredOrder.orderNo} 已恢复为新 PENDING 订单 ${restoreOrderNo}, 数量: ${quantityDecimal}`); } catch (error) { this.logger.error(`过期恢复失败: ${expiredOrder.orderNo}`, error); - // 恢复失败不影响过期流程,只记录日志 + // SELL 订单恢复失败时,需要解冻之前保留的冻结(否则 maker 资金被永久冻结) + if (expiredOrder.type === C2C_ORDER_TYPE.SELL) { + try { + await this.tradingAccountRepository.unfreezeCash(expiredOrder.makerAccountSequence, quantityDecimal); + this.logger.warn(`过期恢复失败,已解冻 ${quantityDecimal} for ${expiredOrder.makerAccountSequence}`); + } catch (unfreezeError) { + this.logger.error(`过期恢复失败后解冻也失败: ${expiredOrder.orderNo}`, unfreezeError); + } + } } } }