# Reward Service 开发指导

## 项目概述

Reward Service 是 RWA 榴莲皇后平台的权益奖励微服务，负责管理奖励计算与分配、24小时倒计时管理、收益状态流转、结算处理等功能。

### 核心职责 ✅
- 权益规则定义（6种权益类型）
- 奖励计算与分配
- 24小时倒计时管理（待领取→可结算/过期）
- 收益状态流转（待领取→可结算→已结算/过期）
- 结算处理（选择币种，触发SWAP）
- 过期奖励转移至总部社区

### 不负责 ❌
- 推荐关系查询（Referral Context）
- 授权资格判定（Authorization Context）
- 钱包余额管理（Wallet Context）
- 区块链SWAP执行（Wallet Context）

---

## 技术栈

| 组件 | 技术选型 |
|------|----------|
| **框架** | NestJS 10.x |
| **数据库** | PostgreSQL + Prisma ORM |
| **架构** | DDD + Hexagonal Architecture (六边形架构) |
| **语言** | TypeScript 5.x |
| **消息队列** | Kafka (kafkajs) |
| **缓存** | Redis (ioredis) |
| **定时任务** | @nestjs/schedule |
| **API文档** | Swagger (@nestjs/swagger) |

---

## 核心业务规则

### 1. 奖励分配规则（每棵树 2199 USDT）

| 权益类型 | 金额 | 算力 | 分配目标 | 说明 |
|---------|------|------|---------|------|
| 成本账户 | 400 USDT | - | 系统指定账户 | 固定分配 |
| 运营账户 | 300 USDT | - | 系统指定账户 | 固定分配 |
| 总部社区 | 9 USDT | - | 总部社区账户 | 固定分配 |
| **分享权益** | 500 USDT | - | 推荐人账户 | 进入待结算或可结算 |
| 省区域权益 | 15 USDT | 1% | 系统省公司账户 | 固定分配 |
| **省团队权益** | 20 USDT | - | 最近授权省公司 | 无则进总部社区 |
| 市区域权益 | 35 USDT | 2% | 系统市公司账户 | 固定分配 |
| **市团队权益** | 40 USDT | - | 最近授权市公司 | 无则进总部社区 |
| **社区权益** | 80 USDT | - | 最近社区 | 无则进总部社区 |
| RWAD底池 | 800 USDT | - | 底池注入 | 5天批次注入 |

### 2. 24小时待领取规则

```
用户A推荐用户B → B认种 → A获得分享权益

情况1: A已认种
  → 奖励直接进入A的"可结算收益"

情况2: A未认种
  → 奖励进入A的"待领取收益"（24小时倒计时）
  → 24小时内A认种 → 待领取转为可结算
  → 24小时后A仍未认种 → 待领取过期，转入总部社区
```

### 3. 收益状态流转

```
[PENDING] 待领取
    │
    ├── 用户认种 ──→ [SETTLEABLE] 可结算
    │                      │
    │                      └── 用户结算 ──→ [SETTLED] 已结算
    │
    └── 24小时后未认种 ──→ [EXPIRED] 已过期 ──→ 转入总部社区
```

### 4. 结算规则

- 用户点击结算按钮
- 可结算收益通过 DST 公链 SWAP 兑换为选定币种（BNB/OG/USDT/DST）
- DST 可在 APP 交易功能中卖出转换为 USDT

---

## 架构设计

```
reward-service/
├── prisma/
│   ├── schema.prisma              # 数据库模型定义
│   └── migrations/                # 数据库迁移文件
│
├── src/
│   ├── api/                       # 🔵 Presentation Layer (表现层)
│   │   ├── controllers/
│   │   │   ├── health.controller.ts
│   │   │   ├── reward.controller.ts
│   │   │   └── settlement.controller.ts
│   │   ├── dto/
│   │   │   ├── request/
│   │   │   │   └── settle-rewards.dto.ts
│   │   │   └── response/
│   │   │       ├── reward-summary.dto.ts
│   │   │       ├── reward-entry.dto.ts
│   │   │       └── settlement-result.dto.ts
│   │   └── api.module.ts
│   │
│   ├── application/               # 🟢 Application Layer (应用层)
│   │   ├── commands/
│   │   │   ├── distribute-rewards/
│   │   │   │   ├── distribute-rewards.command.ts
│   │   │   │   └── distribute-rewards.handler.ts
│   │   │   ├── claim-pending-rewards/
│   │   │   │   ├── claim-pending-rewards.command.ts
│   │   │   │   └── claim-pending-rewards.handler.ts
│   │   │   ├── settle-rewards/
│   │   │   │   ├── settle-rewards.command.ts
│   │   │   │   └── settle-rewards.handler.ts
│   │   │   ├── expire-pending-rewards/
│   │   │   │   ├── expire-pending-rewards.command.ts
│   │   │   │   └── expire-pending-rewards.handler.ts
│   │   │   └── index.ts
│   │   ├── queries/
│   │   │   ├── get-reward-summary/
│   │   │   │   ├── get-reward-summary.query.ts
│   │   │   │   └── get-reward-summary.handler.ts
│   │   │   ├── get-reward-details/
│   │   │   │   ├── get-reward-details.query.ts
│   │   │   │   └── get-reward-details.handler.ts
│   │   │   └── index.ts
│   │   ├── services/
│   │   │   └── reward-application.service.ts
│   │   ├── schedulers/
│   │   │   └── reward-expiration.scheduler.ts
│   │   └── application.module.ts
│   │
│   ├── domain/                    # 🟡 Domain Layer (领域层)
│   │   ├── aggregates/
│   │   │   ├── reward-ledger-entry/
│   │   │   │   ├── reward-ledger-entry.aggregate.ts
│   │   │   │   ├── reward-ledger-entry.spec.ts
│   │   │   │   └── index.ts
│   │   │   └── reward-summary/
│   │   │       ├── reward-summary.aggregate.ts
│   │   │       ├── reward-summary.spec.ts
│   │   │       └── index.ts
│   │   ├── value-objects/
│   │   │   ├── entry-id.vo.ts
│   │   │   ├── reward-source.vo.ts
│   │   │   ├── right-type.enum.ts
│   │   │   ├── reward-status.enum.ts
│   │   │   ├── money.vo.ts
│   │   │   ├── hashpower.vo.ts
│   │   │   ├── pending-rewards.vo.ts
│   │   │   ├── settleable-rewards.vo.ts
│   │   │   ├── settled-rewards.vo.ts
│   │   │   ├── expired-rewards.vo.ts
│   │   │   └── index.ts
│   │   ├── entities/
│   │   │   └── right-definition.entity.ts
│   │   ├── events/
│   │   │   ├── domain-event.base.ts
│   │   │   ├── reward-created.event.ts
│   │   │   ├── reward-claimed.event.ts
│   │   │   ├── reward-settled.event.ts
│   │   │   ├── reward-expired.event.ts
│   │   │   └── index.ts
│   │   ├── repositories/
│   │   │   ├── reward-ledger-entry.repository.interface.ts
│   │   │   ├── reward-summary.repository.interface.ts
│   │   │   └── index.ts
│   │   ├── services/
│   │   │   ├── reward-calculation.service.ts
│   │   │   ├── reward-expiration.service.ts
│   │   │   └── index.ts
│   │   └── domain.module.ts
│   │
│   ├── infrastructure/            # 🔴 Infrastructure Layer (基础设施层)
│   │   ├── persistence/
│   │   │   ├── prisma/
│   │   │   │   └── prisma.service.ts
│   │   │   ├── mappers/
│   │   │   │   ├── reward-ledger-entry.mapper.ts
│   │   │   │   └── reward-summary.mapper.ts
│   │   │   └── repositories/
│   │   │       ├── reward-ledger-entry.repository.impl.ts
│   │   │       └── reward-summary.repository.impl.ts
│   │   ├── external/
│   │   │   ├── referral-service/
│   │   │   │   └── referral-service.client.ts
│   │   │   ├── authorization-service/
│   │   │   │   └── authorization-service.client.ts
│   │   │   └── wallet-service/
│   │   │       └── wallet-service.client.ts
│   │   ├── kafka/
│   │   │   ├── event-consumer.controller.ts
│   │   │   ├── event-publisher.service.ts
│   │   │   └── kafka.module.ts
│   │   ├── redis/
│   │   │   ├── redis.service.ts
│   │   │   └── redis.module.ts
│   │   └── infrastructure.module.ts
│   │
│   ├── shared/                    # 共享模块
│   │   ├── decorators/
│   │   ├── exceptions/
│   │   ├── filters/
│   │   ├── guards/
│   │   ├── interceptors/
│   │   └── strategies/
│   │
│   ├── config/
│   │   ├── app.config.ts
│   │   ├── database.config.ts
│   │   ├── jwt.config.ts
│   │   ├── redis.config.ts
│   │   ├── kafka.config.ts
│   │   └── index.ts
│   │
│   ├── app.module.ts
│   └── main.ts
│
├── test/
├── .env.example
├── .env.development
├── package.json
├── tsconfig.json
└── Dockerfile
```

---

## 第一阶段：项目初始化

### 1.1 创建 NestJS 项目

```bash
cd backend/services/reward-service
npx @nestjs/cli new . --skip-git --package-manager npm
```

### 1.2 安装依赖

```bash
# 核心依赖
npm install @nestjs/config @nestjs/swagger @nestjs/jwt @nestjs/passport @nestjs/microservices @nestjs/schedule
npm install @prisma/client class-validator class-transformer uuid ioredis kafkajs
npm install passport passport-jwt

# 开发依赖
npm install -D prisma @types/uuid @types/passport-jwt
npm install -D @nestjs/testing jest ts-jest @types/jest supertest @types/supertest
```

### 1.3 环境变量配置

创建 `.env.development`:
```env
# 应用配置
NODE_ENV=development
PORT=3005
APP_NAME=reward-service

# 数据库
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/rwadurian_reward?schema=public"

# JWT (与 identity-service 共享密钥)
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_ACCESS_EXPIRES_IN=2h

# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# Kafka
KAFKA_BROKERS=localhost:9092
KAFKA_GROUP_ID=reward-service-group
KAFKA_CLIENT_ID=reward-service

# 外部服务
IDENTITY_SERVICE_URL=http://localhost:3001
REFERRAL_SERVICE_URL=http://localhost:3004
AUTHORIZATION_SERVICE_URL=http://localhost:3006
WALLET_SERVICE_URL=http://localhost:3002
PLANTING_SERVICE_URL=http://localhost:3003

# 奖励过期检查间隔（毫秒）
REWARD_EXPIRATION_CHECK_INTERVAL=3600000

# 总部社区账户ID
HEADQUARTERS_COMMUNITY_USER_ID=1
```

---

## 第二阶段：数据库设计 (Prisma Schema)

### 2.1 创建 prisma/schema.prisma

```prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// ============================================
// 奖励流水表 (聚合根1 - 行为表, append-only)
// 记录每一笔奖励的创建、领取、结算、过期
// ============================================
model RewardLedgerEntry {
  id              BigInt   @id @default(autoincrement()) @map("entry_id")
  userId          BigInt   @map("user_id")  // 接收奖励的用户ID

  // === 奖励来源 ===
  sourceOrderId   BigInt   @map("source_order_id")   // 来源认种订单ID
  sourceUserId    BigInt   @map("source_user_id")    // 触发奖励的用户ID(认种者)
  rightType       String   @map("right_type") @db.VarChar(50)  // 权益类型

  // === 奖励金额 ===
  usdtAmount      Decimal  @map("usdt_amount") @db.Decimal(20, 8)
  hashpowerAmount Decimal  @default(0) @map("hashpower_amount") @db.Decimal(20, 8)

  // === 奖励状态 ===
  rewardStatus    String   @default("PENDING") @map("reward_status") @db.VarChar(20)

  // === 时间戳 ===
  createdAt       DateTime @default(now()) @map("created_at") @db.Timestamp(6)
  expireAt        DateTime? @map("expire_at")       // 过期时间(24h后)
  claimedAt       DateTime? @map("claimed_at")      // 领取时间(用户认种)
  settledAt       DateTime? @map("settled_at")      // 结算时间
  expiredAt       DateTime? @map("expired_at")      // 实际过期时间

  // === 备注 ===
  memo            String?  @map("memo") @db.VarChar(500)

  @@map("reward_ledger_entries")
  @@index([userId, rewardStatus], name: "idx_user_status")
  @@index([userId, createdAt(sort: Desc)], name: "idx_user_created")
  @@index([sourceOrderId], name: "idx_source_order")
  @@index([sourceUserId], name: "idx_source_user")
  @@index([rightType], name: "idx_right_type")
  @@index([rewardStatus], name: "idx_status")
  @@index([expireAt], name: "idx_expire")
  @@index([createdAt], name: "idx_created")
}

// ============================================
// 奖励汇总表 (聚合根2 - 状态表)
// 每个用户的收益汇总，从流水表聚合
// ============================================
model RewardSummary {
  id              BigInt   @id @default(autoincrement()) @map("summary_id")
  userId          BigInt   @unique @map("user_id")

  // === 待领取收益 (24h倒计时) ===
  pendingUsdt         Decimal  @default(0) @map("pending_usdt") @db.Decimal(20, 8)
  pendingHashpower    Decimal  @default(0) @map("pending_hashpower") @db.Decimal(20, 8)
  pendingExpireAt     DateTime? @map("pending_expire_at")  // 最早过期时间

  // === 可结算收益 ===
  settleableUsdt      Decimal  @default(0) @map("settleable_usdt") @db.Decimal(20, 8)
  settleableHashpower Decimal  @default(0) @map("settleable_hashpower") @db.Decimal(20, 8)

  // === 已结算收益 (累计) ===
  settledTotalUsdt      Decimal  @default(0) @map("settled_total_usdt") @db.Decimal(20, 8)
  settledTotalHashpower Decimal  @default(0) @map("settled_total_hashpower") @db.Decimal(20, 8)

  // === 已过期收益 (累计) ===
  expiredTotalUsdt      Decimal  @default(0) @map("expired_total_usdt") @db.Decimal(20, 8)
  expiredTotalHashpower Decimal  @default(0) @map("expired_total_hashpower") @db.Decimal(20, 8)

  // === 时间戳 ===
  lastUpdateAt    DateTime @default(now()) @updatedAt @map("last_update_at")
  createdAt       DateTime @default(now()) @map("created_at")

  @@map("reward_summaries")
  @@index([userId], name: "idx_summary_user")
  @@index([settleableUsdt(sort: Desc)], name: "idx_settleable_desc")
  @@index([pendingExpireAt], name: "idx_pending_expire")
}

// ============================================
// 权益定义表 (配置表)
// 定义每种权益的奖励规则
// ============================================
model RightDefinition {
  id              BigInt   @id @default(autoincrement()) @map("definition_id")
  rightType       String   @unique @map("right_type") @db.VarChar(50)

  // === 奖励规则 ===
  usdtPerTree       Decimal  @map("usdt_per_tree") @db.Decimal(20, 8)
  hashpowerPercent  Decimal  @default(0) @map("hashpower_percent") @db.Decimal(5, 2)

  // === 分配目标 ===
  payableTo         String   @map("payable_to") @db.VarChar(50)  // USER_ACCOUNT/SYSTEM_ACCOUNT/HEADQUARTERS

  // === 规则描述 ===
  ruleDescription   String?  @map("rule_description") @db.Text

  // === 启用状态 ===
  isEnabled         Boolean  @default(true) @map("is_enabled")

  createdAt         DateTime @default(now()) @map("created_at")
  updatedAt         DateTime @updatedAt @map("updated_at")

  @@map("right_definitions")
  @@index([rightType], name: "idx_def_right_type")
  @@index([isEnabled], name: "idx_def_enabled")
}

// ============================================
// 结算记录表 (行为表)
// 记录每次结算的详情
// ============================================
model SettlementRecord {
  id              BigInt   @id @default(autoincrement()) @map("settlement_id")
  userId          BigInt   @map("user_id")

  // === 结算金额 ===
  usdtAmount      Decimal  @map("usdt_amount") @db.Decimal(20, 8)
  hashpowerAmount Decimal  @map("hashpower_amount") @db.Decimal(20, 8)

  // === 结算币种 ===
  settleCurrency  String   @map("settle_currency") @db.VarChar(10)  // BNB/OG/USDT/DST
  receivedAmount  Decimal  @map("received_amount") @db.Decimal(20, 8)  // 实际收到的币种数量

  // === 交易信息 ===
  swapTxHash      String?  @map("swap_tx_hash") @db.VarChar(100)
  swapRate        Decimal? @map("swap_rate") @db.Decimal(20, 8)  // SWAP汇率

  // === 状态 ===
  status          String   @default("PENDING") @map("status") @db.VarChar(20)  // PENDING/SUCCESS/FAILED

  // === 时间戳 ===
  createdAt       DateTime @default(now()) @map("created_at")
  completedAt     DateTime? @map("completed_at")

  // === 关联的奖励条目ID列表 ===
  rewardEntryIds  BigInt[] @map("reward_entry_ids")

  @@map("settlement_records")
  @@index([userId], name: "idx_settlement_user")
  @@index([status], name: "idx_settlement_status")
  @@index([createdAt], name: "idx_settlement_created")
}

// ============================================
// 奖励事件表 (行为表, append-only)
// 用于事件溯源和审计
// ============================================
model RewardEvent {
  id            BigInt   @id @default(autoincrement()) @map("event_id")
  eventType     String   @map("event_type") @db.VarChar(50)

  // 聚合根信息
  aggregateId   String   @map("aggregate_id") @db.VarChar(100)
  aggregateType String   @map("aggregate_type") @db.VarChar(50)

  // 事件数据
  eventData     Json     @map("event_data")

  // 元数据
  userId        BigInt?  @map("user_id")
  occurredAt    DateTime @default(now()) @map("occurred_at") @db.Timestamp(6)
  version       Int      @default(1) @map("version")

  @@map("reward_events")
  @@index([aggregateType, aggregateId], name: "idx_reward_event_aggregate")
  @@index([eventType], name: "idx_reward_event_type")
  @@index([userId], name: "idx_reward_event_user")
  @@index([occurredAt], name: "idx_reward_event_occurred")
}
```

### 2.2 初始化数据库和种子数据

```bash
# 生成 Prisma Client
npx prisma generate

# 创建并运行迁移
npx prisma migrate dev --name init
```

创建 `prisma/seed.ts`:
```typescript
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function main() {
  // 初始化权益定义
  const rightDefinitions = [
    {
      rightType: 'SHARE_RIGHT',
      usdtPerTree: 500,
      hashpowerPercent: 0,
      payableTo: 'USER_ACCOUNT',
      ruleDescription: '分享权益：每棵树500 USDT，分配给推荐链',
    },
    {
      rightType: 'PROVINCE_AREA_RIGHT',
      usdtPerTree: 15,
      hashpowerPercent: 1,
      payableTo: 'SYSTEM_ACCOUNT',
      ruleDescription: '省区域权益：每棵树15 USDT + 1%算力，进系统省公司账户',
    },
    {
      rightType: 'PROVINCE_TEAM_RIGHT',
      usdtPerTree: 20,
      hashpowerPercent: 0,
      payableTo: 'USER_ACCOUNT',
      ruleDescription: '省团队权益：每棵树20 USDT，给最近的授权省公司',
    },
    {
      rightType: 'CITY_AREA_RIGHT',
      usdtPerTree: 35,
      hashpowerPercent: 2,
      payableTo: 'SYSTEM_ACCOUNT',
      ruleDescription: '市区域权益：每棵树35 USDT + 2%算力，进系统市公司账户',
    },
    {
      rightType: 'CITY_TEAM_RIGHT',
      usdtPerTree: 40,
      hashpowerPercent: 0,
      payableTo: 'USER_ACCOUNT',
      ruleDescription: '市团队权益：每棵树40 USDT，给最近的授权市公司',
    },
    {
      rightType: 'COMMUNITY_RIGHT',
      usdtPerTree: 80,
      hashpowerPercent: 0,
      payableTo: 'USER_ACCOUNT',
      ruleDescription: '社区权益：每棵树80 USDT，给最近的社区',
    },
  ];

  for (const def of rightDefinitions) {
    await prisma.rightDefinition.upsert({
      where: { rightType: def.rightType },
      update: def,
      create: def,
    });
  }

  console.log('Seed completed: Right definitions initialized');
}

main()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
```

---

## 第三阶段：领域层实现 (Domain Layer)

### 3.1 值对象 (Value Objects)

#### 3.1.1 src/domain/value-objects/right-type.enum.ts
```typescript
export enum RightType {
  SHARE_RIGHT = 'SHARE_RIGHT',                    // 分享权益 500U
  PROVINCE_AREA_RIGHT = 'PROVINCE_AREA_RIGHT',    // 省区域权益 15U + 1%算力
  PROVINCE_TEAM_RIGHT = 'PROVINCE_TEAM_RIGHT',    // 省团队权益 20U
  CITY_AREA_RIGHT = 'CITY_AREA_RIGHT',            // 市区域权益 35U + 2%算力
  CITY_TEAM_RIGHT = 'CITY_TEAM_RIGHT',            // 市团队权益 40U
  COMMUNITY_RIGHT = 'COMMUNITY_RIGHT',            // 社区权益 80U
}

// 权益金额配置
export const RIGHT_AMOUNTS: Record<RightType, { usdt: number; hashpowerPercent: number }> = {
  [RightType.SHARE_RIGHT]: { usdt: 500, hashpowerPercent: 0 },
  [RightType.PROVINCE_AREA_RIGHT]: { usdt: 15, hashpowerPercent: 1 },
  [RightType.PROVINCE_TEAM_RIGHT]: { usdt: 20, hashpowerPercent: 0 },
  [RightType.CITY_AREA_RIGHT]: { usdt: 35, hashpowerPercent: 2 },
  [RightType.CITY_TEAM_RIGHT]: { usdt: 40, hashpowerPercent: 0 },
  [RightType.COMMUNITY_RIGHT]: { usdt: 80, hashpowerPercent: 0 },
};
```

#### 3.1.2 src/domain/value-objects/reward-status.enum.ts
```typescript
export enum RewardStatus {
  PENDING = 'PENDING',          // 待领取(24h倒计时)
  SETTLEABLE = 'SETTLEABLE',    // 可结算
  SETTLED = 'SETTLED',          // 已结算
  EXPIRED = 'EXPIRED',          // 已过期(进总部社区)
}
```

#### 3.1.3 src/domain/value-objects/money.vo.ts
```typescript
export class Money {
  private constructor(
    public readonly amount: number,
    public readonly currency: string = 'USDT',
  ) {
    if (amount < 0) {
      throw new Error('金额不能为负数');
    }
  }

  static USDT(amount: number): Money {
    return new Money(amount, 'USDT');
  }

  static zero(): Money {
    return new Money(0, 'USDT');
  }

  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('货币类型不匹配');
    }
    return new Money(this.amount + other.amount, this.currency);
  }

  subtract(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('货币类型不匹配');
    }
    return new Money(Math.max(0, this.amount - other.amount), this.currency);
  }

  multiply(factor: number): Money {
    return new Money(this.amount * factor, this.currency);
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }

  isZero(): boolean {
    return this.amount === 0;
  }

  isGreaterThan(other: Money): boolean {
    return this.amount > other.amount;
  }
}
```

#### 3.1.4 src/domain/value-objects/hashpower.vo.ts
```typescript
export class Hashpower {
  private constructor(public readonly value: number) {
    if (value < 0) {
      throw new Error('算力不能为负数');
    }
  }

  static create(value: number): Hashpower {
    return new Hashpower(value);
  }

  static zero(): Hashpower {
    return new Hashpower(0);
  }

  /**
   * 根据树数量和百分比计算算力
   * @param treeCount 树数量
   * @param percent 算力百分比 (1 = 1%)
   */
  static fromTreeCount(treeCount: number, percent: number): Hashpower {
    return new Hashpower(treeCount * percent);
  }

  add(other: Hashpower): Hashpower {
    return new Hashpower(this.value + other.value);
  }

  subtract(other: Hashpower): Hashpower {
    return new Hashpower(Math.max(0, this.value - other.value));
  }

  equals(other: Hashpower): boolean {
    return this.value === other.value;
  }

  isZero(): boolean {
    return this.value === 0;
  }
}
```

