# Kiến Trúc Merchant Service

## Tổng Quan Hệ Thống

Merchant Service quản lý **Merchant (Shop Owner)**, **Shop**, và **Merchant Staff** cho hệ sinh thái GoodGo. Service này hỗ trợ đa loại hình kinh doanh: cửa hàng online, cửa hàng vật lý, và hybrid.

```mermaid
graph TB
    subgraph Client["Client Layer"]
        WEB["🌐 Merchant Portal"]
        POS["💳 POS System"]
        MOBILE["📱 Customer App"]
    end
    
    subgraph Gateway["API Gateway"]
        TRAEFIK["⚡ Traefik"]
    end
    
    subgraph Services["Microservices"]
        IAM["🔐 IAM Service<br/>(User & Roles)"]
        MERCHANT["🏪 Merchant Service<br/>(Shop, Staff)"]
        WALLET["💰 Wallet Service"]
        MEMBER["👥 Membership Service"]
        VOUCHER["🎁 Voucher Service"]
    end
    
    subgraph Data["Data Layer"]
        PG[("🐘 PostgreSQL")]
    end
    
    WEB --> TRAEFIK
    POS --> TRAEFIK
    MOBILE --> TRAEFIK
    TRAEFIK --> |"/api/v1/auth"| IAM
    TRAEFIK --> |"/api/v1/merchants<br/>/api/v1/shops"| MERCHANT
    TRAEFIK --> |"/api/v1/wallets"| WALLET
    
    IAM --> |"JWT Token"| MERCHANT
    MERCHANT --> |"Assign Roles"| IAM
    MERCHANT --> |"Settlement"| WALLET
    MERCHANT -.-> |"Customer Visit"| MEMBER
    MERCHANT -.-> |"Voucher Redemption"| VOUCHER
    MERCHANT --> PG

    style IAM fill:#3b82f6,stroke:#1e40af,color:#fff
    style MERCHANT fill:#f97316,stroke:#c2410c,color:#fff
    style WALLET fill:#a855f7,stroke:#7e22ce,color:#fff
    style MEMBER fill:#22c55e,stroke:#15803d,color:#fff
    style VOUCHER fill:#ec4899,stroke:#be185d,color:#fff
    style TRAEFIK fill:#6366f1,stroke:#4338ca,color:#fff
```

> [!IMPORTANT]
> **Phân chia trách nhiệm:**
> - **IAM Service**: Quản lý User identity, Roles (Merchant, MerchantStaff)
> - **Merchant Service**: Quản lý Merchant, Shop, ShopBranch, MerchantStaff
> - **Wallet Service**: Settlement, Commission calculation
> - **Voucher Service**: Gift Voucher (planned)

## Các Lớp Clean Architecture

```
┌────────────────────────────────────────────────────────────┐
│                   Lớp API (Presentation)                   │
│  Controllers, Commands, Queries, Validators, DTOs          │
├────────────────────────────────────────────────────────────┤
│                   Lớp Domain (Core)                        │
│  Entities, Aggregates, Value Objects, Domain Events        │
│  Merchant, Shop, MerchantStaff Aggregates                  │
├────────────────────────────────────────────────────────────┤
│                   Lớp Infrastructure                       │
│  DbContext, Repositories, Entity Configurations            │
│  External Services (IAM Client, Wallet Client)             │
└────────────────────────────────────────────────────────────┘
```

## Domain Model

### Tổng Quan Aggregates

```mermaid
classDiagram
    class Merchant {
        +Guid Id
        +Guid UserId
        +Guid? OrganizationId
        +string BusinessName
        +MerchantType Type
        +MerchantStatus Status
        +BusinessInfo BusinessInfo
        +SettlementConfig SettlementConfig
        +Register()
        +Approve()
        +Suspend()
        +CreateShop()
    }
    
    class Shop {
        +Guid Id
        +Guid MerchantId
        +string Name
        +string Slug
        +ShopType Type
        +BusinessCategory Category
        +ShopStatus Status
        +ContactInfo ContactInfo
        +OperatingHours? Hours
        +AddBranch()
        +AssignStaff()
        +UpdateSettings()
    }
    
    class ShopBranch {
        +Guid Id
        +Guid ShopId
        +string Name
        +string Code
        +Address Address
        +GeoLocation? Location
        +bool IsActive
    }
    
    class MerchantStaff {
        +Guid Id
        +Guid UserId
        +Guid MerchantId
        +StaffRole Role
        +StaffStatus Status
        +string? EmployeeCode
        +StaffPermissions Permissions
        +SetPinCode()
        +RegisterDevice()
    }
    
    class ShopMember {
        +Guid StaffId
        +Guid ShopId
        +Guid? BranchId
        +ShopRole Role
        +int? CustomPermissions
    }
    
    Merchant "1" --> "*" Shop : owns
    Merchant "1" --> "*" MerchantStaff : employs
    Shop "1" --> "*" ShopBranch : has
    Shop "1" --> "*" ShopMember : assigns
    MerchantStaff "1" --> "*" ShopMember : works at

    style Merchant fill:#f97316,stroke:#c2410c,color:#fff
    style Shop fill:#3b82f6,stroke:#1e40af,color:#fff
    style MerchantStaff fill:#22c55e,stroke:#15803d,color:#fff
```

### Merchant Aggregate (Aggregate Root)

```csharp
public class Merchant : Entity, IAggregateRoot
{
    private Guid _userId;              // Reference to IAM User (Owner)
    private Guid? _organizationId;     // Reference to IAM Organization
    private string _businessName;
    private MerchantType _type;        // Individual, Company
    private MerchantStatus _status;    // Pending, Active, Suspended, Banned
    private MerchantVerification _verification;
    private SettlementConfig _settlementConfig;
    
    // Value Object for business details
    public BusinessInfo BusinessInfo { get; private set; }
    
    // Collections
    private readonly List<Shop> _shops = new();
    private readonly List<MerchantStaff> _staff = new();
    
    public IReadOnlyCollection<Shop> Shops => _shops.AsReadOnly();
    public IReadOnlyCollection<MerchantStaff> Staff => _staff.AsReadOnly();
    
    /// <summary>
    /// EN: Factory method to register a new merchant.
    /// VI: Factory method để đăng ký merchant mới.
    /// </summary>
    public static Merchant Register(Guid userId, string businessName, MerchantType type)
    {
        var merchant = new Merchant
        {
            Id = Guid.NewGuid(),
            _userId = userId,
            _businessName = businessName,
            _type = type,
            _status = MerchantStatus.PendingApproval,
            _verification = new MerchantVerification()
        };
        
        merchant.AddDomainEvent(new MerchantRegisteredDomainEvent(merchant));
        return merchant;
    }
    
    /// <summary>
    /// EN: Approve merchant application.
    /// VI: Phê duyệt đơn đăng ký merchant.
    /// </summary>
    public void Approve(Guid approvedBy)
    {
        if (_status != MerchantStatus.PendingApproval)
            throw new DomainException("Can only approve pending merchants");
        
        _status = MerchantStatus.Active;
        AddDomainEvent(new MerchantApprovedDomainEvent(this, approvedBy));
    }
    
    /// <summary>
    /// EN: Create a new shop under this merchant.
    /// VI: Tạo shop mới thuộc merchant này.
    /// </summary>
    public Shop CreateShop(string name, string slug, ShopType type, BusinessCategory category)
    {
        if (_status != MerchantStatus.Active)
            throw new DomainException("Only active merchants can create shops");
        
        var shop = new Shop(Id, name, slug, type, category);
        _shops.Add(shop);
        return shop;
    }
}
```

### Shop Aggregate (Aggregate Root)

```csharp
public class Shop : Entity, IAggregateRoot
{
    private Guid _merchantId;
    private string _name;
    private string _slug;
    private ShopType _type;
    private BusinessCategory _category;
    private ShopStatus _status;
    private ContactInfo _contactInfo;
    private OperatingHours? _operatingHours;
    private ShopSettings _settings;
    
    // Physical locations
    private readonly List<ShopBranch> _branches = new();
    
    // Staff assignments
    private readonly List<ShopMember> _members = new();
    
    public IReadOnlyCollection<ShopBranch> Branches => _branches.AsReadOnly();
    public IReadOnlyCollection<ShopMember> Members => _members.AsReadOnly();
    
    /// <summary>
    /// EN: Add a physical branch to the shop.
    /// VI: Thêm chi nhánh vật lý cho shop.
    /// </summary>
    public ShopBranch AddBranch(string name, Address address, GeoLocation? location = null)
    {
        if (_type == ShopType.OnlineOnly)
            throw new DomainException("Online-only shops cannot have physical branches");
        
        var branch = new ShopBranch(Id, name, address, location);
        _branches.Add(branch);
        AddDomainEvent(new ShopBranchAddedDomainEvent(this, branch));
        return branch;
    }
    
    /// <summary>
    /// EN: Assign a staff member to this shop.
    /// VI: Gán nhân viên cho shop này.
    /// </summary>
    public void AssignStaff(MerchantStaff staff, ShopRole role, Guid? branchId = null)
    {
        var member = new ShopMember(staff.Id, Id, role, branchId);
        _members.Add(member);
        AddDomainEvent(new StaffAssignedToShopDomainEvent(staff, this, role));
    }
}
```

### MerchantStaff Aggregate (Aggregate Root)

```csharp
public class MerchantStaff : Entity, IAggregateRoot
{
    private Guid _userId;           // Reference to IAM User
    private Guid _merchantId;       // Employer
    private StaffRole _role;        // Cashier, Manager, Admin
    private StaffStatus _status;
    private string? _employeeCode;
    private ContactInfo _contactInfo;
    
    // For POS System quick authentication
    private readonly List<DeviceToken> _deviceTokens = new();
    private string? _pinCodeHash;   // Hashed 4-6 digit PIN
    
    // Permissions bitmask
    private StaffPermissions _permissions;
    
    /// <summary>
    /// EN: Set PIN code for POS authentication.
    /// VI: Đặt mã PIN cho xác thực POS.
    /// </summary>
    public void SetPinCode(string pin)
    {
        if (pin.Length < 4 || pin.Length > 6 || !pin.All(char.IsDigit))
            throw new DomainException("PIN must be 4-6 digits");
        
        _pinCodeHash = HashPin(pin);
        AddDomainEvent(new StaffPinCodeSetDomainEvent(this));
    }
    
    /// <summary>
    /// EN: Verify PIN code for POS login.
    /// VI: Xác minh mã PIN cho đăng nhập POS.
    /// </summary>
    public bool VerifyPinCode(string pin)
    {
        if (string.IsNullOrEmpty(_pinCodeHash))
            return false;
        
        return VerifyHash(pin, _pinCodeHash);
    }
    
    /// <summary>
    /// EN: Register a device for push notifications.
    /// VI: Đăng ký thiết bị cho push notifications.
    /// </summary>
    public void RegisterDevice(string deviceId, string deviceName, string fcmToken)
    {
        var existingToken = _deviceTokens.FirstOrDefault(t => t.DeviceId == deviceId);
        if (existingToken != null)
        {
            existingToken.Update(fcmToken);
        }
        else
        {
            var token = new DeviceToken(deviceId, deviceName, fcmToken);
            _deviceTokens.Add(token);
        }
        
        AddDomainEvent(new StaffDeviceRegisteredDomainEvent(this, deviceId));
    }
}
```

### Value Objects

```csharp
/// <summary>
/// EN: Business information for merchant verification.
/// VI: Thông tin doanh nghiệp để xác minh merchant.
/// </summary>
public record BusinessInfo(
    string? TaxId,
    string? BusinessLicenseNumber,
    string? CompanyRegistrationNumber,
    DateTime? EstablishedDate
);

/// <summary>
/// EN: Settlement configuration for merchant payouts.
/// VI: Cấu hình thanh toán cho chi trả merchant.
/// </summary>
public record SettlementConfig(
    BankAccount BankAccount,
    decimal CommissionRate,      // Platform fee (%)
    SettlementCycle Cycle,       // Daily, Weekly, Monthly
    bool AutoSettlement
);

/// <summary>
/// EN: Bank account for settlement.
/// VI: Tài khoản ngân hàng để thanh toán.
/// </summary>
public record BankAccount(
    string BankCode,
    string BankName,
    string AccountNumber,
    string AccountHolderName
);

/// <summary>
/// EN: Physical address.
/// VI: Địa chỉ vật lý.
/// </summary>
public record Address(
    string Street,
    string Ward,
    string District,
    string City,
    string Province,
    string PostalCode,
    string CountryCode = "VN"
);

/// <summary>
/// EN: Geographic location for map display and nearby search.
/// VI: Vị trí địa lý để hiển thị bản đồ và tìm kiếm gần đây.
/// </summary>
public record GeoLocation(double Latitude, double Longitude);

/// <summary>
/// EN: Contact information.
/// VI: Thông tin liên hệ.
/// </summary>
public record ContactInfo(
    string Phone,
    string? Email,
    string? Website
);

/// <summary>
/// EN: Operating hours for physical shops.
/// VI: Giờ hoạt động cho cửa hàng vật lý.
/// </summary>
public record OperatingHours(
    TimeOnly OpenTime,
    TimeOnly CloseTime,
    List<DayOfWeek> OpenDays
);
```

