docs: Add initial English and Vietnamese README and architecture documentation for the promotion service.

This commit is contained in:
Ho Ngoc Hai
2026-01-17 21:02:29 +07:00
parent 4ed7eb2e52
commit 28731c7686
4 changed files with 1289 additions and 0 deletions

View File

@@ -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<br/>(What's inside voucher)"] --> V["Voucher"]
AM["Acquisition Method<br/>(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

View File

@@ -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

View File

@@ -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<br/>(Tài sản đảm bảo)"] --> V["Voucher"]
AM["Acquisition Method<br/>(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

View File

@@ -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