#### 3.1.5 src/domain/value-objects/reward-source.vo.ts
```typescript
import { RightType } from './right-type.enum';

export class RewardSource {
  private constructor(
    public readonly rightType: RightType,
    public readonly sourceOrderId: bigint,
    public readonly sourceUserId: bigint,
  ) {}

  static create(
    rightType: RightType,
    sourceOrderId: bigint,
    sourceUserId: bigint,
  ): RewardSource {
    return new RewardSource(rightType, sourceOrderId, sourceUserId);
  }

  equals(other: RewardSource): boolean {
    return (
      this.rightType === other.rightType &&
      this.sourceOrderId === other.sourceOrderId &&
      this.sourceUserId === other.sourceUserId
    );
  }
}
```

#### 3.1.6 src/domain/value-objects/index.ts
```typescript
export * from './right-type.enum';
export * from './reward-status.enum';
export * from './money.vo';
export * from './hashpower.vo';
export * from './reward-source.vo';
```

### 3.2 领域事件 (Domain Events)

#### 3.2.1 src/domain/events/domain-event.base.ts
```typescript
import { v4 as uuidv4 } from 'uuid';

export abstract class DomainEvent {
  public readonly eventId: string;
  public readonly occurredAt: Date;
  public readonly version: number;

  protected constructor(version: number = 1) {
    this.eventId = uuidv4();
    this.occurredAt = new Date();
    this.version = version;
  }

  abstract get eventType(): string;
  abstract get aggregateId(): string;
  abstract get aggregateType(): string;
  abstract toPayload(): Record<string, any>;
}
```

#### 3.2.2 src/domain/events/reward-created.event.ts
```typescript
import { DomainEvent } from './domain-event.base';
import { RightType } from '../value-objects/right-type.enum';
import { RewardStatus } from '../value-objects/reward-status.enum';

export interface RewardCreatedPayload {
  entryId: string;
  userId: string;
  sourceOrderId: string;
  sourceUserId: string;
  rightType: RightType;
  usdtAmount: number;
  hashpowerAmount: number;
  rewardStatus: RewardStatus;
  expireAt: Date | null;
}

export class RewardCreatedEvent extends DomainEvent {
  constructor(private readonly payload: RewardCreatedPayload) {
    super();
  }

  get eventType(): string {
    return 'RewardCreated';
  }

  get aggregateId(): string {
    return this.payload.entryId;
  }

  get aggregateType(): string {
    return 'RewardLedgerEntry';
  }

  toPayload(): RewardCreatedPayload {
    return { ...this.payload };
  }
}
```

#### 3.2.3 src/domain/events/reward-claimed.event.ts
```typescript
import { DomainEvent } from './domain-event.base';

export interface RewardClaimedPayload {
  entryId: string;
  userId: string;
  usdtAmount: number;
  hashpowerAmount: number;
}

export class RewardClaimedEvent extends DomainEvent {
  constructor(private readonly payload: RewardClaimedPayload) {
    super();
  }

  get eventType(): string {
    return 'RewardClaimed';
  }

  get aggregateId(): string {
    return this.payload.entryId;
  }

  get aggregateType(): string {
    return 'RewardLedgerEntry';
  }

  toPayload(): RewardClaimedPayload {
    return { ...this.payload };
  }
}
```

#### 3.2.4 src/domain/events/reward-expired.event.ts
```typescript
import { DomainEvent } from './domain-event.base';

export interface RewardExpiredPayload {
  entryId: string;
  userId: string;
  usdtAmount: number;
  hashpowerAmount: number;
  transferredTo: string;  // 转移到的目标账户 (总部社区)
}

export class RewardExpiredEvent extends DomainEvent {
  constructor(private readonly payload: RewardExpiredPayload) {
    super();
  }

  get eventType(): string {
    return 'RewardExpired';
  }

  get aggregateId(): string {
    return this.payload.entryId;
  }

  get aggregateType(): string {
    return 'RewardLedgerEntry';
  }

  toPayload(): RewardExpiredPayload {
    return { ...this.payload };
  }
}
```

#### 3.2.5 src/domain/events/reward-settled.event.ts
```typescript
import { DomainEvent } from './domain-event.base';

export interface RewardSettledPayload {
  entryId: string;
  userId: string;
  usdtAmount: number;
  hashpowerAmount: number;
  settleCurrency: string;
  receivedAmount: number;
}

export class RewardSettledEvent extends DomainEvent {
  constructor(private readonly payload: RewardSettledPayload) {
    super();
  }

  get eventType(): string {
    return 'RewardSettled';
  }

  get aggregateId(): string {
    return this.payload.entryId;
  }

  get aggregateType(): string {
    return 'RewardLedgerEntry';
  }

  toPayload(): RewardSettledPayload {
    return { ...this.payload };
  }
}
```

### 3.3 聚合根 (Aggregates)

#### 3.3.1 src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts
```typescript
import { DomainEvent } from '../../events/domain-event.base';
import { RewardCreatedEvent } from '../../events/reward-created.event';
import { RewardClaimedEvent } from '../../events/reward-claimed.event';
import { RewardExpiredEvent } from '../../events/reward-expired.event';
import { RewardSettledEvent } from '../../events/reward-settled.event';
import { RewardSource } from '../../value-objects/reward-source.vo';
import { RewardStatus } from '../../value-objects/reward-status.enum';
import { Money } from '../../value-objects/money.vo';
import { Hashpower } from '../../value-objects/hashpower.vo';

/**
 * 奖励流水聚合根
 *
 * 不变式:
 * 1. 待领取奖励必须在24小时内认种，否则过期
 * 2. 只有待领取状态才能领取(claim)
 * 3. 只有可结算状态才能结算(settle)
 * 4. 已结算/已过期的奖励状态不可变更
 */
export class RewardLedgerEntry {
  private _id: bigint | null = null;
  private readonly _userId: bigint;
  private readonly _rewardSource: RewardSource;
  private readonly _usdtAmount: Money;
  private readonly _hashpowerAmount: Hashpower;
  private _rewardStatus: RewardStatus;
  private readonly _createdAt: Date;
  private _expireAt: Date | null;
  private _claimedAt: Date | null;
  private _settledAt: Date | null;
  private _expiredAt: Date | null;
  private readonly _memo: string;

  private _domainEvents: DomainEvent[] = [];

  private constructor(
    userId: bigint,
    rewardSource: RewardSource,
    usdtAmount: Money,
    hashpowerAmount: Hashpower,
    rewardStatus: RewardStatus,
    createdAt: Date,
    expireAt: Date | null,
    memo: string,
  ) {
    this._userId = userId;
    this._rewardSource = rewardSource;
    this._usdtAmount = usdtAmount;
    this._hashpowerAmount = hashpowerAmount;
    this._rewardStatus = rewardStatus;
    this._createdAt = createdAt;
    this._expireAt = expireAt;
    this._claimedAt = null;
    this._settledAt = null;
    this._expiredAt = null;
    this._memo = memo;
  }

  // ============ Getters ============
  get id(): bigint | null { return this._id; }
  get userId(): bigint { return this._userId; }
  get rewardSource(): RewardSource { return this._rewardSource; }
  get usdtAmount(): Money { return this._usdtAmount; }
  get hashpowerAmount(): Hashpower { return this._hashpowerAmount; }
  get rewardStatus(): RewardStatus { return this._rewardStatus; }
  get createdAt(): Date { return this._createdAt; }
  get expireAt(): Date | null { return this._expireAt; }
  get claimedAt(): Date | null { return this._claimedAt; }
  get settledAt(): Date | null { return this._settledAt; }
  get expiredAt(): Date | null { return this._expiredAt; }
  get memo(): string { return this._memo; }
  get domainEvents(): DomainEvent[] { return [...this._domainEvents]; }

  get isPending(): boolean { return this._rewardStatus === RewardStatus.PENDING; }
  get isSettleable(): boolean { return this._rewardStatus === RewardStatus.SETTLEABLE; }
  get isSettled(): boolean { return this._rewardStatus === RewardStatus.SETTLED; }
  get isExpired(): boolean { return this._rewardStatus === RewardStatus.EXPIRED; }

  // ============ 工厂方法 ============

  /**
   * 创建待领取奖励 (24小时倒计时)
   * 用于推荐人未认种的情况
   */
  static createPending(params: {
    userId: bigint;
    rewardSource: RewardSource;
    usdtAmount: Money;
    hashpowerAmount: Hashpower;
    memo?: string;
  }): RewardLedgerEntry {
    const now = new Date();
    const expireAt = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24小时后

    const entry = new RewardLedgerEntry(
      params.userId,
      params.rewardSource,
      params.usdtAmount,
      params.hashpowerAmount,
      RewardStatus.PENDING,
      now,
      expireAt,
      params.memo || '',
    );

    entry._domainEvents.push(new RewardCreatedEvent({
      entryId: entry._id?.toString() || 'temp',
      userId: entry._userId.toString(),
      sourceOrderId: entry._rewardSource.sourceOrderId.toString(),
      sourceUserId: entry._rewardSource.sourceUserId.toString(),
      rightType: entry._rewardSource.rightType,
      usdtAmount: entry._usdtAmount.amount,
      hashpowerAmount: entry._hashpowerAmount.value,
      rewardStatus: entry._rewardStatus,
      expireAt: entry._expireAt,
    }));

    return entry;
  }

  /**
   * 创建直接可结算奖励 (无需24小时等待)
   * 用于推荐人已认种的情况
   */
  static createSettleable(params: {
    userId: bigint;
    rewardSource: RewardSource;
    usdtAmount: Money;
    hashpowerAmount: Hashpower;
    memo?: string;
  }): RewardLedgerEntry {
    const entry = new RewardLedgerEntry(
      params.userId,
      params.rewardSource,
      params.usdtAmount,
      params.hashpowerAmount,
      RewardStatus.SETTLEABLE,
      new Date(),
      null,
      params.memo || '',
    );

    entry._domainEvents.push(new RewardCreatedEvent({
      entryId: entry._id?.toString() || 'temp',
      userId: entry._userId.toString(),
      sourceOrderId: entry._rewardSource.sourceOrderId.toString(),
      sourceUserId: entry._rewardSource.sourceUserId.toString(),
      rightType: entry._rewardSource.rightType,
      usdtAmount: entry._usdtAmount.amount,
      hashpowerAmount: entry._hashpowerAmount.value,
      rewardStatus: entry._rewardStatus,
      expireAt: null,
    }));

    return entry;
  }

  // ============ 领域行为 ============

  /**
   * 领取奖励 (用户认种后，待领取 → 可结算)
   */
  claim(): void {
    if (this._rewardStatus !== RewardStatus.PENDING) {
      throw new Error('只有待领取状态才能领取');
    }

    if (this.isExpiredNow()) {
      throw new Error('奖励已过期，无法领取');
    }

    this._rewardStatus = RewardStatus.SETTLEABLE;
    this._claimedAt = new Date();
    this._expireAt = null;

    this._domainEvents.push(new RewardClaimedEvent({
      entryId: this._id?.toString() || '',
      userId: this._userId.toString(),
      usdtAmount: this._usdtAmount.amount,
      hashpowerAmount: this._hashpowerAmount.value,
    }));
  }

  /**
   * 过期 (24小时后未认种)
   */
  expire(): void {
    if (this._rewardStatus !== RewardStatus.PENDING) {
      throw new Error('只有待领取状态才能过期');
    }

    this._rewardStatus = RewardStatus.EXPIRED;
    this._expiredAt = new Date();

    this._domainEvents.push(new RewardExpiredEvent({
      entryId: this._id?.toString() || '',
      userId: this._userId.toString(),
      usdtAmount: this._usdtAmount.amount,
      hashpowerAmount: this._hashpowerAmount.value,
      transferredTo: 'HEADQUARTERS_COMMUNITY',
    }));
  }

  /**
   * 结算 (可结算 → 已结算)
   */
  settle(settleCurrency: string, receivedAmount: number): void {
    if (this._rewardStatus !== RewardStatus.SETTLEABLE) {
      throw new Error('只有可结算状态才能结算');
    }

    this._rewardStatus = RewardStatus.SETTLED;
    this._settledAt = new Date();

    this._domainEvents.push(new RewardSettledEvent({
      entryId: this._id?.toString() || '',
      userId: this._userId.toString(),
      usdtAmount: this._usdtAmount.amount,
      hashpowerAmount: this._hashpowerAmount.value,
      settleCurrency,
      receivedAmount,
    }));
  }

  /**
   * 检查是否已过期
   */
  isExpiredNow(): boolean {
    if (!this._expireAt) return false;
    return new Date() > this._expireAt;
  }

  /**
   * 获取剩余过期时间 (毫秒)
   */
  getRemainingTimeMs(): number {
    if (!this._expireAt) return 0;
    const remaining = this._expireAt.getTime() - Date.now();
    return Math.max(0, remaining);
  }

  setId(id: bigint): void {
    this._id = id;
  }

  clearDomainEvents(): void {
    this._domainEvents = [];
  }

  // ============ 重建 ============

  static reconstitute(data: {
    id: bigint;
    userId: bigint;
    rewardSource: RewardSource;
    usdtAmount: number;
    hashpowerAmount: number;
    rewardStatus: RewardStatus;
    createdAt: Date;
    expireAt: Date | null;
    claimedAt: Date | null;
    settledAt: Date | null;
    expiredAt: Date | null;
    memo: string;
  }): RewardLedgerEntry {
    const entry = new RewardLedgerEntry(
      data.userId,
      data.rewardSource,
      Money.USDT(data.usdtAmount),
      Hashpower.create(data.hashpowerAmount),
      data.rewardStatus,
      data.createdAt,
      data.expireAt,
      data.memo,
    );
    entry._id = data.id;
    entry._claimedAt = data.claimedAt;
    entry._settledAt = data.settledAt;
    entry._expiredAt = data.expiredAt;
    return entry;
  }
}
```