### Enumerations

```csharp
// Merchant Types
public class MerchantType : Enumeration
{
    public static readonly MerchantType Individual = new(1, "Individual");  // Cá nhân
    public static readonly MerchantType Company = new(2, "Company");        // Doanh nghiệp
}

// Merchant Status
public class MerchantStatus : Enumeration
{
    public static readonly MerchantStatus PendingApproval = new(1, "PendingApproval");
    public static readonly MerchantStatus Active = new(2, "Active");
    public static readonly MerchantStatus Suspended = new(3, "Suspended");
    public static readonly MerchantStatus Banned = new(4, "Banned");
}

// Shop Types
public class ShopType : Enumeration
{
    public static readonly ShopType OnlineOnly = new(1, "OnlineOnly");      // Chỉ online
    public static readonly ShopType PhysicalOnly = new(2, "PhysicalOnly");  // Chỉ cửa hàng
    public static readonly ShopType Hybrid = new(3, "Hybrid");              // Cả hai
}

// Business Categories (Ngành nghề)
public class BusinessCategory : Enumeration
{
    public static readonly BusinessCategory FoodBeverage = new(1, "FoodBeverage");
    public static readonly BusinessCategory Fashion = new(2, "Fashion");
    public static readonly BusinessCategory Electronics = new(3, "Electronics");
    public static readonly BusinessCategory Healthcare = new(4, "Healthcare");
    public static readonly BusinessCategory Beauty = new(5, "Beauty");
    public static readonly BusinessCategory Education = new(6, "Education");
    public static readonly BusinessCategory Entertainment = new(7, "Entertainment");
    public static readonly BusinessCategory Services = new(8, "Services");
    public static readonly BusinessCategory Other = new(99, "Other");
}

// Shop Roles (for Staff assignment at shop level)
public class ShopRole : Enumeration
{
    public static readonly ShopRole Cashier = new(1, "Cashier");   // Thu ngân
    public static readonly ShopRole Waiter = new(2, "Waiter");     // Phục vụ
    public static readonly ShopRole Manager = new(3, "Manager");   // Quản lý
    public static readonly ShopRole Owner = new(4, "Owner");       // Chủ shop
}

// Staff Permissions (Bitmask)
[Flags]
public enum StaffPermissions
{
    None = 0,
    ViewSales = 1,
    ProcessPayment = 2,
    RefundOrder = 4,
    ManageInventory = 8,
    ViewReports = 16,
    ManageStaff = 32,
    ManageSettings = 64,
    All = int.MaxValue
}
```

### Domain Events

| Event | Trigger | Mục Đích |
|-------|---------|----------|
| `MerchantRegisteredDomainEvent` | Đăng ký merchant mới | Notify admin for review |
| `MerchantApprovedDomainEvent` | Admin phê duyệt | **Assign "Merchant" role via IAM** |
| `MerchantSuspendedDomainEvent` | Tạm ngưng merchant | Notify merchant, disable shops |
| `ShopCreatedDomainEvent` | Tạo shop mới | Analytics, indexing |
| `ShopBranchAddedDomainEvent` | Thêm chi nhánh | Geo-indexing for nearby search |
| `StaffInvitedDomainEvent` | Mời nhân viên | Send invitation email |
| `StaffJoinedDomainEvent` | Nhân viên chấp nhận | **Assign "MerchantStaff" role via IAM** |
| `StaffDeviceRegisteredDomainEvent` | Đăng ký thiết bị POS | Enable push notifications |

## CQRS Pattern

### Write Side (Commands)

