feat: 跨服务使用 accountSequence 查询推荐链 + 系统账户动态创建
1. reward-service 使用 accountSequence 查询推荐链 - event-consumer.controller.ts: 优先使用 accountSequence 作为用户标识 - reward-calculation.service.ts: 使用 accountSequence 查询推荐关系 - referral-service.client.ts: 参数从 userId 改为 accountSequence 2. referral-service 支持 accountSequence 格式的推荐链查询 - referral.controller.ts: /chain/:identifier 同时支持 userId 和 accountSequence 3. wallet-service 系统账户动态创建 - wallet-application.service.ts: allocateToUserWallet 使用 getOrCreate - 支持省区域(9+code)和市区域(8+code)账户自动创建 - 新增 migration seed: 4个固定系统账户 (S0000000001-S0000000004) 4. planting-service 事件增强 - 事件中添加 accountSequence 字段用于跨服务关联 系统账户格式: - S0000000001: 总部社区 (基础费9U + 兜底权益) - S0000000002: 成本费账户 (400U) - S0000000003: 运营费账户 (300U) - S0000000004: RWAD底池账户 (800U) - 9+provinceCode: 省区域系统账户 (动态创建) - 8+cityCode: 市区域系统账户 (动态创建) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d20ff9e9b5
commit
ebbf2d971a
|
|
@ -267,7 +267,7 @@ export class PlantingApplicationService {
|
||||||
|
|
||||||
// 8. 添加 Outbox 事件(在同一事务中保存)
|
// 8. 添加 Outbox 事件(在同一事务中保存)
|
||||||
// 使用 Outbox Pattern 保证事件发布的可靠性
|
// 使用 Outbox Pattern 保证事件发布的可靠性
|
||||||
const outboxEvents = this.buildOutboxEvents(order, selection);
|
const outboxEvents = this.buildOutboxEvents(order, selection, accountSequence);
|
||||||
uow.addOutboxEvents(outboxEvents);
|
uow.addOutboxEvents(outboxEvents);
|
||||||
|
|
||||||
// 提交 Outbox 事件(在事务中保存到数据库)
|
// 提交 Outbox 事件(在事务中保存到数据库)
|
||||||
|
|
@ -478,6 +478,7 @@ export class PlantingApplicationService {
|
||||||
private buildOutboxEvents(
|
private buildOutboxEvents(
|
||||||
order: PlantingOrder,
|
order: PlantingOrder,
|
||||||
selection: { provinceCode: string; cityCode: string },
|
selection: { provinceCode: string; cityCode: string },
|
||||||
|
accountSequence?: string,
|
||||||
): OutboxEventData[] {
|
): OutboxEventData[] {
|
||||||
const events: OutboxEventData[] = [];
|
const events: OutboxEventData[] = [];
|
||||||
|
|
||||||
|
|
@ -488,11 +489,12 @@ export class PlantingApplicationService {
|
||||||
events.push({
|
events.push({
|
||||||
eventType: 'planting.planting.created',
|
eventType: 'planting.planting.created',
|
||||||
topic: 'planting.planting.created',
|
topic: 'planting.planting.created',
|
||||||
key: order.userId.toString(),
|
key: accountSequence || order.userId.toString(),
|
||||||
payload: {
|
payload: {
|
||||||
eventName: 'planting.created',
|
eventName: 'planting.created',
|
||||||
data: {
|
data: {
|
||||||
userId: order.userId.toString(),
|
userId: order.userId.toString(),
|
||||||
|
accountSequence: accountSequence || order.userId.toString(), // 添加 accountSequence 用于跨服务关联
|
||||||
treeCount: order.treeCount.value,
|
treeCount: order.treeCount.value,
|
||||||
provinceCode: selection.provinceCode,
|
provinceCode: selection.provinceCode,
|
||||||
cityCode: selection.cityCode,
|
cityCode: selection.cityCode,
|
||||||
|
|
|
||||||
|
|
@ -97,13 +97,19 @@ export class ReferralController {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly logger = new Logger(ReferralController.name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户的推荐链(内部API,供 reward-service 调用)
|
* 获取用户的推荐链(内部API,供 reward-service 调用)
|
||||||
* 返回直接推荐人及其认种状态
|
* 返回直接推荐人及其认种状态
|
||||||
|
*
|
||||||
|
* @param accountSequence 支持两种格式:
|
||||||
|
* - accountSequence 格式: D25121300006 或 25121300006 (去掉D前缀)
|
||||||
|
* - 纯数字 userId 格式: 2 (旧格式,不推荐)
|
||||||
*/
|
*/
|
||||||
@Get('chain/:userId')
|
@Get('chain/:accountSequence')
|
||||||
@ApiOperation({ summary: '获取用户推荐链(内部API)' })
|
@ApiOperation({ summary: '获取用户推荐链(内部API)' })
|
||||||
@ApiParam({ name: 'userId', description: '用户ID' })
|
@ApiParam({ name: 'accountSequence', description: '用户标识 (accountSequence 或 userId)' })
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: 200,
|
status: 200,
|
||||||
description: '推荐链数据',
|
description: '推荐链数据',
|
||||||
|
|
@ -116,6 +122,7 @@ export class ReferralController {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
userId: { type: 'string' },
|
userId: { type: 'string' },
|
||||||
|
accountSequence: { type: 'string' },
|
||||||
hasPlanted: { type: 'boolean' },
|
hasPlanted: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -124,13 +131,33 @@ export class ReferralController {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
async getReferralChainForReward(
|
async getReferralChainForReward(
|
||||||
@Param('userId') userId: string,
|
@Param('accountSequence') accountSequence: string,
|
||||||
): Promise<{ ancestors: Array<{ userId: string; hasPlanted: boolean }> }> {
|
): Promise<{ ancestors: Array<{ userId: string; accountSequence: string; hasPlanted: boolean }> }> {
|
||||||
const userIdBigInt = BigInt(userId);
|
this.logger.log(`[INTERNAL] getReferralChain: accountSequence=${accountSequence}`);
|
||||||
|
|
||||||
|
let relationship;
|
||||||
|
|
||||||
|
// 判断传入的是 accountSequence 还是 userId
|
||||||
|
// accountSequence 格式: D25121300006 或 25121300006 (11位数字或D+11位数字)
|
||||||
|
if (accountSequence.startsWith('D') || accountSequence.length >= 11) {
|
||||||
|
// 使用 accountSequence 查询
|
||||||
|
const normalizedSequence = accountSequence.startsWith('D') ? accountSequence : `D${accountSequence}`;
|
||||||
|
this.logger.log(`Using accountSequence query: ${normalizedSequence}`);
|
||||||
|
relationship = await this.referralRepo.findByAccountSequence(normalizedSequence);
|
||||||
|
} else {
|
||||||
|
// 尝试作为 userId 查询 (向后兼容)
|
||||||
|
this.logger.log(`Using userId query: ${accountSequence}`);
|
||||||
|
try {
|
||||||
|
const userIdBigInt = BigInt(accountSequence);
|
||||||
|
relationship = await this.referralRepo.findByUserId(userIdBigInt);
|
||||||
|
} catch {
|
||||||
|
this.logger.warn(`Invalid userId format: ${accountSequence}`);
|
||||||
|
return { ancestors: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取用户的推荐关系
|
|
||||||
const relationship = await this.referralRepo.findByUserId(userIdBigInt);
|
|
||||||
if (!relationship || !relationship.referrerId) {
|
if (!relationship || !relationship.referrerId) {
|
||||||
|
this.logger.log(`No referral found for accountSequence: ${accountSequence}`);
|
||||||
return { ancestors: [] };
|
return { ancestors: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,10 +166,17 @@ export class ReferralController {
|
||||||
const referrerStats = await this.teamStatsRepo.findByUserId(referrerId);
|
const referrerStats = await this.teamStatsRepo.findByUserId(referrerId);
|
||||||
const hasPlanted = referrerStats ? referrerStats.personalPlantingCount > 0 : false;
|
const hasPlanted = referrerStats ? referrerStats.personalPlantingCount > 0 : false;
|
||||||
|
|
||||||
|
// 获取推荐人的 accountSequence
|
||||||
|
const referrerRelationship = await this.referralRepo.findByUserId(referrerId);
|
||||||
|
const referrerAccountSequence = referrerRelationship?.accountSequence || referrerId.toString();
|
||||||
|
|
||||||
|
this.logger.log(`Found referrer: userId=${referrerId}, accountSequence=${referrerAccountSequence}, hasPlanted=${hasPlanted}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ancestors: [
|
ancestors: [
|
||||||
{
|
{
|
||||||
userId: referrerId.toString(),
|
userId: referrerId.toString(),
|
||||||
|
accountSequence: referrerAccountSequence,
|
||||||
hasPlanted,
|
hasPlanted,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ interface PlantingCreatedEvent {
|
||||||
eventName: string;
|
eventName: string;
|
||||||
data: {
|
data: {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
accountSequence: string; // 跨服务关联标识(优先使用)
|
||||||
treeCount: number;
|
treeCount: number;
|
||||||
provinceCode: string;
|
provinceCode: string;
|
||||||
cityCode: string;
|
cityCode: string;
|
||||||
|
|
@ -138,12 +139,13 @@ export class PlantingCreatedHandler implements OnModuleInit {
|
||||||
try {
|
try {
|
||||||
await this.kafkaService.publish({
|
await this.kafkaService.publish({
|
||||||
topic: 'planting.order.paid',
|
topic: 'planting.order.paid',
|
||||||
key: event.data.userId,
|
key: event.data.accountSequence || event.data.userId,
|
||||||
value: {
|
value: {
|
||||||
eventName: 'planting.order.paid',
|
eventName: 'planting.order.paid',
|
||||||
data: {
|
data: {
|
||||||
orderId: event.data.orderId,
|
orderId: event.data.orderId,
|
||||||
userId: event.data.userId,
|
userId: event.data.userId,
|
||||||
|
accountSequence: event.data.accountSequence, // 跨服务关联标识
|
||||||
treeCount: event.data.treeCount,
|
treeCount: event.data.treeCount,
|
||||||
provinceCode: event.data.provinceCode,
|
provinceCode: event.data.provinceCode,
|
||||||
cityCode: event.data.cityCode,
|
cityCode: event.data.cityCode,
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,12 @@ export class RewardApplicationService {
|
||||||
async distributeRewards(params: {
|
async distributeRewards(params: {
|
||||||
sourceOrderNo: string; // 订单号是字符串格式如 PLT1765391584505Q0Q6QD
|
sourceOrderNo: string; // 订单号是字符串格式如 PLT1765391584505Q0Q6QD
|
||||||
sourceUserId: bigint;
|
sourceUserId: bigint;
|
||||||
|
sourceAccountSequence?: string; // 跨服务关联标识
|
||||||
treeCount: number;
|
treeCount: number;
|
||||||
provinceCode: string;
|
provinceCode: string;
|
||||||
cityCode: string;
|
cityCode: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
this.logger.log(`Distributing rewards for order ${params.sourceOrderNo}`);
|
this.logger.log(`Distributing rewards for order ${params.sourceOrderNo}, accountSequence=${params.sourceAccountSequence}`);
|
||||||
|
|
||||||
// 1. 计算所有奖励(包含考核逻辑,调用 authorization-service)
|
// 1. 计算所有奖励(包含考核逻辑,调用 authorization-service)
|
||||||
const rewards = await this.rewardCalculationService.calculateRewards(params);
|
const rewards = await this.rewardCalculationService.calculateRewards(params);
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import { Hashpower } from '../value-objects/hashpower.vo';
|
||||||
|
|
||||||
// 外部服务接口 (防腐层)
|
// 外部服务接口 (防腐层)
|
||||||
export interface IReferralServiceClient {
|
export interface IReferralServiceClient {
|
||||||
getReferralChain(userId: bigint): Promise<{
|
getReferralChain(accountSequence: string): Promise<{
|
||||||
ancestors: Array<{ userId: bigint; hasPlanted: boolean }>;
|
ancestors: Array<{ userId: bigint; accountSequence: string; hasPlanted: boolean }>;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,6 +77,7 @@ export class RewardCalculationService {
|
||||||
async calculateRewards(params: {
|
async calculateRewards(params: {
|
||||||
sourceOrderNo: string; // 订单号是字符串格式
|
sourceOrderNo: string; // 订单号是字符串格式
|
||||||
sourceUserId: bigint;
|
sourceUserId: bigint;
|
||||||
|
sourceAccountSequence?: string; // 跨服务关联标识
|
||||||
treeCount: number;
|
treeCount: number;
|
||||||
provinceCode: string;
|
provinceCode: string;
|
||||||
cityCode: string;
|
cityCode: string;
|
||||||
|
|
@ -132,6 +133,7 @@ export class RewardCalculationService {
|
||||||
const shareRewards = await this.calculateShareRights(
|
const shareRewards = await this.calculateShareRights(
|
||||||
params.sourceOrderNo,
|
params.sourceOrderNo,
|
||||||
params.sourceUserId,
|
params.sourceUserId,
|
||||||
|
params.sourceAccountSequence,
|
||||||
params.treeCount,
|
params.treeCount,
|
||||||
);
|
);
|
||||||
rewards.push(...shareRewards);
|
rewards.push(...shareRewards);
|
||||||
|
|
@ -317,9 +319,10 @@ export class RewardCalculationService {
|
||||||
private async calculateShareRights(
|
private async calculateShareRights(
|
||||||
sourceOrderNo: string,
|
sourceOrderNo: string,
|
||||||
sourceUserId: bigint,
|
sourceUserId: bigint,
|
||||||
|
sourceAccountSequence: string | undefined,
|
||||||
treeCount: number,
|
treeCount: number,
|
||||||
): Promise<RewardLedgerEntry[]> {
|
): Promise<RewardLedgerEntry[]> {
|
||||||
this.logger.debug(`[calculateShareRights] userId=${sourceUserId}, treeCount=${treeCount}`);
|
this.logger.debug(`[calculateShareRights] userId=${sourceUserId}, accountSequence=${sourceAccountSequence}, treeCount=${treeCount}`);
|
||||||
|
|
||||||
const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.SHARE_RIGHT];
|
const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.SHARE_RIGHT];
|
||||||
const usdtAmount = Money.USDT(usdt * treeCount);
|
const usdtAmount = Money.USDT(usdt * treeCount);
|
||||||
|
|
@ -331,8 +334,10 @@ export class RewardCalculationService {
|
||||||
sourceUserId,
|
sourceUserId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获取推荐链
|
// 使用 accountSequence 获取推荐链(优先),否则用 userId
|
||||||
const referralChain = await this.referralService.getReferralChain(sourceUserId);
|
const queryId = sourceAccountSequence || sourceUserId.toString();
|
||||||
|
this.logger.debug(`[calculateShareRights] querying referral chain with: ${queryId}`);
|
||||||
|
const referralChain = await this.referralService.getReferralChain(queryId);
|
||||||
|
|
||||||
if (referralChain.ancestors.length > 0) {
|
if (referralChain.ancestors.length > 0) {
|
||||||
const directReferrer = referralChain.ancestors[0];
|
const directReferrer = referralChain.ancestors[0];
|
||||||
|
|
@ -340,28 +345,28 @@ export class RewardCalculationService {
|
||||||
if (directReferrer.hasPlanted) {
|
if (directReferrer.hasPlanted) {
|
||||||
// 推荐人已认种,直接可结算
|
// 推荐人已认种,直接可结算
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[calculateShareRights] referrer=${directReferrer.userId} hasPlanted=true -> SETTLEABLE`,
|
`[calculateShareRights] referrer=${directReferrer.userId} (${directReferrer.accountSequence}) hasPlanted=true -> SETTLEABLE`,
|
||||||
);
|
);
|
||||||
return [RewardLedgerEntry.createSettleable({
|
return [RewardLedgerEntry.createSettleable({
|
||||||
userId: directReferrer.userId,
|
userId: directReferrer.userId,
|
||||||
accountSequence: directReferrer.userId.toString(),
|
accountSequence: directReferrer.accountSequence,
|
||||||
rewardSource,
|
rewardSource,
|
||||||
usdtAmount,
|
usdtAmount,
|
||||||
hashpowerAmount: hashpower,
|
hashpowerAmount: hashpower,
|
||||||
memo: `分享权益:来自用户${sourceUserId}的认种`,
|
memo: `分享权益:来自用户${sourceAccountSequence || sourceUserId}的认种`,
|
||||||
})];
|
})];
|
||||||
} else {
|
} else {
|
||||||
// 推荐人未认种,进入待领取(24h倒计时)
|
// 推荐人未认种,进入待领取(24h倒计时)
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[calculateShareRights] referrer=${directReferrer.userId} hasPlanted=false -> PENDING (24h)`,
|
`[calculateShareRights] referrer=${directReferrer.userId} (${directReferrer.accountSequence}) hasPlanted=false -> PENDING (24h)`,
|
||||||
);
|
);
|
||||||
return [RewardLedgerEntry.createPending({
|
return [RewardLedgerEntry.createPending({
|
||||||
userId: directReferrer.userId,
|
userId: directReferrer.userId,
|
||||||
accountSequence: directReferrer.userId.toString(),
|
accountSequence: directReferrer.accountSequence,
|
||||||
rewardSource,
|
rewardSource,
|
||||||
usdtAmount,
|
usdtAmount,
|
||||||
hashpowerAmount: hashpower,
|
hashpowerAmount: hashpower,
|
||||||
memo: `分享权益:来自用户${sourceUserId}的认种(24h内认种可领取)`,
|
memo: `分享权益:来自用户${sourceAccountSequence || sourceUserId}的认种(24h内认种可领取)`,
|
||||||
})];
|
})];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -11,27 +11,30 @@ export class ReferralServiceClient implements IReferralServiceClient {
|
||||||
this.baseUrl = this.configService.get<string>('REFERRAL_SERVICE_URL', 'http://localhost:3004');
|
this.baseUrl = this.configService.get<string>('REFERRAL_SERVICE_URL', 'http://localhost:3004');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getReferralChain(userId: bigint): Promise<{
|
async getReferralChain(accountSequence: string): Promise<{
|
||||||
ancestors: Array<{ userId: bigint; hasPlanted: boolean }>;
|
ancestors: Array<{ userId: bigint; accountSequence: string; hasPlanted: boolean }>;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${this.baseUrl}/api/v1/referral/chain/${userId}`);
|
this.logger.log(`Fetching referral chain for accountSequence: ${accountSequence}`);
|
||||||
|
const response = await fetch(`${this.baseUrl}/api/v1/referral/chain/${accountSequence}`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
this.logger.warn(`Failed to get referral chain for user ${userId}: ${response.status}`);
|
this.logger.warn(`Failed to get referral chain for accountSequence ${accountSequence}: ${response.status}`);
|
||||||
return { ancestors: [] };
|
return { ancestors: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
this.logger.log(`Referral chain response: ${JSON.stringify(data)}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ancestors: (data.ancestors || []).map((a: any) => ({
|
ancestors: (data.ancestors || []).map((a: any) => ({
|
||||||
userId: BigInt(a.userId),
|
userId: BigInt(a.userId),
|
||||||
|
accountSequence: a.accountSequence || a.userId,
|
||||||
hasPlanted: a.hasPlanted ?? false,
|
hasPlanted: a.hasPlanted ?? false,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Error fetching referral chain for user ${userId}:`, error);
|
this.logger.error(`Error fetching referral chain for accountSequence ${accountSequence}:`, error);
|
||||||
return { ancestors: [] };
|
return { ancestors: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ interface PlantingOrderPaidEvent {
|
||||||
data?: {
|
data?: {
|
||||||
orderId: string;
|
orderId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
accountSequence?: string; // 跨服务关联标识(优先使用)
|
||||||
treeCount: number;
|
treeCount: number;
|
||||||
provinceCode: string;
|
provinceCode: string;
|
||||||
cityCode: string;
|
cityCode: string;
|
||||||
|
|
@ -16,6 +17,7 @@ interface PlantingOrderPaidEvent {
|
||||||
// 兼容旧格式
|
// 兼容旧格式
|
||||||
orderId?: string;
|
orderId?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
|
accountSequence?: string;
|
||||||
treeCount?: number;
|
treeCount?: number;
|
||||||
provinceCode?: string;
|
provinceCode?: string;
|
||||||
cityCode?: string;
|
cityCode?: string;
|
||||||
|
|
@ -48,12 +50,17 @@ export class EventConsumerController {
|
||||||
const eventData = message.data || {
|
const eventData = message.data || {
|
||||||
orderId: message.orderId!,
|
orderId: message.orderId!,
|
||||||
userId: message.userId!,
|
userId: message.userId!,
|
||||||
|
accountSequence: message.accountSequence,
|
||||||
treeCount: message.treeCount!,
|
treeCount: message.treeCount!,
|
||||||
provinceCode: message.provinceCode!,
|
provinceCode: message.provinceCode!,
|
||||||
cityCode: message.cityCode!,
|
cityCode: message.cityCode!,
|
||||||
paidAt: message.paidAt!,
|
paidAt: message.paidAt!,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 优先使用 accountSequence,如果未提供则使用 userId
|
||||||
|
const userIdentifier = eventData.accountSequence || eventData.userId;
|
||||||
|
this.logger.log(`Processing event with userIdentifier: ${userIdentifier} (accountSequence: ${eventData.accountSequence}, userId: ${eventData.userId})`);
|
||||||
|
|
||||||
// B方案:提取 outbox 信息用于发送确认
|
// B方案:提取 outbox 信息用于发送确认
|
||||||
const outboxInfo = message._outbox;
|
const outboxInfo = message._outbox;
|
||||||
const eventId = outboxInfo?.aggregateId || eventData.orderId;
|
const eventId = outboxInfo?.aggregateId || eventData.orderId;
|
||||||
|
|
@ -63,6 +70,7 @@ export class EventConsumerController {
|
||||||
await this.rewardService.distributeRewards({
|
await this.rewardService.distributeRewards({
|
||||||
sourceOrderNo: eventData.orderId, // orderId 实际是 orderNo 字符串格式
|
sourceOrderNo: eventData.orderId, // orderId 实际是 orderNo 字符串格式
|
||||||
sourceUserId: BigInt(eventData.userId),
|
sourceUserId: BigInt(eventData.userId),
|
||||||
|
sourceAccountSequence: userIdentifier, // 优先使用 accountSequence
|
||||||
treeCount: eventData.treeCount,
|
treeCount: eventData.treeCount,
|
||||||
provinceCode: eventData.provinceCode,
|
provinceCode: eventData.provinceCode,
|
||||||
cityCode: eventData.cityCode,
|
cityCode: eventData.cityCode,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
-- Seed system accounts for reward distribution
|
||||||
|
-- These accounts receive fixed allocations from each planting order
|
||||||
|
-- Note: Province/City area accounts (9+provinceCode, 8+cityCode) are dynamically created
|
||||||
|
-- when users select their planting location
|
||||||
|
|
||||||
|
-- S0000000001: 总部社区 (Headquarters Community)
|
||||||
|
-- Receives: 基础费 9 USDT, 无推荐人的分享权益 500 USDT, 无授权的团队权益等
|
||||||
|
INSERT INTO "wallet_accounts" (
|
||||||
|
"account_sequence", "user_id", "status", "created_at", "updated_at"
|
||||||
|
) VALUES (
|
||||||
|
'S0000000001', 1, 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
) ON CONFLICT ("account_sequence") DO NOTHING;
|
||||||
|
|
||||||
|
-- S0000000002: 成本费账户 (Cost Fee Account)
|
||||||
|
-- Receives: 成本费 400 USDT per tree
|
||||||
|
INSERT INTO "wallet_accounts" (
|
||||||
|
"account_sequence", "user_id", "status", "created_at", "updated_at"
|
||||||
|
) VALUES (
|
||||||
|
'S0000000002', 2, 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
) ON CONFLICT ("account_sequence") DO NOTHING;
|
||||||
|
|
||||||
|
-- S0000000003: 运营费账户 (Operation Fee Account)
|
||||||
|
-- Receives: 运营费 300 USDT per tree
|
||||||
|
INSERT INTO "wallet_accounts" (
|
||||||
|
"account_sequence", "user_id", "status", "created_at", "updated_at"
|
||||||
|
) VALUES (
|
||||||
|
'S0000000003', 3, 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
) ON CONFLICT ("account_sequence") DO NOTHING;
|
||||||
|
|
||||||
|
-- S0000000004: RWAD底池账户 (RWAD Pool Injection Account)
|
||||||
|
-- Receives: 底池注入 800 USDT per tree
|
||||||
|
INSERT INTO "wallet_accounts" (
|
||||||
|
"account_sequence", "user_id", "status", "created_at", "updated_at"
|
||||||
|
) VALUES (
|
||||||
|
'S0000000004', 4, 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
) ON CONFLICT ("account_sequence") DO NOTHING;
|
||||||
|
|
@ -614,15 +614,39 @@ export class WalletApplicationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分配资金到用户钱包
|
* 分配资金到用户钱包
|
||||||
|
* 支持用户账户 (D+日期+序号) 和系统账户 (S+序号, 9+省代码, 8+市代码)
|
||||||
*/
|
*/
|
||||||
private async allocateToUserWallet(
|
private async allocateToUserWallet(
|
||||||
allocation: FundAllocationItem,
|
allocation: FundAllocationItem,
|
||||||
orderId: string,
|
orderId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// targetId 是 accountSequence (如 D2512120001),优先用它查找钱包
|
// targetId 是 accountSequence
|
||||||
const wallet = await this.walletRepo.findByAccountSequence(allocation.targetId);
|
// - 用户账户: D2512120001
|
||||||
|
// - 固定系统账户: S0000000001
|
||||||
|
// - 省区域账户: 9440000 (9 + provinceCode)
|
||||||
|
// - 市区域账户: 8440100 (8 + cityCode)
|
||||||
|
|
||||||
|
// 为系统账户生成 userId (用于数据库约束)
|
||||||
|
// 省区域: 使用 9 + provinceCode 作为 userId
|
||||||
|
// 市区域: 使用 8 + cityCode 作为 userId
|
||||||
|
let systemUserId = BigInt(0);
|
||||||
|
const targetId = allocation.targetId;
|
||||||
|
if (targetId.startsWith('9')) {
|
||||||
|
// 省区域账户: 9440000 -> userId = 9440000
|
||||||
|
systemUserId = BigInt(targetId);
|
||||||
|
} else if (targetId.startsWith('8')) {
|
||||||
|
// 市区域账户: 8440100 -> userId = 8440100
|
||||||
|
systemUserId = BigInt(targetId);
|
||||||
|
} else if (targetId.startsWith('S')) {
|
||||||
|
// 固定系统账户: S0000000001 -> userId 从后面的数字提取
|
||||||
|
const numPart = targetId.slice(1);
|
||||||
|
systemUserId = BigInt(parseInt(numPart, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 getOrCreate 自动创建不存在的账户(包括省/市区域账户)
|
||||||
|
const wallet = await this.walletRepo.getOrCreate(targetId, systemUserId);
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
this.logger.warn(`Wallet not found for accountSequence ${allocation.targetId}, skipping allocation`);
|
this.logger.warn(`Failed to get or create wallet for accountSequence ${allocation.targetId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue