From 28731c768644426b0a502e34bfb49c77a755a3a1 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Sat, 17 Jan 2026 21:02:29 +0700 Subject: [PATCH] docs: Add initial English and Vietnamese README and architecture documentation for the promotion service. --- .../docs/en/ARCHITECTURE.md | 383 +++++++++++++++++ .../promotion-service-net/docs/en/README.md | 261 ++++++++++++ .../docs/vi/ARCHITECTURE.md | 384 ++++++++++++++++++ .../promotion-service-net/docs/vi/README.md | 261 ++++++++++++ 4 files changed, 1289 insertions(+) create mode 100644 services/promotion-service-net/docs/en/ARCHITECTURE.md create mode 100644 services/promotion-service-net/docs/en/README.md create mode 100644 services/promotion-service-net/docs/vi/ARCHITECTURE.md create mode 100644 services/promotion-service-net/docs/vi/README.md diff --git a/services/promotion-service-net/docs/en/ARCHITECTURE.md b/services/promotion-service-net/docs/en/ARCHITECTURE.md new file mode 100644 index 00000000..2b5f6bcd --- /dev/null +++ b/services/promotion-service-net/docs/en/ARCHITECTURE.md @@ -0,0 +1,383 @@ +# Promotion Service Architecture + +## Overview + +Promotion Service manages marketing campaigns, Vouchers and Gift Cards for GoodGo Platform. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Promotion Service │ +├─────────────────────────────────────────────────────────────┤ +│ API Layer (Controllers, CQRS) │ +├─────────────────────────────────────────────────────────────┤ +│ Domain Layer (Campaign, Voucher Aggregates) │ +├─────────────────────────────────────────────────────────────┤ +│ Infrastructure Layer (EF Core, Repositories, Events) │ +├─────────────────────────────────────────────────────────────┤ +│ PostgreSQL Database │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Decoupling Concept + +```mermaid +graph TB + subgraph "Voucher Creation" + BA["Backing Asset
(What's inside voucher)"] --> V["Voucher"] + AM["Acquisition Method
(How user gets it)"] --> V + end + + subgraph "Backing Assets" + BA1["CURRENCY (VND, USD)"] + BA2["POINT (PPoint)"] + end + + subgraph "Acquisition Methods" + AM1["FREE"] + AM2["EXCHANGE_POINTS"] + AM3["PURCHASE"] + end +``` + +## Architecture Patterns + +### Domain-Driven Design (DDD) + +- **Aggregates**: Campaign, Voucher (nested in Campaign) +- **Entities**: Voucher, Redemption +- **Value Objects**: AssetType, AcquisitionType +- **Domain Events**: CampaignCreated, VoucherClaimed, VoucherRedeemed + +### CQRS Pattern + +``` +Commands (Write) Queries (Read) + │ │ + ▼ ▼ +CreateCampaignCommand GetCampaignQuery +ActivateCampaignCommand GetCampaignsQuery +ClaimVoucherCommand GetVoucherQuery +ExchangeVoucherCommand GetUserVouchersQuery +RedeemVoucherCommand ValidateVoucherQuery +CancelCampaignCommand GetCampaignStatisticsQuery +``` + +## Domain Model + +### Campaign Aggregate + +```mermaid +classDiagram + class Campaign { + +Guid Id + +Guid MerchantId + +string Name + +AssetType BackingAssetType + +string BackingAssetCode + +decimal FaceValue + +AcquisitionType AcquisitionType + +decimal AcquisitionPrice + +Guid EscrowHoldId + +int TotalVouchers + +int IssuedVouchers + +CampaignStatus Status + +List~Voucher~ Vouchers + +Activate() + +Pause() + +Cancel() + +IssueVoucher(userId) + } + + class Voucher { + +Guid Id + +string Code + +Guid? OwnerId + +VoucherStatus Status + +decimal RemainingValue + +DateTime? ClaimedAt + +DateTime? RedeemedAt + +Claim(userId) + +Redeem(amount) + +Expire() + } + + class Redemption { + +Guid Id + +Guid VoucherId + +Guid? OrderId + +decimal AmountUsed + +decimal AmountRefunded + +DateTime RedeemedAt + } + + Campaign "1" --> "*" Voucher + Voucher "1" --> "*" Redemption +``` + +### Enums + +```csharp +public enum AssetType +{ + Currency = 1, // VND, USD + Point = 2 // PPoint +} + +public enum AcquisitionType +{ + Free = 1, // Free giveaway + ExchangePoints = 2, // Exchange points + Purchase = 3 // Purchase with money +} + +public enum CampaignStatus +{ + Draft = 1, + Active = 2, + Paused = 3, + Completed = 4, + Cancelled = 5 +} + +public enum VoucherStatus +{ + Available = 1, + Claimed = 2, + PartiallyRedeemed = 3, + FullyRedeemed = 4, + Expired = 5 +} +``` + +## Database Schema + +### Tables + +| Table | Description | +|-------|-------------| +| `campaigns` | Merchant campaigns | +| `vouchers` | Voucher/gift card codes | +| `redemptions` | Redemption history | + +### Detailed Schema + +```sql +CREATE TABLE campaigns ( + id UUID PRIMARY KEY, + merchant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + backing_asset_type INT NOT NULL, + backing_asset_code VARCHAR(10) NOT NULL, + face_value DECIMAL(18,2) NOT NULL, + acquisition_type INT NOT NULL, + acquisition_price DECIMAL(18,2) DEFAULT 0, + escrow_hold_id UUID, + total_vouchers INT NOT NULL, + issued_vouchers INT DEFAULT 0, + start_date TIMESTAMP NOT NULL, + end_date TIMESTAMP NOT NULL, + status INT NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE vouchers ( + id UUID PRIMARY KEY, + campaign_id UUID NOT NULL REFERENCES campaigns(id), + code VARCHAR(20) UNIQUE NOT NULL, + owner_id UUID, + status INT NOT NULL, + remaining_value DECIMAL(18,2) NOT NULL, + claimed_at TIMESTAMP, + redeemed_at TIMESTAMP, + expires_at TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE redemptions ( + id UUID PRIMARY KEY, + voucher_id UUID NOT NULL REFERENCES vouchers(id), + order_id UUID, + amount_used DECIMAL(18,2) NOT NULL, + amount_refunded DECIMAL(18,2) DEFAULT 0, + redeemed_at TIMESTAMP DEFAULT NOW() +); +``` + +### Key Indexes + +- `IX_Campaigns_MerchantId` - Campaigns by merchant +- `IX_Vouchers_CampaignId` - Vouchers by campaign +- `IX_Vouchers_Code` - Lookup by code +- `IX_Vouchers_OwnerId` - Vouchers by user + +## Service Integration + +### Overall Architecture + +```mermaid +graph TB + APP["Mobile/Web App"] + PS["Promotion Service"] + WS["Wallet Service"] + MS["Merchant Service"] + MQ["RabbitMQ"] + + APP --> PS + PS --> WS + PS --> MS + PS --> MQ + WS --> MQ + + style PS fill:#90EE90 +``` + +### Wallet Service Integration (Escrow) + +``` +Promotion Service ──────► Wallet Service + │ │ + │ Hold/Execute/ │ + │ Release │ + ▼ ▼ + Campaign Escrow Balance + Created Locked/Released +``` + +**API Calls:** + +| Action | Wallet API | Description | +|--------|------------|-------------| +| Create Campaign | `POST /holds` | Lock funds in escrow | +| Redeem Voucher | `POST /holds/{id}/execute` | Deduct from escrow | +| Refund Surplus | `POST /holds/{id}/release` | Return unused amount | +| Cancel Campaign | `POST /holds/{id}/release` | Release full escrow | + +## Processing Flows + +### 1. Create Campaign + +``` +1. Merchant → POST /api/v1/campaigns +2. Validate campaign info +3. Call Wallet Service: Hold(amount, merchantWallet) +4. Save Campaign with escrowHoldId +5. Generate voucher codes +6. Response → Campaign created +``` + +### 2. User Claims Voucher (Free) + +``` +1. User → POST /api/v1/vouchers/claim +2. Validate campaign is active +3. Check user doesn't have this voucher +4. Update voucher.ownerId = userId +5. Response → Voucher code +``` + +### 3. User Redeems Voucher + +``` +1. Checkout → POST /api/v1/vouchers/redeem +2. Validate voucher is valid +3. Calculate: amountUsed = min(faceValue, orderAmount) +4. Calculate: surplus = faceValue - amountUsed +5. Call Wallet: Execute(holdId, amountUsed) +6. If surplus > 0: Call Wallet: Release(holdId, surplus) +7. Update voucher.status = Redeemed +8. Save redemption history +9. Response → Redemption confirmed +``` + +## Integration Events + +### Outgoing Events + +```csharp +// When campaign is created +public record CampaignCreatedIntegrationEvent( + Guid CampaignId, + Guid MerchantId, + string AssetCode, + decimal TotalEscrow); + +// When voucher is redeemed +public record VoucherRedeemedIntegrationEvent( + Guid VoucherId, + Guid CampaignId, + Guid UserId, + Guid? OrderId, + decimal AmountUsed, + decimal AmountRefunded); +``` + +### Incoming Events + +```csharp +// From Wallet Service +public record EscrowHeldIntegrationEvent( + Guid HoldId, + Guid WalletId, + Guid ReferenceId, + decimal Amount); + +public record EscrowExecutedIntegrationEvent( + Guid HoldId, + decimal ExecutedAmount, + string ExecutionRef); +``` + +## Security + +### Authentication +- JWT Bearer token from IAM Service +- Merchant context from Merchant Service + +### Authorization +- Merchant can only manage their own campaigns +- User can only view/use their own vouchers +- Admin has full access + +### Validation +- FluentValidation for all commands +- Rate limiting for claim/redeem APIs + +## Deployment + +### Docker Compose + +```yaml +promotion-service: + build: + context: ../.. + dockerfile: services/promotion-service-net/Dockerfile + environment: + - DATABASE_URL=${PROMOTION_DATABASE_URL} + - WALLET_SERVICE_URL=http://wallet-service:5000 + - RABBITMQ_URL=${RABBITMQ_URL} + - JWT_AUTHORITY=${IAM_SERVICE_URL} + labels: + - traefik.http.routers.promotion.rule=PathPrefix(`/api/v1/campaigns`) || PathPrefix(`/api/v1/vouchers`) +``` + +### Health Checks + +| Endpoint | Checks | +|----------|--------| +| `/health/live` | Service is running | +| `/health/ready` | Database + RabbitMQ + Wallet Service | +| `/health` | Full status | + +## Monitoring + +### Metrics +- Number of active campaigns +- Vouchers issued/redeemed per campaign +- Redemption rate +- Response times + +### Logging +- Serilog structured logging +- Correlation IDs for tracing +- Audit log for financial transactions diff --git a/services/promotion-service-net/docs/en/README.md b/services/promotion-service-net/docs/en/README.md new file mode 100644 index 00000000..6ed70baf --- /dev/null +++ b/services/promotion-service-net/docs/en/README.md @@ -0,0 +1,261 @@ +# Promotion Service .NET + +> **EN**: Campaign, Voucher and Gift Card management service for GoodGo Platform. +> **VI**: Dịch vụ quản lý Chiến dịch, Voucher và Gift Card cho GoodGo Platform. + +## Overview + +Promotion Service manages marketing campaigns with Vouchers and Gift Cards: + +- **Campaign Management** - Create and manage campaigns with multiple models +- **Voucher/Gift Card** - Generate codes, distribute, redeem and expire +- **Unified Asset Model** - Support both Currency and Point as backing assets +- **Escrow Integration** - Integrate escrow with Wallet Service +- **Refund Surplus** - Mechanism to refund unused amount to Merchant + +## Tech Stack + +| Component | Technology | +|-----------|------------| +| Framework | .NET 10 | +| Database | PostgreSQL (EF Core) | +| CQRS | MediatR | +| Validation | FluentValidation | +| API Docs | Swagger/OpenAPI | +| Logging | Serilog | +| Message Bus | RabbitMQ (Events) | + +## Requirements + +```bash +# Check .NET version +dotnet --version # Must be 10.0.x +``` + +## Quick Start + +### 1. Environment Configuration + +```bash +cp .env.example .env +# Edit .env with your database connection +``` + +### 2. Run with Docker + +```bash +cd deployments/local +docker-compose up promotion-service -d +``` + +### 3. Run Locally + +```bash +cd services/promotion-service-net +dotnet restore +dotnet build +dotnet run --project src/PromotionService.API +``` + +## API Endpoints + +### Campaign APIs (Merchant) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/v1/campaigns` | Create new campaign | +| `GET` | `/api/v1/campaigns` | List merchant's campaigns | +| `GET` | `/api/v1/campaigns/{id}` | Get campaign details | +| `PUT` | `/api/v1/campaigns/{id}` | Update campaign | +| `POST` | `/api/v1/campaigns/{id}/activate` | Activate campaign | +| `POST` | `/api/v1/campaigns/{id}/pause` | Pause campaign | +| `POST` | `/api/v1/campaigns/{id}/cancel` | Cancel campaign (refund escrow) | + +### Voucher APIs (User) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/v1/vouchers/claim` | Claim free voucher | +| `POST` | `/api/v1/vouchers/exchange` | Exchange points for voucher | +| `POST` | `/api/v1/vouchers/purchase` | Purchase gift card | +| `GET` | `/api/v1/vouchers/validate/{code}` | Validate voucher code | +| `POST` | `/api/v1/vouchers/redeem` | Redeem voucher | +| `GET` | `/api/v1/users/{userId}/vouchers` | Get user's vouchers | + +### Admin APIs + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/v1/admin/campaigns` | List all campaigns | +| `GET` | `/api/v1/admin/campaigns/{id}/statistics` | Campaign statistics | +| `POST` | `/api/v1/admin/vouchers/{id}/revoke` | Revoke voucher | + +### Health Endpoints + +| Endpoint | Purpose | +|----------|---------| +| `/health` | Full health status | +| `/health/live` | Liveness probe (K8s) | +| `/health/ready` | Readiness probe (K8s) | + +## Project Structure + +``` +promotion-service-net/ +├── src/ +│ ├── PromotionService.API/ # API Layer +│ │ ├── Controllers/ # REST endpoints +│ │ └── Application/ # Commands & Queries +│ │ ├── Commands/ # Write operations +│ │ ├── Queries/ # Read operations +│ │ └── IntegrationEvents/ # Event handlers +│ │ +│ ├── PromotionService.Domain/ # Domain Layer +│ │ ├── AggregatesModel/ +│ │ │ ├── CampaignAggregate/ # Campaign, Voucher +│ │ │ └── RedemptionAggregate/ # Redemption history +│ │ ├── Events/ # Domain events +│ │ └── Exceptions/ # Domain exceptions +│ │ +│ └── PromotionService.Infrastructure/ # Infrastructure Layer +│ ├── EntityConfigurations/ # EF Core mappings +│ ├── Repositories/ # Data access +│ └── PromotionContext.cs # DbContext +│ +├── tests/ +│ ├── PromotionService.UnitTests/ # Domain & Logic tests +│ └── PromotionService.FunctionalTests/ # API integration tests +│ +├── docs/ +│ ├── en/ # English documentation +│ └── vi/ # Vietnamese documentation +│ +└── Dockerfile +``` + +## 4 Marketing Scenarios + +### A. Free Cash Voucher + +```yaml +Campaign: + backing_asset_type: CURRENCY + backing_asset_code: VND + face_value: 50000 + acquisition_type: FREE + acquisition_price: 0 +``` + +### B. Exchange Points for Cash Voucher + +```yaml +Campaign: + backing_asset_type: CURRENCY + backing_asset_code: VND + face_value: 50000 + acquisition_type: EXCHANGE_POINTS + acquisition_price: 500 # 500 points +``` + +### C. Purchase Gift Card + +```yaml +Campaign: + backing_asset_type: CURRENCY + backing_asset_code: VND + face_value: 100000 + acquisition_type: PURCHASE + acquisition_price: 100000 # VND +``` + +### D. Free Bonus Points Voucher + +```yaml +Campaign: + backing_asset_type: POINT + backing_asset_code: PPoint + face_value: 100 # 100 points + acquisition_type: FREE + acquisition_price: 0 +``` + +## Testing + +```bash +# Run all tests +dotnet test + +# Run with coverage +dotnet test /p:CollectCoverage=true + +# Unit tests only +dotnet test tests/PromotionService.UnitTests + +# Functional tests only +dotnet test tests/PromotionService.FunctionalTests +``` + +## Configuration + +### Environment Variables + +| Variable | Description | Required | +|----------|-------------|----------| +| `DATABASE_URL` | PostgreSQL connection | Yes | +| `WALLET_SERVICE_URL` | Wallet Service URL | Yes | +| `RABBITMQ_URL` | RabbitMQ connection | Yes | +| `JWT_AUTHORITY` | JWT issuer URL | Yes | + +### appsettings.json + +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Database=promotion_db;Username=postgres;Password=postgres" + }, + "WalletService": { + "BaseUrl": "http://wallet-service:5000" + }, + "RabbitMQ": { + "Host": "localhost", + "Username": "guest", + "Password": "guest" + } +} +``` + +## Database Migrations + +```bash +# Create migration +dotnet ef migrations add InitialCreate \ + --project src/PromotionService.Infrastructure \ + --startup-project src/PromotionService.API + +# Apply migration +dotnet ef database update \ + --project src/PromotionService.Infrastructure \ + --startup-project src/PromotionService.API +``` + +## Deployment + +### Docker Build + +```bash +docker build -t goodgo/promotion-service:latest . +``` + +### Docker Compose + +Service is registered in `deployments/local/docker-compose.yml` with Traefik routing. + +## Resources + +- [Architecture Documentation](./ARCHITECTURE.md) +- [Wallet Service](../../../wallet-service-net/docs/en/README.md) +- [Merchant Service](../../../merchant-service-net/docs/en/README.md) + +## License + +Proprietary - GoodGo Platform diff --git a/services/promotion-service-net/docs/vi/ARCHITECTURE.md b/services/promotion-service-net/docs/vi/ARCHITECTURE.md new file mode 100644 index 00000000..e88f1b84 --- /dev/null +++ b/services/promotion-service-net/docs/vi/ARCHITECTURE.md @@ -0,0 +1,384 @@ +# Kiến Trúc Promotion Service + +## Tổng Quan + +Promotion Service quản lý chiến dịch khuyến mãi, Voucher và Gift Card cho GoodGo Platform. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Promotion Service │ +├─────────────────────────────────────────────────────────────┤ +│ API Layer (Controllers, CQRS) │ +├─────────────────────────────────────────────────────────────┤ +│ Domain Layer (Campaign, Voucher Aggregates) │ +├─────────────────────────────────────────────────────────────┤ +│ Infrastructure Layer (EF Core, Repositories, Events) │ +├─────────────────────────────────────────────────────────────┤ +│ PostgreSQL Database │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Decoupling Concept + +```mermaid +graph TB + subgraph "Voucher Creation" + BA["Backing Asset
(Tài sản đảm bảo)"] --> V["Voucher"] + AM["Acquisition Method
(Phương thức sở hữu)"] --> V + end + + subgraph "Backing Assets" + BA1["CURRENCY (VND, USD)"] + BA2["POINT (PPoint)"] + end + + subgraph "Acquisition Methods" + AM1["FREE"] + AM2["EXCHANGE_POINTS"] + AM3["PURCHASE"] + end +``` + +## Các Pattern Kiến Trúc + +### Domain-Driven Design (DDD) + +- **Aggregates**: Campaign, Voucher (nested in Campaign) +- **Entities**: Voucher, Redemption +- **Value Objects**: AssetType, AcquisitionType +- **Domain Events**: CampaignCreated, VoucherClaimed, VoucherRedeemed + +### CQRS Pattern + +``` +Commands (Ghi) Queries (Đọc) + │ │ + ▼ ▼ +CreateCampaignCommand GetCampaignQuery +ActivateCampaignCommand GetCampaignsQuery +ClaimVoucherCommand GetVoucherQuery +ExchangeVoucherCommand GetUserVouchersQuery +RedeemVoucherCommand ValidateVoucherQuery +CancelCampaignCommand GetCampaignStatisticsQuery +``` + +## Domain Model + +### Campaign Aggregate + +```mermaid +classDiagram + class Campaign { + +Guid Id + +Guid MerchantId + +string Name + +AssetType BackingAssetType + +string BackingAssetCode + +decimal FaceValue + +AcquisitionType AcquisitionType + +decimal AcquisitionPrice + +Guid EscrowHoldId + +int TotalVouchers + +int IssuedVouchers + +CampaignStatus Status + +List~Voucher~ Vouchers + +Activate() + +Pause() + +Cancel() + +IssueVoucher(userId) + } + + class Voucher { + +Guid Id + +string Code + +Guid? OwnerId + +VoucherStatus Status + +decimal RemainingValue + +DateTime? ClaimedAt + +DateTime? RedeemedAt + +Claim(userId) + +Redeem(amount) + +Expire() + } + + class Redemption { + +Guid Id + +Guid VoucherId + +Guid? OrderId + +decimal AmountUsed + +decimal AmountRefunded + +DateTime RedeemedAt + } + + Campaign "1" --> "*" Voucher + Voucher "1" --> "*" Redemption +``` + +### Enums + +```csharp +public enum AssetType +{ + Currency = 1, // VND, USD + Point = 2 // PPoint +} + +public enum AcquisitionType +{ + Free = 1, // Tặng miễn phí + ExchangePoints = 2, // Đổi điểm + Purchase = 3 // Mua bằng tiền +} + +public enum CampaignStatus +{ + Draft = 1, + Active = 2, + Paused = 3, + Completed = 4, + Cancelled = 5 +} + +public enum VoucherStatus +{ + Available = 1, + Claimed = 2, + PartiallyRedeemed = 3, + FullyRedeemed = 4, + Expired = 5 +} +``` + +## Database Schema + +### Các Bảng + +| Bảng | Mô Tả | +|------|-------| +| `campaigns` | Chiến dịch của Merchant | +| `vouchers` | Mã voucher/gift card | +| `redemptions` | Lịch sử sử dụng | + +### Schema Chi Tiết + +```sql +CREATE TABLE campaigns ( + id UUID PRIMARY KEY, + merchant_id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + backing_asset_type INT NOT NULL, + backing_asset_code VARCHAR(10) NOT NULL, + face_value DECIMAL(18,2) NOT NULL, + acquisition_type INT NOT NULL, + acquisition_price DECIMAL(18,2) DEFAULT 0, + escrow_hold_id UUID, + total_vouchers INT NOT NULL, + issued_vouchers INT DEFAULT 0, + start_date TIMESTAMP NOT NULL, + end_date TIMESTAMP NOT NULL, + status INT NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE vouchers ( + id UUID PRIMARY KEY, + campaign_id UUID NOT NULL REFERENCES campaigns(id), + code VARCHAR(20) UNIQUE NOT NULL, + owner_id UUID, + status INT NOT NULL, + remaining_value DECIMAL(18,2) NOT NULL, + claimed_at TIMESTAMP, + redeemed_at TIMESTAMP, + expires_at TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE redemptions ( + id UUID PRIMARY KEY, + voucher_id UUID NOT NULL REFERENCES vouchers(id), + order_id UUID, + amount_used DECIMAL(18,2) NOT NULL, + amount_refunded DECIMAL(18,2) DEFAULT 0, + redeemed_at TIMESTAMP DEFAULT NOW() +); +``` + +### Indexes Chính + +- `IX_Campaigns_MerchantId` - Campaigns theo merchant +- `IX_Vouchers_CampaignId` - Vouchers theo campaign +- `IX_Vouchers_Code` - Tra cứu theo mã +- `IX_Vouchers_OwnerId` - Vouchers theo user + +## Tích Hợp Giữa Các Service + +### Kiến Trúc Tổng Quan + +```mermaid +graph TB + APP["Mobile/Web App"] + PS["Promotion Service"] + WS["Wallet Service"] + MS["Merchant Service"] + MQ["RabbitMQ"] + + APP --> PS + PS --> WS + PS --> MS + PS --> MQ + WS --> MQ + + style PS fill:#90EE90 +``` + +### Tích Hợp Wallet Service (Escrow) + +``` + +Promotion Service ──────► Wallet Service + │ │ + │ Hold/Execute/ │ + │ Release │ + ▼ ▼ + Campaign Escrow Balance + Created Locked/Released +``` + +**API Calls:** + +| Action | Wallet API | Mô Tả | +|--------|------------|-------| +| Tạo Campaign | `POST /holds` | Lock tiền/điểm vào escrow | +| Sử dụng Voucher | `POST /holds/{id}/execute` | Trừ tiền từ escrow | +| Hoàn dư | `POST /holds/{id}/release` | Trả lại tiền dư | +| Hủy Campaign | `POST /holds/{id}/release` | Hoàn toàn bộ escrow | + +## Luồng Xử Lý + +### 1. Tạo Campaign + +``` +1. Merchant → POST /api/v1/campaigns +2. Validate thông tin campaign +3. Gọi Wallet Service: Hold(amount, merchantWallet) +4. Lưu Campaign với escrowHoldId +5. Sinh voucher codes +6. Response → Campaign created +``` + +### 2. User Lấy Voucher (Free) + +``` +1. User → POST /api/v1/vouchers/claim +2. Validate campaign đang active +3. Kiểm tra user chưa có voucher này +4. Cập nhật voucher.ownerId = userId +5. Response → Voucher code +``` + +### 3. User Sử Dụng Voucher + +``` +1. Checkout → POST /api/v1/vouchers/redeem +2. Validate voucher còn hiệu lực +3. Tính toán: amountUsed = min(faceValue, orderAmount) +4. Tính toán: surplus = faceValue - amountUsed +5. Gọi Wallet: Execute(holdId, amountUsed) +6. Nếu surplus > 0: Gọi Wallet: Release(holdId, surplus) +7. Cập nhật voucher.status = Redeemed +8. Lưu redemption history +9. Response → Redemption confirmed +``` + +## Integration Events + +### Outgoing Events + +```csharp +// Khi campaign được tạo +public record CampaignCreatedIntegrationEvent( + Guid CampaignId, + Guid MerchantId, + string AssetCode, + decimal TotalEscrow); + +// Khi voucher được sử dụng +public record VoucherRedeemedIntegrationEvent( + Guid VoucherId, + Guid CampaignId, + Guid UserId, + Guid? OrderId, + decimal AmountUsed, + decimal AmountRefunded); +``` + +### Incoming Events + +```csharp +// Từ Wallet Service +public record EscrowHeldIntegrationEvent( + Guid HoldId, + Guid WalletId, + Guid ReferenceId, + decimal Amount); + +public record EscrowExecutedIntegrationEvent( + Guid HoldId, + decimal ExecutedAmount, + string ExecutionRef); +``` + +## Bảo Mật + +### Xác Thực +- JWT Bearer token từ IAM Service +- Merchant context từ Merchant Service + +### Phân Quyền +- Merchant chỉ quản lý campaigns của mình +- User chỉ xem/sử dụng vouchers của mình +- Admin có full access + +### Validation +- FluentValidation cho tất cả commands +- Rate limiting cho claim/redeem APIs + +## Triển Khai + +### Docker Compose + +```yaml +promotion-service: + build: + context: ../.. + dockerfile: services/promotion-service-net/Dockerfile + environment: + - DATABASE_URL=${PROMOTION_DATABASE_URL} + - WALLET_SERVICE_URL=http://wallet-service:5000 + - RABBITMQ_URL=${RABBITMQ_URL} + - JWT_AUTHORITY=${IAM_SERVICE_URL} + labels: + - traefik.http.routers.promotion.rule=PathPrefix(`/api/v1/campaigns`) || PathPrefix(`/api/v1/vouchers`) +``` + +### Health Checks + +| Endpoint | Kiểm Tra | +|----------|----------| +| `/health/live` | Service đang chạy | +| `/health/ready` | Database + RabbitMQ + Wallet Service | +| `/health` | Trạng thái đầy đủ | + +## Giám Sát + +### Metrics +- Số lượng campaigns active +- Vouchers issued/redeemed per campaign +- Tỷ lệ redemption +- Thời gian response + +### Logging +- Serilog structured logging +- Correlation IDs cho tracing +- Audit log cho financial transactions diff --git a/services/promotion-service-net/docs/vi/README.md b/services/promotion-service-net/docs/vi/README.md new file mode 100644 index 00000000..ff8a090f --- /dev/null +++ b/services/promotion-service-net/docs/vi/README.md @@ -0,0 +1,261 @@ +# Promotion Service .NET + +> **EN**: Campaign, Voucher and Gift Card management service for GoodGo Platform. +> **VI**: Dịch vụ quản lý Chiến dịch, Voucher và Gift Card cho GoodGo Platform. + +## Tổng Quan + +Promotion Service quản lý chiến dịch khuyến mãi với Voucher và Gift Card: + +- **Campaign Management** - Tạo, quản lý chiến dịch theo nhiều mô hình +- **Voucher/Gift Card** - Sinh mã, phân phối, sử dụng và hết hạn +- **Unified Asset Model** - Hỗ trợ cả Currency và Point làm tài sản đảm bảo +- **Escrow Integration** - Tích hợp ký quỹ với Wallet Service +- **Refund Surplus** - Cơ chế hoàn tiền dư về Merchant + +## Tech Stack + +| Thành Phần | Công Nghệ | +|------------|-----------| +| Framework | .NET 10 | +| Database | PostgreSQL (EF Core) | +| CQRS | MediatR | +| Validation | FluentValidation | +| API Docs | Swagger/OpenAPI | +| Logging | Serilog | +| Message Bus | RabbitMQ (Events) | + +## Yêu Cầu Hệ Thống + +```bash +# Kiểm tra phiên bản .NET +dotnet --version # Phải là 10.0.x +``` + +## Bắt Đầu Nhanh + +### 1. Cấu Hình Môi Trường + +```bash +cp .env.example .env +# Chỉnh sửa .env với connection database của bạn +``` + +### 2. Chạy với Docker + +```bash +cd deployments/local +docker-compose up promotion-service -d +``` + +### 3. Chạy Local + +```bash +cd services/promotion-service-net +dotnet restore +dotnet build +dotnet run --project src/PromotionService.API +``` + +## API Endpoints + +### Campaign APIs (Merchant) + +| Method | Endpoint | Mô Tả | +|--------|----------|-------| +| `POST` | `/api/v1/campaigns` | Tạo chiến dịch mới | +| `GET` | `/api/v1/campaigns` | Danh sách campaigns của merchant | +| `GET` | `/api/v1/campaigns/{id}` | Chi tiết campaign | +| `PUT` | `/api/v1/campaigns/{id}` | Cập nhật campaign | +| `POST` | `/api/v1/campaigns/{id}/activate` | Kích hoạt campaign | +| `POST` | `/api/v1/campaigns/{id}/pause` | Tạm dừng campaign | +| `POST` | `/api/v1/campaigns/{id}/cancel` | Hủy campaign (hoàn escrow) | + +### Voucher APIs (User) + +| Method | Endpoint | Mô Tả | +|--------|----------|-------| +| `POST` | `/api/v1/vouchers/claim` | Nhận voucher miễn phí | +| `POST` | `/api/v1/vouchers/exchange` | Đổi points lấy voucher | +| `POST` | `/api/v1/vouchers/purchase` | Mua gift card | +| `GET` | `/api/v1/vouchers/validate/{code}` | Kiểm tra mã voucher | +| `POST` | `/api/v1/vouchers/redeem` | Sử dụng voucher | +| `GET` | `/api/v1/users/{userId}/vouchers` | Vouchers của user | + +### Admin APIs + +| Method | Endpoint | Mô Tả | +|--------|----------|-------| +| `GET` | `/api/v1/admin/campaigns` | Tất cả campaigns | +| `GET` | `/api/v1/admin/campaigns/{id}/statistics` | Thống kê campaign | +| `POST` | `/api/v1/admin/vouchers/{id}/revoke` | Thu hồi voucher | + +### Health Endpoints + +| Endpoint | Mục Đích | +|----------|----------| +| `/health` | Trạng thái sức khỏe đầy đủ | +| `/health/live` | Liveness probe (K8s) | +| `/health/ready` | Readiness probe (K8s) | + +## Cấu Trúc Dự Án + +``` +promotion-service-net/ +├── src/ +│ ├── PromotionService.API/ # API Layer +│ │ ├── Controllers/ # REST endpoints +│ │ └── Application/ # Commands & Queries +│ │ ├── Commands/ # Thao tác ghi +│ │ ├── Queries/ # Thao tác đọc +│ │ └── IntegrationEvents/ # Event handlers +│ │ +│ ├── PromotionService.Domain/ # Domain Layer +│ │ ├── AggregatesModel/ +│ │ │ ├── CampaignAggregate/ # Campaign, Voucher +│ │ │ └── RedemptionAggregate/ # Redemption history +│ │ ├── Events/ # Domain events +│ │ └── Exceptions/ # Domain exceptions +│ │ +│ └── PromotionService.Infrastructure/ # Infrastructure Layer +│ ├── EntityConfigurations/ # EF Core mappings +│ ├── Repositories/ # Data access +│ └── PromotionContext.cs # DbContext +│ +├── tests/ +│ ├── PromotionService.UnitTests/ # Domain & Logic tests +│ └── PromotionService.FunctionalTests/ # API integration tests +│ +├── docs/ +│ ├── en/ # Tài liệu tiếng Anh +│ └── vi/ # Tài liệu tiếng Việt +│ +└── Dockerfile +``` + +## 4 Kịch Bản Marketing + +### A. Tặng Free Voucher Tiền mặt + +```yaml +Campaign: + backing_asset_type: CURRENCY + backing_asset_code: VND + face_value: 50000 + acquisition_type: FREE + acquisition_price: 0 +``` + +### B. Đổi Point lấy Voucher Tiền mặt + +```yaml +Campaign: + backing_asset_type: CURRENCY + backing_asset_code: VND + face_value: 50000 + acquisition_type: EXCHANGE_POINTS + acquisition_price: 500 # 500 points +``` + +### C. Mua Gift Card bằng Tiền + +```yaml +Campaign: + backing_asset_type: CURRENCY + backing_asset_code: VND + face_value: 100000 + acquisition_type: PURCHASE + acquisition_price: 100000 # VND +``` + +### D. Tặng Voucher tích điểm + +```yaml +Campaign: + backing_asset_type: POINT + backing_asset_code: PPoint + face_value: 100 # 100 points + acquisition_type: FREE + acquisition_price: 0 +``` + +## Testing + +```bash +# Chạy tất cả tests +dotnet test + +# Chạy với coverage +dotnet test /p:CollectCoverage=true + +# Chỉ unit tests +dotnet test tests/PromotionService.UnitTests + +# Chỉ functional tests +dotnet test tests/PromotionService.FunctionalTests +``` + +## Cấu Hình + +### Biến Môi Trường + +| Biến | Mô Tả | Bắt Buộc | +|------|-------|----------| +| `DATABASE_URL` | Connection PostgreSQL | Có | +| `WALLET_SERVICE_URL` | URL Wallet Service | Có | +| `RABBITMQ_URL` | Connection RabbitMQ | Có | +| `JWT_AUTHORITY` | URL JWT issuer | Có | + +### appsettings.json + +```json +{ + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Database=promotion_db;Username=postgres;Password=postgres" + }, + "WalletService": { + "BaseUrl": "http://wallet-service:5000" + }, + "RabbitMQ": { + "Host": "localhost", + "Username": "guest", + "Password": "guest" + } +} +``` + +## Database Migrations + +```bash +# Tạo migration +dotnet ef migrations add InitialCreate \ + --project src/PromotionService.Infrastructure \ + --startup-project src/PromotionService.API + +# Áp dụng migration +dotnet ef database update \ + --project src/PromotionService.Infrastructure \ + --startup-project src/PromotionService.API +``` + +## Triển Khai + +### Docker Build + +```bash +docker build -t goodgo/promotion-service:latest . +``` + +### Docker Compose + +Service được đăng ký trong `deployments/local/docker-compose.yml` với Traefik routing. + +## Tài Nguyên + +- [Tài liệu Kiến trúc](./ARCHITECTURE.md) +- [Wallet Service](../../../wallet-service-net/docs/vi/README.md) +- [Merchant Service](../../../merchant-service-net/docs/vi/README.md) + +## Giấy Phép + +Proprietary - GoodGo Platform