```mermaid
flowchart LR
    subgraph Commands["✏️ Commands"]
        CMD1["RegisterMerchantCommand"]
        CMD2["CreateShopCommand"]
        CMD3["AddShopBranchCommand"]
        CMD4["InviteStaffCommand"]
        CMD5["SetStaffPinCommand"]
    end
    
    subgraph Handlers["⚙️ Command Handlers"]
        H1["RegisterMerchantHandler"]
        H2["CreateShopHandler"]
        H3["AddShopBranchHandler"]
        H4["InviteStaffHandler"]
        H5["SetStaffPinHandler"]
    end
    
    subgraph Domain["🏛️ Domain Model"]
        DM["EF Core<br/>Repository"]
    end
    
    DB[("🐘 Database")]
    
    CMD1 --> H1 --> DM --> DB
    CMD2 --> H2 --> DM
    CMD3 --> H3 --> DM
    CMD4 --> H4 --> DM
    CMD5 --> H5 --> DM

    style CMD1 fill:#ef4444,stroke:#b91c1c,color:#fff
    style CMD2 fill:#ef4444,stroke:#b91c1c,color:#fff
    style CMD3 fill:#ef4444,stroke:#b91c1c,color:#fff
```

### Read Side (Queries)

```mermaid
flowchart LR
    subgraph Queries["📖 Queries"]
        Q1["GetMerchantByIdQuery"]
        Q2["GetShopsByMerchantQuery"]
        Q3["GetNearbyShopsQuery"]
        Q4["GetStaffByShopQuery"]
    end
    
    subgraph Handlers["🔍 Query Handlers"]
        QH1["GetMerchantByIdHandler"]
        QH2["GetShopsByMerchantHandler"]
        QH3["GetNearbyShopsHandler"]
        QH4["GetStaffByShopHandler"]
    end
    
    subgraph ReadModel["📊 Read Model"]
        RM["Dapper / Raw SQL"]
    end
    
    DB[("🐘 Database")]
    
    Q1 --> QH1 --> RM --> DB
    Q2 --> QH2 --> RM
    Q3 --> QH3 --> RM
    Q4 --> QH4 --> RM

    style Q1 fill:#22c55e,stroke:#15803d,color:#fff
    style Q2 fill:#22c55e,stroke:#15803d,color:#fff
    style Q3 fill:#22c55e,stroke:#15803d,color:#fff
```

## Database Schema

```mermaid
erDiagram
    MERCHANTS ||--o{ SHOPS : owns
    MERCHANTS ||--o{ MERCHANT_STAFF : employs
    SHOPS ||--o{ SHOP_BRANCHES : has
    SHOPS ||--o{ SHOP_MEMBERS : assigns
    MERCHANT_STAFF ||--o{ SHOP_MEMBERS : works_at
    MERCHANT_STAFF ||--o{ DEVICE_TOKENS : has
    
    MERCHANTS {
        uuid id PK
        uuid user_id FK "IAM.users"
        uuid organization_id FK "IAM.organizations"
        varchar business_name
        int type_id FK
        int status_id FK
        varchar tax_id
        varchar business_license_number
        varchar bank_account_number
        decimal commission_rate
        int settlement_cycle
        timestamp created_at
    }
    
    SHOPS {
        uuid id PK
        uuid merchant_id FK
        varchar name
        varchar slug UK
        int type_id FK
        int category_id FK
        int status_id FK
        varchar phone
        jsonb operating_hours
        jsonb settings
        varchar logo_url
        timestamp created_at
    }
    
    SHOP_BRANCHES {
        uuid id PK
        uuid shop_id FK
        varchar name
        varchar code
        varchar street
        varchar district
        varchar city
        decimal latitude
        decimal longitude
        boolean is_active
    }
    
    MERCHANT_STAFF {
        uuid id PK
        uuid user_id FK "IAM.users"
        uuid merchant_id FK
        int role_id FK
        int status_id FK
        varchar employee_code
        varchar pin_code_hash
        int permissions
        timestamp joined_at
    }
    
    SHOP_MEMBERS {
        uuid id PK
        uuid staff_id FK
        uuid shop_id FK
        uuid branch_id FK
        int shop_role_id FK
        int custom_permissions
        boolean is_primary
    }
    
    DEVICE_TOKENS {
        uuid id PK
        uuid staff_id FK
        varchar device_id UK
        varchar device_name
        varchar fcm_token
        varchar platform
        timestamp last_used_at
    }
```

## Inter-Service Communication

### Synchronous (HTTP)

| Target Service | Use Case | Method |
|----------------|----------|--------|
| IAM Service | Validate user token | `GET /connect/userinfo` |
| IAM Service | Assign Merchant role | `POST /api/v1/users/{id}/roles` |
| Wallet Service | Check merchant balance | `GET /api/v1/wallets/{merchantId}` |

### Asynchronous (Events)

