feat(pre-planting): 预种购买后自动触发待领取→可结算转换
购买预种份额后,planting-service 调用 wallet-service 新增的内部 API, 将用户标记为 hasPlanted=true 并结算所有 PENDING 奖励为 SETTLED。 纯新增代码,不修改任何现有方法逻辑,两步均幂等,失败不阻塞购买流程。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e9b9896317
commit
722c124cc9
|
|
@ -406,6 +406,50 @@ export class WalletServiceClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [2026-03-01] 预种购买后触发待领取→可结算转换
|
||||
*
|
||||
* 调用 wallet-service 内部 API,将用户标记为 hasPlanted=true,
|
||||
* 并结算所有 PENDING 奖励为 SETTLED。
|
||||
* 幂等:重复调用安全,不会重复结算。
|
||||
*/
|
||||
async settleAfterPrePlanting(accountSequence: string): Promise<{
|
||||
markedAsPlanted: boolean;
|
||||
settledCount: number;
|
||||
totalUsdt: number;
|
||||
totalHashpower: number;
|
||||
}> {
|
||||
try {
|
||||
return await this.withRetry(
|
||||
`settleAfterPrePlanting(${accountSequence})`,
|
||||
async () => {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.post<{
|
||||
markedAsPlanted: boolean;
|
||||
settledCount: number;
|
||||
totalUsdt: number;
|
||||
totalHashpower: number;
|
||||
}>(
|
||||
`${this.baseUrl}/api/v1/wallets/settle-after-pre-planting`,
|
||||
{ accountSequence },
|
||||
),
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to settle after pre-planting for ${accountSequence}`,
|
||||
error,
|
||||
);
|
||||
if (this.configService.get('NODE_ENV') === 'development') {
|
||||
this.logger.warn('Development mode: simulating successful settle-after-pre-planting');
|
||||
return { markedAsPlanted: false, settledCount: 0, totalUsdt: 0, totalHashpower: 0 };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入底池
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -182,6 +182,25 @@ export class PrePlantingApplicationService {
|
|||
provinceCode,
|
||||
cityCode,
|
||||
);
|
||||
|
||||
// [2026-03-01] 预种购买后触发待领取→可结算转换
|
||||
// 将用户标记为 hasPlanted=true,并结算所有 PENDING 奖励
|
||||
// 幂等:重复调用安全。失败不阻塞购买流程(奖励仍在 PENDING,不会丢失)
|
||||
try {
|
||||
const settleResult = await this.walletClient.settleAfterPrePlanting(accountSequence);
|
||||
this.logger.log(
|
||||
`[PRE-PLANTING] Settle after purchase: order=${orderNo}, ` +
|
||||
`markedAsPlanted=${settleResult.markedAsPlanted}, ` +
|
||||
`settled=${settleResult.settledCount} rewards, usdt=${settleResult.totalUsdt}`,
|
||||
);
|
||||
} catch (settleError) {
|
||||
// 结算失败不影响购买结果,奖励仍在 PENDING 状态,用户可后续手动领取
|
||||
this.logger.error(
|
||||
`[PRE-PLANTING] Failed to settle after purchase for order ${orderNo}, ` +
|
||||
`accountSequence=${accountSequence}. Rewards remain in PENDING status.`,
|
||||
settleError,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// 事务失败,解冻余额
|
||||
this.logger.error(
|
||||
|
|
|
|||
|
|
@ -137,6 +137,30 @@ export class InternalWalletController {
|
|||
return { success };
|
||||
}
|
||||
|
||||
/**
|
||||
* [2026-03-01] 预种购买后触发待领取→可结算转换(内部API)
|
||||
*
|
||||
* planting-service 在用户购买预种份额后调用此端点:
|
||||
* 1. markUserAsPlanted(accountSequence) — 设置 hasPlanted=true
|
||||
* 2. settleUserPendingRewards(accountSequence) — 结算所有 PENDING 奖励
|
||||
* 两步均幂等,重复调用安全。
|
||||
*/
|
||||
@Post('settle-after-pre-planting')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '预种购买后结算待领取奖励(内部API) - 幂等' })
|
||||
@ApiResponse({ status: 200, description: '结算结果' })
|
||||
async settleAfterPrePlanting(
|
||||
@Body() dto: { accountSequence: string },
|
||||
) {
|
||||
this.logger.log(`========== settle-after-pre-planting 请求 ==========`);
|
||||
this.logger.log(`accountSequence: ${dto.accountSequence}`);
|
||||
|
||||
const result = await this.walletService.settleAfterPrePlanting(dto.accountSequence);
|
||||
|
||||
this.logger.log(`预种结算结果: ${JSON.stringify(result)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('allocate-funds')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '资金分配(内部API)' })
|
||||
|
|
|
|||
|
|
@ -2490,6 +2490,44 @@ export class WalletApplicationService {
|
|||
this.logger.log(`[markUserAsPlanted] User ${accountSequence} marked as planted`);
|
||||
}
|
||||
|
||||
/**
|
||||
* [2026-03-01] 预种购买后触发待领取→可结算转换
|
||||
*
|
||||
* 用户购买预种份额后,planting-service 调用此方法,
|
||||
* 将该用户标记为 hasPlanted=true,并结算所有 PENDING 奖励为 SETTLED。
|
||||
* 内部复用已有幂等方法:markUserAsPlanted + settleUserPendingRewards。
|
||||
*/
|
||||
async settleAfterPrePlanting(accountSequence: string): Promise<{
|
||||
markedAsPlanted: boolean;
|
||||
settledCount: number;
|
||||
totalUsdt: number;
|
||||
totalHashpower: number;
|
||||
}> {
|
||||
this.logger.log(`[settleAfterPrePlanting] Processing for ${accountSequence}`);
|
||||
|
||||
// Step 1: 标记为已认种(幂等)
|
||||
const walletBefore = await this.walletRepo.findByAccountSequence(accountSequence);
|
||||
const wasAlreadyPlanted = walletBefore?.hasPlanted ?? false;
|
||||
|
||||
await this.markUserAsPlanted(accountSequence);
|
||||
|
||||
// Step 2: 结算所有 PENDING 奖励(幂等)
|
||||
const settleResult = await this.settleUserPendingRewards(accountSequence);
|
||||
|
||||
this.logger.log(
|
||||
`[settleAfterPrePlanting] Done for ${accountSequence}: ` +
|
||||
`wasAlreadyPlanted=${wasAlreadyPlanted}, settled=${settleResult.settledCount} rewards, ` +
|
||||
`usdt=${settleResult.totalUsdt}, hashpower=${settleResult.totalHashpower}`,
|
||||
);
|
||||
|
||||
return {
|
||||
markedAsPlanted: !wasAlreadyPlanted,
|
||||
settledCount: settleResult.settledCount,
|
||||
totalUsdt: settleResult.totalUsdt,
|
||||
totalHashpower: settleResult.totalHashpower,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过期奖励
|
||||
* 定时任务调用,将已过期的 PENDING 奖励标记为 EXPIRED
|
||||
|
|
|
|||
Loading…
Reference in New Issue