#### 3.3.2 src/domain/aggregates/reward-summary/reward-summary.aggregate.ts
```typescript
import { Money } from '../../value-objects/money.vo';
import { Hashpower } from '../../value-objects/hashpower.vo';

/**
 * 奖励汇总聚合根
 * 维护用户的各类收益汇总数据
 */
export class RewardSummary {
  private _id: bigint | null = null;
  private readonly _userId: bigint;

  // 待领取收益
  private _pendingUsdt: Money;
  private _pendingHashpower: Hashpower;
  private _pendingExpireAt: Date | null;

  // 可结算收益
  private _settleableUsdt: Money;
  private _settleableHashpower: Hashpower;

  // 已结算收益 (累计)
  private _settledTotalUsdt: Money;
  private _settledTotalHashpower: Hashpower;

  // 已过期收益 (累计)
  private _expiredTotalUsdt: Money;
  private _expiredTotalHashpower: Hashpower;

  private _lastUpdateAt: Date;
  private readonly _createdAt: Date;

  private constructor(userId: bigint) {
    this._userId = userId;
    this._pendingUsdt = Money.zero();
    this._pendingHashpower = Hashpower.zero();
    this._pendingExpireAt = null;
    this._settleableUsdt = Money.zero();
    this._settleableHashpower = Hashpower.zero();
    this._settledTotalUsdt = Money.zero();
    this._settledTotalHashpower = Hashpower.zero();
    this._expiredTotalUsdt = Money.zero();
    this._expiredTotalHashpower = Hashpower.zero();
    this._lastUpdateAt = new Date();
    this._createdAt = new Date();
  }

  // ============ Getters ============
  get id(): bigint | null { return this._id; }
  get userId(): bigint { return this._userId; }
  get pendingUsdt(): Money { return this._pendingUsdt; }
  get pendingHashpower(): Hashpower { return this._pendingHashpower; }
  get pendingExpireAt(): Date | null { return this._pendingExpireAt; }
  get settleableUsdt(): Money { return this._settleableUsdt; }
  get settleableHashpower(): Hashpower { return this._settleableHashpower; }
  get settledTotalUsdt(): Money { return this._settledTotalUsdt; }
  get settledTotalHashpower(): Hashpower { return this._settledTotalHashpower; }
  get expiredTotalUsdt(): Money { return this._expiredTotalUsdt; }
  get expiredTotalHashpower(): Hashpower { return this._expiredTotalHashpower; }
  get lastUpdateAt(): Date { return this._lastUpdateAt; }
  get createdAt(): Date { return this._createdAt; }

  // ============ 工厂方法 ============

  static create(userId: bigint): RewardSummary {
    return new RewardSummary(userId);
  }

  // ============ 领域行为 ============

  /**
   * 增加待领取收益
   */
  addPending(usdt: Money, hashpower: Hashpower, expireAt: Date): void {
    this._pendingUsdt = this._pendingUsdt.add(usdt);
    this._pendingHashpower = this._pendingHashpower.add(hashpower);
    // 更新为最早的过期时间
    if (!this._pendingExpireAt || expireAt < this._pendingExpireAt) {
      this._pendingExpireAt = expireAt;
    }
    this._lastUpdateAt = new Date();
  }

  /**
   * 待领取 → 可结算 (用户认种)
   */
  movePendingToSettleable(usdt: Money, hashpower: Hashpower): void {
    this._pendingUsdt = this._pendingUsdt.subtract(usdt);
    this._pendingHashpower = this._pendingHashpower.subtract(hashpower);
    this._settleableUsdt = this._settleableUsdt.add(usdt);
    this._settleableHashpower = this._settleableHashpower.add(hashpower);

    // 如果待领取清空了，清除过期时间
    if (this._pendingUsdt.isZero()) {
      this._pendingExpireAt = null;
    }
    this._lastUpdateAt = new Date();
  }

  /**
   * 待领取 → 已过期
   */
  movePendingToExpired(usdt: Money, hashpower: Hashpower): void {
    this._pendingUsdt = this._pendingUsdt.subtract(usdt);
    this._pendingHashpower = this._pendingHashpower.subtract(hashpower);
    this._expiredTotalUsdt = this._expiredTotalUsdt.add(usdt);
    this._expiredTotalHashpower = this._expiredTotalHashpower.add(hashpower);

    if (this._pendingUsdt.isZero()) {
      this._pendingExpireAt = null;
    }
    this._lastUpdateAt = new Date();
  }

  /**
   * 增加可结算收益 (直接可结算的奖励)
   */
  addSettleable(usdt: Money, hashpower: Hashpower): void {
    this._settleableUsdt = this._settleableUsdt.add(usdt);
    this._settleableHashpower = this._settleableHashpower.add(hashpower);
    this._lastUpdateAt = new Date();
  }

  /**
   * 可结算 → 已结算
   */
  settle(usdt: Money, hashpower: Hashpower): void {
    this._settleableUsdt = this._settleableUsdt.subtract(usdt);
    this._settleableHashpower = this._settleableHashpower.subtract(hashpower);
    this._settledTotalUsdt = this._settledTotalUsdt.add(usdt);
    this._settledTotalHashpower = this._settledTotalHashpower.add(hashpower);
    this._lastUpdateAt = new Date();
  }

  setId(id: bigint): void {
    this._id = id;
  }

  // ============ 重建 ============

  static reconstitute(data: {
    id: bigint;
    userId: bigint;
    pendingUsdt: number;
    pendingHashpower: number;
    pendingExpireAt: Date | null;
    settleableUsdt: number;
    settleableHashpower: number;
    settledTotalUsdt: number;
    settledTotalHashpower: number;
    expiredTotalUsdt: number;
    expiredTotalHashpower: number;
    lastUpdateAt: Date;
    createdAt: Date;
  }): RewardSummary {
    const summary = new RewardSummary(data.userId);
    summary._id = data.id;
    summary._pendingUsdt = Money.USDT(data.pendingUsdt);
    summary._pendingHashpower = Hashpower.create(data.pendingHashpower);
    summary._pendingExpireAt = data.pendingExpireAt;
    summary._settleableUsdt = Money.USDT(data.settleableUsdt);
    summary._settleableHashpower = Hashpower.create(data.settleableHashpower);
    summary._settledTotalUsdt = Money.USDT(data.settledTotalUsdt);
    summary._settledTotalHashpower = Hashpower.create(data.settledTotalHashpower);
    summary._expiredTotalUsdt = Money.USDT(data.expiredTotalUsdt);
    summary._expiredTotalHashpower = Hashpower.create(data.expiredTotalHashpower);
    return summary;
  }
}
```

### 3.4 仓储接口 (Repository Interfaces)

#### 3.4.1 src/domain/repositories/reward-ledger-entry.repository.interface.ts
```typescript
import { RewardLedgerEntry } from '../aggregates/reward-ledger-entry/reward-ledger-entry.aggregate';
import { RewardStatus } from '../value-objects/reward-status.enum';
import { RightType } from '../value-objects/right-type.enum';

export interface IRewardLedgerEntryRepository {
  save(entry: RewardLedgerEntry): Promise<void>;
  saveAll(entries: RewardLedgerEntry[]): Promise<void>;
  findById(entryId: bigint): Promise<RewardLedgerEntry | null>;
  findByUserId(
    userId: bigint,
    filters?: {
      status?: RewardStatus;
      rightType?: RightType;
      startDate?: Date;
      endDate?: Date;
    },
    pagination?: { page: number; pageSize: number },
  ): Promise<RewardLedgerEntry[]>;
  findPendingByUserId(userId: bigint): Promise<RewardLedgerEntry[]>;
  findSettleableByUserId(userId: bigint): Promise<RewardLedgerEntry[]>;
  findExpiredPending(beforeDate: Date): Promise<RewardLedgerEntry[]>;
  findBySourceOrderId(sourceOrderId: bigint): Promise<RewardLedgerEntry[]>;
  countByUserId(userId: bigint, status?: RewardStatus): Promise<number>;
}

export const REWARD_LEDGER_ENTRY_REPOSITORY = Symbol('IRewardLedgerEntryRepository');
```

#### 3.4.2 src/domain/repositories/reward-summary.repository.interface.ts
```typescript
import { RewardSummary } from '../aggregates/reward-summary/reward-summary.aggregate';

export interface IRewardSummaryRepository {
  save(summary: RewardSummary): Promise<void>;
  findByUserId(userId: bigint): Promise<RewardSummary | null>;
  getOrCreate(userId: bigint): Promise<RewardSummary>;
  findByUserIds(userIds: bigint[]): Promise<Map<string, RewardSummary>>;
  findTopSettleableUsers(limit: number): Promise<RewardSummary[]>;
}

export const REWARD_SUMMARY_REPOSITORY = Symbol('IRewardSummaryRepository');
```

### 3.5 领域服务 (Domain Services)