```mermaid
flowchart LR
    MERCHANT["🏪 Merchant Service"]
    BUS["📬 Event Bus"]
    IAM["🔐 IAM Service"]
    WALLET["💰 Wallet Service"]
    NOTIF["🔔 Notification"]
    
    MERCHANT --> |MerchantApprovedEvent| BUS
    MERCHANT --> |StaffJoinedEvent| BUS
    MERCHANT --> |ShopCreatedEvent| BUS
    
    BUS --> |Subscribe| IAM
    BUS --> |Subscribe| WALLET
    BUS --> |Subscribe| NOTIF
    
    IAM --> |Assign Role| IAM
    WALLET --> |Create Wallet| WALLET
    NOTIF --> |Send Email| NOTIF

    style MERCHANT fill:#f97316,stroke:#c2410c,color:#fff
    style BUS fill:#ef4444,stroke:#b91c1c,color:#fff
    style IAM fill:#3b82f6,stroke:#1e40af,color:#fff
```

## Bảo Mật

### Authorization

| Role | Permissions |
|------|-------------|
| `User` | Register merchant, Accept staff invite |
| `Merchant` | Manage own shops, Manage own staff |
| `MerchantStaff` | Access POS, View assigned shop data |
| `Admin` | Approve/suspend merchants, View all data |

### POS Authentication

```mermaid
sequenceDiagram
    participant POS as POS Device
    participant API as Merchant Service
    participant IAM as IAM Service
    
    POS->>API: POST /api/v1/pos/auth/pin<br/>{userId, pin, deviceId}
    API->>API: Verify PIN hash
    
    alt PIN Valid
        API->>IAM: Validate user & role
        IAM-->>API: OK
        API->>API: Generate short-lived token
        API-->>POS: {posToken, expiresIn: 8h}
    else PIN Invalid
        API-->>POS: 401 Unauthorized
    end
```

## Hạ Tầng

### Docker Compose

```yaml
merchant-service-net:
  build:
    context: ../..
    dockerfile: services/merchant-service-net/Dockerfile
  environment:
    - ASPNETCORE_ENVIRONMENT=Development
    - DATABASE_URL=Host=postgres;Port=5432;Database=merchant;...
    - Jwt__Authority=http://iam-service-net:8080
    - IamService__BaseUrl=http://iam-service-net:8080
    - WalletService__BaseUrl=http://wallet-service-net:8080
  depends_on:
    - postgres
    - iam-service-net
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.merchant.rule=PathPrefix(`/api/v1/merchants`) || PathPrefix(`/api/v1/shops`) || PathPrefix(`/api/v1/pos`)"
    - "traefik.http.services.merchant.loadbalancer.server.port=8080"
```

### Health Checks

| Endpoint | Kiểm tra | Mục đích |
|----------|----------|----------|
| `/health` | Tất cả dependencies | Trạng thái đầy đủ |
| `/health/live` | App đang chạy | Kubernetes liveness |
| `/health/ready` | DB + IAM Service | Kubernetes readiness |

## Giám Sát

### Metrics (Prometheus)

- `merchant_registrations_total` - Số merchant đăng ký
- `merchant_approvals_total` - Số merchant được phê duyệt
- `shop_creations_total` - Số shop được tạo
- `pos_auth_attempts_total` - Số lần đăng nhập POS
- `pos_auth_failures_total` - Số lần đăng nhập POS thất bại

### Logging

Structured Serilog logging với:
- Request/Response logging
- Domain event logging
- POS authentication audit trail
- Error tracking với stack traces

## Xử Lý Lỗi

### Error Codes

| Code | HTTP Status | Description |
|------|-------------|-------------|
| `MERCHANT_NOT_FOUND` | 404 | Merchant không tồn tại |
| `MERCHANT_NOT_ACTIVE` | 400 | Merchant chưa được phê duyệt |
| `SHOP_SLUG_EXISTS` | 409 | Slug shop đã tồn tại |
| `STAFF_ALREADY_EXISTS` | 409 | Nhân viên đã tồn tại |
| `INVALID_PIN` | 401 | PIN không đúng |
| `DEVICE_NOT_REGISTERED` | 403 | Thiết bị chưa đăng ký |

### Response Format

```json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
  "title": "Bad Request",
  "status": 400,
  "detail": "Only active merchants can create shops.",
  "errorCode": "MERCHANT_NOT_ACTIVE",
  "traceId": "00-1234567890abcdef-1234567890abcdef-00"
}
```