#### 3.5.1 src/domain/services/reward-calculation.service.ts
```typescript
import { Injectable, Inject } from '@nestjs/common';
import { RewardLedgerEntry } from '../aggregates/reward-ledger-entry/reward-ledger-entry.aggregate';
import { RewardSource } from '../value-objects/reward-source.vo';
import { RightType, RIGHT_AMOUNTS } from '../value-objects/right-type.enum';
import { Money } from '../value-objects/money.vo';
import { Hashpower } from '../value-objects/hashpower.vo';

// 外部服务接口 (防腐层)
export interface IReferralServiceClient {
  getReferralChain(userId: bigint): Promise<{
    ancestors: Array<{ userId: bigint; hasPlanted: boolean }>;
  }>;
}

export interface IAuthorizationServiceClient {
  findNearestAuthorizedProvince(userId: bigint, provinceCode: string): Promise<bigint | null>;
  findNearestAuthorizedCity(userId: bigint, cityCode: string): Promise<bigint | null>;
  findNearestCommunity(userId: bigint): Promise<bigint | null>;
}

export const REFERRAL_SERVICE_CLIENT = Symbol('IReferralServiceClient');
export const AUTHORIZATION_SERVICE_CLIENT = Symbol('IAuthorizationServiceClient');

// 总部社区账户ID
const HEADQUARTERS_COMMUNITY_USER_ID = BigInt(1);

@Injectable()
export class RewardCalculationService {
  constructor(
    @Inject(REFERRAL_SERVICE_CLIENT)
    private readonly referralService: IReferralServiceClient,
    @Inject(AUTHORIZATION_SERVICE_CLIENT)
    private readonly authorizationService: IAuthorizationServiceClient,
  ) {}

  /**
   * 计算认种订单产生的所有奖励
   */
  async calculateRewards(params: {
    sourceOrderId: bigint;
    sourceUserId: bigint;
    treeCount: number;
    provinceCode: string;
    cityCode: string;
  }): Promise<RewardLedgerEntry[]> {
    const rewards: RewardLedgerEntry[] = [];

    // 1. 分享权益 (500 USDT)
    const shareRewards = await this.calculateShareRights(
      params.sourceOrderId,
      params.sourceUserId,
      params.treeCount,
    );
    rewards.push(...shareRewards);

    // 2. 省团队权益 (20 USDT)
    const provinceTeamReward = await this.calculateProvinceTeamRight(
      params.sourceOrderId,
      params.sourceUserId,
      params.provinceCode,
      params.treeCount,
    );
    rewards.push(provinceTeamReward);

    // 3. 省区域权益 (15 USDT + 1%算力)
    const provinceAreaReward = this.calculateProvinceAreaRight(
      params.sourceOrderId,
      params.sourceUserId,
      params.provinceCode,
      params.treeCount,
    );
    rewards.push(provinceAreaReward);

    // 4. 市团队权益 (40 USDT)
    const cityTeamReward = await this.calculateCityTeamRight(
      params.sourceOrderId,
      params.sourceUserId,
      params.cityCode,
      params.treeCount,
    );
    rewards.push(cityTeamReward);

    // 5. 市区域权益 (35 USDT + 2%算力)
    const cityAreaReward = this.calculateCityAreaRight(
      params.sourceOrderId,
      params.sourceUserId,
      params.cityCode,
      params.treeCount,
    );
    rewards.push(cityAreaReward);

    // 6. 社区权益 (80 USDT)
    const communityReward = await this.calculateCommunityRight(
      params.sourceOrderId,
      params.sourceUserId,
      params.treeCount,
    );
    rewards.push(communityReward);

    return rewards;
  }

  /**
   * 计算分享权益 (500 USDT)
   */
  private async calculateShareRights(
    sourceOrderId: bigint,
    sourceUserId: bigint,
    treeCount: number,
  ): Promise<RewardLedgerEntry[]> {
    const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.SHARE_RIGHT];
    const usdtAmount = Money.USDT(usdt * treeCount);
    const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent);

    const rewardSource = RewardSource.create(
      RightType.SHARE_RIGHT,
      sourceOrderId,
      sourceUserId,
    );

    // 获取推荐链
    const referralChain = await this.referralService.getReferralChain(sourceUserId);

    if (referralChain.ancestors.length > 0) {
      const directReferrer = referralChain.ancestors[0];

      if (directReferrer.hasPlanted) {
        // 推荐人已认种，直接可结算
        return [RewardLedgerEntry.createSettleable({
          userId: directReferrer.userId,
          rewardSource,
          usdtAmount,
          hashpowerAmount: hashpower,
          memo: `分享权益：来自用户${sourceUserId}的认种`,
        })];
      } else {
        // 推荐人未认种，进入待领取（24h倒计时）
        return [RewardLedgerEntry.createPending({
          userId: directReferrer.userId,
          rewardSource,
          usdtAmount,
          hashpowerAmount: hashpower,
          memo: `分享权益：来自用户${sourceUserId}的认种（24h内认种可领取）`,
        })];
      }
    } else {
      // 无推荐人，进总部社区
      return [RewardLedgerEntry.createSettleable({
        userId: HEADQUARTERS_COMMUNITY_USER_ID,
        rewardSource,
        usdtAmount,
        hashpowerAmount: hashpower,
        memo: '分享权益：无推荐人，进总部社区',
      })];
    }
  }

  /**
   * 计算省团队权益 (20 USDT)
   */
  private async calculateProvinceTeamRight(
    sourceOrderId: bigint,
    sourceUserId: bigint,
    provinceCode: string,
    treeCount: number,
  ): Promise<RewardLedgerEntry> {
    const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.PROVINCE_TEAM_RIGHT];
    const usdtAmount = Money.USDT(usdt * treeCount);
    const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent);

    const rewardSource = RewardSource.create(
      RightType.PROVINCE_TEAM_RIGHT,
      sourceOrderId,
      sourceUserId,
    );

    // 查找最近的授权省公司
    const nearestProvince = await this.authorizationService.findNearestAuthorizedProvince(
      sourceUserId,
      provinceCode,
    );

    if (nearestProvince) {
      return RewardLedgerEntry.createSettleable({
        userId: nearestProvince,
        rewardSource,
        usdtAmount,
        hashpowerAmount: hashpower,
        memo: `省团队权益：来自${provinceCode}省的认种`,
      });
    } else {
      return RewardLedgerEntry.createSettleable({
        userId: HEADQUARTERS_COMMUNITY_USER_ID,
        rewardSource,
        usdtAmount,
        hashpowerAmount: hashpower,
        memo: '省团队权益：无达标授权省公司，进总部社区',
      });
    }
  }

  /**
   * 计算省区域权益 (15 USDT + 1%算力)
   */
  private calculateProvinceAreaRight(
    sourceOrderId: bigint,
    sourceUserId: bigint,
    provinceCode: string,
    treeCount: number,
  ): RewardLedgerEntry {
    const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.PROVINCE_AREA_RIGHT];
    const usdtAmount = Money.USDT(usdt * treeCount);
    const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent);

    const rewardSource = RewardSource.create(
      RightType.PROVINCE_AREA_RIGHT,
      sourceOrderId,
      sourceUserId,
    );

    // 进系统省公司账户 (使用特殊账户ID格式)
    const systemProvinceAccountId = BigInt(`9${provinceCode.padStart(6, '0')}`);

    return RewardLedgerEntry.createSettleable({
      userId: systemProvinceAccountId,
      rewardSource,
      usdtAmount,
      hashpowerAmount: hashpower,
      memo: `省区域权益：${provinceCode}省，15U + 1%算力`,
    });
  }

  /**
   * 计算市团队权益 (40 USDT)
   */
  private async calculateCityTeamRight(
    sourceOrderId: bigint,
    sourceUserId: bigint,
    cityCode: string,
    treeCount: number,
  ): Promise<RewardLedgerEntry> {
    const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.CITY_TEAM_RIGHT];
    const usdtAmount = Money.USDT(usdt * treeCount);
    const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent);

    const rewardSource = RewardSource.create(
      RightType.CITY_TEAM_RIGHT,
      sourceOrderId,
      sourceUserId,
    );

    // 查找最近的授权市公司
    const nearestCity = await this.authorizationService.findNearestAuthorizedCity(
      sourceUserId,
      cityCode,
    );

    if (nearestCity) {
      return RewardLedgerEntry.createSettleable({
        userId: nearestCity,
        rewardSource,
        usdtAmount,
        hashpowerAmount: hashpower,
        memo: `市团队权益：来自${cityCode}市的认种`,
      });
    } else {
      return RewardLedgerEntry.createSettleable({
        userId: HEADQUARTERS_COMMUNITY_USER_ID,
        rewardSource,
        usdtAmount,
        hashpowerAmount: hashpower,
        memo: '市团队权益：无达标授权市公司，进总部社区',
      });
    }
  }

  /**
   * 计算市区域权益 (35 USDT + 2%算力)
   */
  private calculateCityAreaRight(
    sourceOrderId: bigint,
    sourceUserId: bigint,
    cityCode: string,
    treeCount: number,
  ): RewardLedgerEntry {
    const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.CITY_AREA_RIGHT];
    const usdtAmount = Money.USDT(usdt * treeCount);
    const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent);

    const rewardSource = RewardSource.create(
      RightType.CITY_AREA_RIGHT,
      sourceOrderId,
      sourceUserId,
    );

    // 进系统市公司账户
    const systemCityAccountId = BigInt(`8${cityCode.padStart(6, '0')}`);

    return RewardLedgerEntry.createSettleable({
      userId: systemCityAccountId,
      rewardSource,
      usdtAmount,
      hashpowerAmount: hashpower,
      memo: `市区域权益：${cityCode}市，35U + 2%算力`,
    });
  }

  /**
   * 计算社区权益 (80 USDT)
   */
  private async calculateCommunityRight(
    sourceOrderId: bigint,
    sourceUserId: bigint,
    treeCount: number,
  ): Promise<RewardLedgerEntry> {
    const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.COMMUNITY_RIGHT];
    const usdtAmount = Money.USDT(usdt * treeCount);
    const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent);

    const rewardSource = RewardSource.create(
      RightType.COMMUNITY_RIGHT,
      sourceOrderId,
      sourceUserId,
    );

    // 查找最近的社区
    const nearestCommunity = await this.authorizationService.findNearestCommunity(sourceUserId);

    if (nearestCommunity) {
      return RewardLedgerEntry.createSettleable({
        userId: nearestCommunity,
        rewardSource,
        usdtAmount,
        hashpowerAmount: hashpower,
        memo: '社区权益：来自社区成员的认种',
      });
    } else {
      return RewardLedgerEntry.createSettleable({
        userId: HEADQUARTERS_COMMUNITY_USER_ID,
        rewardSource,
        usdtAmount,
        hashpowerAmount: hashpower,
        memo: '社区权益：无归属社区，进总部社区',
      });
    }
  }
}
```

#### 3.5.2 src/domain/services/reward-expiration.service.ts
```typescript
import { Injectable } from '@nestjs/common';
import { RewardLedgerEntry } from '../aggregates/reward-ledger-entry/reward-ledger-entry.aggregate';

@Injectable()
export class RewardExpirationService {
  /**
   * 检查并过期所有到期的待领取奖励
   */
  expireOverdueRewards(pendingRewards: RewardLedgerEntry[]): RewardLedgerEntry[] {
    const expiredRewards: RewardLedgerEntry[] = [];

    for (const reward of pendingRewards) {
      if (reward.isExpiredNow()) {
        reward.expire();
        expiredRewards.push(reward);
      }
    }

    return expiredRewards;
  }

  /**
   * 检查用户的待领取奖励状态
   */
  checkUserPendingRewards(
    pendingRewards: RewardLedgerEntry[],
  ): {
    expired: RewardLedgerEntry[];
    stillPending: RewardLedgerEntry[];
  } {
    const expired: RewardLedgerEntry[] = [];
    const stillPending: RewardLedgerEntry[] = [];

    for (const reward of pendingRewards) {
      if (reward.isExpiredNow()) {
        reward.expire();
        expired.push(reward);
      } else {
        stillPending.push(reward);
      }
    }

    return { expired, stillPending };
  }
}
```

---

## 领域不变式 (Domain Invariants)

```typescript
// 文档参考
class RewardContextInvariants {
  // 1. 待领取奖励必须在24小时内认种，否则过期
  static PENDING_REWARD_EXPIRES_IN_24H =
    "待领取奖励必须在24小时内用户认种，否则进入总部社区";

  // 2. 只有可结算状态才能结算
  static ONLY_SETTLEABLE_CAN_BE_SETTLED =
    "只有可结算状态的收益才能结算";

  // 3. 已结算/已过期的奖励不可变更
  static SETTLED_EXPIRED_IMMUTABLE =
    "已结算或已过期的奖励状态不可变更";

  // 4. 奖励金额必须匹配权益定义
  static REWARD_AMOUNT_MUST_MATCH_DEFINITION =
    "奖励金额必须符合权益定义规则";

  // 5. 分享权益500 USDT必须分配给推荐链
  static REFERRAL_RIGHTS_DISTRIBUTION =
    "分享权益500 USDT必须按推荐链分配";

  // 6. 无上级授权时，权益进系统账户或总部社区
  static FALLBACK_TO_SYSTEM_ACCOUNT =
    "无达标上级时，权益进对应系统账户或总部社区";
}
```

---

## API 端点设计

| 方法 | 路径 | 描述 | 认证 |
|------|------|------|------|
| GET | `/health` | 健康检查 | 否 |
| GET | `/rewards/summary` | 获取我的收益汇总 | JWT |
| GET | `/rewards/details` | 获取我的奖励明细 | JWT |
| GET | `/rewards/pending` | 获取待领取奖励（含倒计时） | JWT |
| POST | `/rewards/settle` | 结算可结算收益 | JWT |
| GET | `/rewards/settlement-history` | 获取结算历史 | JWT |

---

## 事件订阅 (Kafka Events)

### 订阅的事件

| Topic | 事件类型 | 触发条件 | 处理逻辑 |
|-------|---------|---------|---------|
| `planting.order.paid` | PlantingOrderPaidEvent | 认种订单支付成功 | 计算并分配所有奖励 |
| `planting.order.paid` | PlantingOrderPaidEvent | 认种订单支付成功 | 检查该用户是否有待领取奖励，若有则转为可结算 |

### 发布的事件

| Topic | 事件类型 | 触发条件 |
|-------|---------|---------|
| `reward.created` | RewardCreatedEvent | 奖励创建成功 |
| `reward.claimed` | RewardClaimedEvent | 待领取奖励被领取 |
| `reward.expired` | RewardExpiredEvent | 待领取奖励过期 |
| `reward.settled` | RewardSettledEvent | 奖励结算成功 |

---

## 定时任务

### 过期奖励检查

```typescript
// application/schedulers/reward-expiration.scheduler.ts

import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { RewardApplicationService } from '../services/reward-application.service';

@Injectable()
export class RewardExpirationScheduler {
  constructor(
    private readonly rewardService: RewardApplicationService,
  ) {}

  /**
   * 每小时检查过期的待领取奖励
   */
  @Cron(CronExpression.EVERY_HOUR)
  async handleExpiredRewards() {
    console.log('开始检查过期奖励...');

    const result = await this.rewardService.expireOverdueRewards();

    console.log(`过期奖励处理完成：${result.expiredCount}笔，共${result.totalUsdtExpired} USDT`);
  }
}
```

---

## 开发顺序建议

1. **Phase 1: 项目初始化**
   - 创建NestJS项目
   - 安装依赖
   - 配置环境变量

2. **Phase 2: 数据库层**
   - 创建Prisma Schema
   - 运行迁移和种子数据
   - 创建PrismaService

3. **Phase 3: 领域层**
   - 实现所有值对象
   - 实现聚合根 (RewardLedgerEntry, RewardSummary)
   - 实现领域事件
   - 实现领域服务 (RewardCalculationService, RewardExpirationService)
   - 编写单元测试

4. **Phase 4: 基础设施层**
   - 实现仓储 (Repository Implementations)
   - 实现外部服务客户端 (ReferralServiceClient, AuthorizationServiceClient, WalletServiceClient)
   - 实现Kafka消费者和发布者

5. **Phase 5: 应用层**
   - 实现应用服务 (RewardApplicationService)
   - 实现定时任务 (RewardExpirationScheduler)
   - 实现Command/Query handlers

6. **Phase 6: API层**
   - 实现DTO
   - 实现Controllers
   - 配置Swagger文档
   - 配置JWT认证

7. **Phase 7: 测试和部署**
   - 集成测试
   - E2E测试
   - Docker配置

---

## 与前端页面对应关系

### 我的收益页面
- 显示待领取收益（含24小时倒计时）
- 显示可结算收益
- 显示已结算总额
- 显示已过期总额
- 调用: `GET /rewards/summary`

### 奖励明细页面
- 分页显示奖励流水
- 按状态/类型筛选
- 调用: `GET /rewards/details?status=PENDING&page=1&pageSize=20`

### 结算页面
- 选择结算币种（BNB/OG/USDT/DST）
- 显示可结算金额
- 点击结算按钮
- 调用: `POST /rewards/settle`

---

## 注意事项

1. **24小时倒计时**: 前端需要实时显示倒计时，后端返回 `expireAt` 和 `remainingTimeMs`
2. **结算币种选择**: 结算时支持 BNB/OG/USDT/DST 四种币种
3. **过期奖励转移**: 过期奖励自动转入总部社区账户
4. **外部服务依赖**: 需要调用 referral-service 获取推荐链，调用 authorization-service 查找授权用户
5. **SWAP执行**: 结算时调用 wallet-service 执行 SWAP 操作
6. **事务一致性**: 奖励分配需要保证事务一致性，使用数据库事务
7. **幂等性**: Kafka消费者需要实现幂等性，避免重复处理
