Files
pos-system/services/mkt-zalo-service-net/SERVICE_DOCS.md
Ho Ngoc Hai f3779c4ebe docs: add SERVICE_DOCS.md for all 24 microservices from per-service code audit
Each SERVICE_DOCS.md documents: Overview, API Endpoints, Commands, Queries,
Domain Model, Database Schema, Integration Events, Dependencies, Configuration.
Generated by 23 parallel audit agents reading actual source code.

Key corrections from audit:
- inventory-service: 12 commands/6 queries (was listed as scaffold)
- promotion-service: 12 commands/10 queries (was listed as 0)
- mission-service: 4 commands/7 queries (was listed as 0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:54:53 +07:00

20 KiB

MktZaloService (Zalo Marketing Integration)

Overview

  • Purpose: Microservice for integrating with Zalo Official Account (OA) — managing conversations, customers, chatbot automation rules, ZNS message templates, and webhook event processing.
  • Port: 5000 (configured in Program.cs via ASPNETCORE_URLS)
  • Database: PostgreSQL (connection string: DefaultConnection or DATABASE_URL)
  • Cache: Redis (connection string: Redis or REDIS_URL) with fallback to in-memory cache
  • Architecture: Clean Architecture + CQRS (MediatR 12.4.1)
  • API Versioning: URL segment api/v{version:apiVersion} (v1.0)
  • Note: Some .csproj files still use template naming (MyService.API.csproj, MyService.Domain.csproj, MyService.Infrastructure.csproj)

API Endpoints

WebhooksController (api/v1/webhooks) — AllowAnonymous

Method Route Description
GET /webhooks/zalo Zalo webhook challenge verification (returns challenge value)
POST /webhooks/zalo Receive Zalo webhook events; verifies X-ZaloOA-Signature via HMAC-SHA256

Supported webhook events:

  • user_send_text — Text message from user
  • user_send_image — Image message from user
  • user_send_file — File message from user
  • user_send_sticker — Sticker message from user
  • follow — User follows OA
  • unfollow — User unfollows OA

ConversationsController (api/v1/conversations) — Authorize

Method Route Description
GET /conversations/{id}?skip&take Get conversation with paginated messages
POST /conversations/{id}/messages Send a message in a conversation

CustomersController (api/v1/customers) — Authorize

Method Route Description
GET /customers/{id} Get customer by ID
GET /customers/by-zalo/{zaloUserId} Get customer by Zalo user ID
PATCH /customers/{id} Update customer profile

ChatbotRulesController (api/v1/chatbot-rules) — Authorize

Method Route Description
GET /chatbot-rules?includeInactive List chatbot rules
POST /chatbot-rules Create a new chatbot rule
DELETE /chatbot-rules/{id} Delete a chatbot rule
PATCH /chatbot-rules/{id}/toggle?activate Toggle rule active/inactive

Commands (6 total)

Webhook Commands

Command Result Description
ProcessWebhookCommand(EventName, ZaloUserId, MessageText?, MessageId?, Attachments?) ProcessWebhookResult(Success, Error?) Process incoming Zalo webhook event. Handles: text/image/file/sticker messages, follow/unfollow. Creates customer via GetOrCreate, finds/creates active conversation, adds message, triggers domain event for chatbot auto-response.

Message Commands

Command Result Description
SendMessageCommand(ConversationId, Content, IsFromBot?) SendMessageResult(Success, MessageId?, Error?) Send outgoing message via Zalo OA API. Adds message to conversation, sends via IZaloOfficialAccountClient, updates ZaloMessageId on success.

Customer Commands

Command Result Description
UpdateCustomerCommand(CustomerId, DisplayName, AvatarUrl?, PhoneNumber?, Email?) UpdateCustomerResult(Success, Error?) Update customer profile fields.

Chatbot Rule Commands

Command Result Description
CreateChatbotRuleCommand(Name, Description?, Type, Priority, ActionType, ResponseText?, TemplateId?, Conditions) CreateChatbotRuleResult(Success, RuleId?, Error?) Create a new chatbot automation rule with conditions.
DeleteChatbotRuleCommand(RuleId) DeleteChatbotRuleResult(Success, Error?) Delete a chatbot rule.
ToggleChatbotRuleCommand(RuleId, Activate) ToggleChatbotRuleResult(Success, Error?) Activate or deactivate a chatbot rule.

Queries (4 total)

Query Result Description
GetConversationHistoryQuery(ConversationId, Skip?, Take?) ConversationHistoryDto? Get conversation with paginated messages (default: skip=0, take=50)
GetCustomerQuery(CustomerId) CustomerDto? Get customer by internal ID
GetCustomerByZaloIdQuery(ZaloUserId) CustomerDto? Get customer by Zalo user ID
GetChatbotRulesQuery(IncludeInactive?) List<ChatbotRuleDto> List chatbot rules (optionally include inactive)

DTOs

  • ConversationHistoryDto: ConversationId, ZaloUserId, CustomerId, Status, MessageCount, LastMessagePreview, LastMessageAt, StartedAt, EndedAt, Messages (list of MessageDto)
  • MessageDto: Id, Type, Content, Direction, IsFromBot, SentAt, ZaloMessageId
  • CustomerDto: Id, ZaloUserId, DisplayName, AvatarUrl, PhoneNumber, Email, Segment, ConversationCount, TotalMessageCount, IsActive, FirstInteractionAt, LastInteractionAt, Tags
  • ChatbotRuleDto: Id, Name, Description, Type, Priority, IsActive, ActionType, ResponseText, TemplateId, Conditions (list of RuleConditionDto), MatchCount, LastMatchedAt, CreatedAt
  • RuleConditionDto: Field, Operator, Value

Domain Model

Aggregates

ZaloCustomer (Aggregate Root)

  • Properties: ZaloUserId (unique), Profile (CustomerProfile value object), Segment (CustomerSegment enum), FirstInteractionAt, LastInteractionAt, ConversationCount, TotalMessageCount, IsActive, CreatedAt, UpdatedAt
  • Child Entity: Tag — Name (normalized lowercase), CreatedAt, CustomerId (FK)
  • Value Object: CustomerProfile — DisplayName, AvatarUrl, PhoneNumber, Email
  • Methods: UpdateProfile(), AddTag() (idempotent), RemoveTag(), RecordInteraction(), IncrementConversationCount(), IncrementMessageCount(), MarkInactive(), MarkActive()
  • Auto-segmentation: UpdateSegment() based on engagement:
    • VIP: >50 conversations AND >500 messages
    • Active: >20 conversations AND >200 messages
    • Regular: >5 conversations AND >50 messages
    • New: default
  • Domain Events: CustomerCreatedDomainEvent, CustomerProfileUpdatedDomainEvent

Conversation (Aggregate Root)

  • Properties: ZaloUserId, CustomerId, Status (ConversationStatus enum), StartedAt, EndedAt, MessageCount, LastMessagePreview (max 100 chars + "..."), LastMessageAt, CreatedAt, UpdatedAt
  • Child Entity: Message — Type (MessageType enum), Content, Direction (MessageDirection enum), IsFromBot, SentAt, ZaloMessageId, ConversationId (FK)
  • Methods: AddMessage() (throws ConversationClosedException if closed), Close() (idempotent), Reopen() (idempotent)
  • Domain Events: ConversationStartedDomainEvent, MessageReceivedDomainEvent, ConversationClosedDomainEvent

ChatbotRule (Aggregate Root)

  • Properties: Name, Description, Type (RuleType enum), Action (RuleAction value object), Priority (0-100, higher = evaluated first), IsActive, MatchCount, LastMatchedAt, CreatedAt, UpdatedAt
  • Child Entity: ChatbotRuleCondition — Field, Operator, Value, RuleId (FK), CreatedAt
  • Value Objects:
    • RuleCondition — Field, Operator, Value
    • RuleAction — ActionType (ActionType enum), ResponseText, TemplateId. Factory methods: SendText(), SendTemplate(), ForwardToHuman()
  • Methods: AddCondition(), ClearConditions(), Evaluate(userMessage), RecordMatch(), Activate(), Deactivate(), Update()
  • Evaluation Logic:
    • Keyword: any condition with "contains" operator matches (case-insensitive)
    • Regex: any condition matches regex pattern (1-second timeout)
    • Intent: delegated to AI engine (returns false in Evaluate)
  • Domain Events: ChatbotRuleMatchedDomainEvent

MessageTemplate (Aggregate Root)

  • Properties: ZaloTemplateId (unique, Zalo's ID), Name, Content, Status (TemplateStatus enum), SendCount, CreatedAt, UpdatedAt
  • Child Entity: TemplateParameter — Name, IsRequired, DefaultValue, TemplateId (FK)
  • Methods: AddParameter() (duplicate check), ValidateAndFillParameters() (fills defaults, throws MissingTemplateParametersException), Approve(), Reject(), RecordSend(), Update()

Enums

Enum Values
ConversationStatus Active(0), Closed(1)
MessageType Text(0), Image(1), Link(2), Sticker(3), Audio(4)
MessageDirection Incoming(0), Outgoing(1)
CustomerSegment New(0), Regular(1), Active(2), VIP(3)
RuleType Keyword(0), Regex(1), Intent(2)
ActionType SendText(0), SendTemplate(1), ForwardToHuman(2)
TemplateStatus Pending(0), Approved(1), Rejected(2)

Exceptions

Exception Description
ZaloDomainException Base domain exception
ConversationClosedException Cannot operate on closed conversation
CustomerNotFoundException Customer not found by Zalo user ID
InvalidRuleConfigurationException Invalid rule configuration
MissingTemplateParametersException Missing required template parameters

Database Schema

DbContext: MktZaloServiceContext (8 DbSets)

  • Implements IUnitOfWork, dispatches domain events before SaveChanges
  • Transaction support: BeginTransactionAsync, CommitTransactionAsync, RollbackTransaction
  • Migration: 20260118181258_InitialSchema

Tables

ZaloCustomers

Column Type Constraints
Id uuid PK
ZaloUserId varchar(50) NOT NULL, unique index
DisplayName varchar(255) NOT NULL (owned, from Profile)
AvatarUrl varchar(500) nullable (owned, from Profile)
PhoneNumber varchar(20) nullable (owned, from Profile)
Email varchar(255) nullable (owned, from Profile)
Segment varchar(20) string conversion (enum), indexed
FirstInteractionAt timestamp
LastInteractionAt timestamp indexed
ConversationCount int default 0
TotalMessageCount int default 0
IsActive bool default true
CreatedAt timestamp NOT NULL
UpdatedAt timestamp NOT NULL

Indexes: IX_ZaloCustomers_ZaloUserId (unique), IX_ZaloCustomers_Segment, IX_ZaloCustomers_LastInteractionAt

CustomerTags

Column Type Constraints
Id uuid PK
Name varchar(100) NOT NULL
CustomerId uuid FK -> ZaloCustomers (cascade delete)
CreatedAt timestamp NOT NULL

Indexes: IX_CustomerTags_CustomerId_Name (unique composite)

Conversations

Column Type Constraints
Id uuid PK
ZaloUserId varchar(50) NOT NULL, indexed
CustomerId uuid indexed
Status varchar(20) string conversion (enum)
StartedAt timestamp
EndedAt timestamp nullable
MessageCount int default 0
LastMessagePreview varchar(200)
LastMessageAt timestamp indexed
CreatedAt timestamp NOT NULL
UpdatedAt timestamp NOT NULL

Indexes: IX_Conversations_CustomerId, IX_Conversations_ZaloUserId, IX_Conversations_Status_StartedAt (composite), IX_Conversations_LastMessageAt

Messages

Column Type Constraints
Id uuid PK
Type varchar(20) string conversion (enum)
Content text NOT NULL
Direction varchar(20) string conversion (enum)
IsFromBot bool
SentAt timestamp NOT NULL
ZaloMessageId varchar(100) indexed
ConversationId uuid FK -> Conversations (cascade delete)

Indexes: IX_Messages_ConversationId_SentAt (composite), IX_Messages_ZaloMessageId

ChatbotRules

Column Type Constraints
Id uuid PK
Name varchar(255) NOT NULL, indexed
Description varchar(1000) nullable
Type varchar(20) string conversion (enum)
Priority int default 50
IsActive bool default true
ActionType varchar(30) string conversion (owned from Action)
ActionResponseText varchar(2000) nullable (owned from Action)
ActionTemplateId uuid nullable (owned from Action)
MatchCount int default 0
LastMatchedAt timestamp nullable
CreatedAt timestamp NOT NULL
UpdatedAt timestamp NOT NULL

Indexes: IX_ChatbotRules_IsActive_Priority (IsActive asc, Priority desc), IX_ChatbotRules_Name

RuleConditions

Column Type Constraints
Id uuid PK
Field varchar(50) NOT NULL
Operator varchar(50) NOT NULL
Value text NOT NULL
RuleId uuid FK -> ChatbotRules (cascade delete), indexed
CreatedAt timestamp NOT NULL

Indexes: IX_RuleConditions_RuleId

MessageTemplates

Column Type Constraints
Id uuid PK
ZaloTemplateId varchar(50) NOT NULL, unique index
Name varchar(255) NOT NULL
Content text NOT NULL
Status varchar(20) string conversion (enum), indexed
SendCount int default 0
CreatedAt timestamp NOT NULL
UpdatedAt timestamp NOT NULL

Indexes: IX_MessageTemplates_ZaloTemplateId (unique), IX_MessageTemplates_Status

TemplateParameters

Column Type Constraints
Id uuid PK
Name varchar(100) NOT NULL
IsRequired bool default true
DefaultValue varchar(500) nullable
TemplateId uuid FK -> MessageTemplates (cascade delete)

Indexes: IX_TemplateParameters_TemplateId_Name (unique composite)


Domain Events (7 total)

Event Payload
MessageReceivedDomainEvent ConversationId, MessageId, Content, IsFromBot
ConversationClosedDomainEvent ConversationId
ConversationStartedDomainEvent ConversationId, CustomerId, ZaloUserId
CustomerProfileUpdatedDomainEvent CustomerId, ZaloUserId
CustomerCreatedDomainEvent CustomerId, ZaloUserId, DisplayName
ChatbotRuleMatchedDomainEvent RuleId, ConversationId, MatchedMessage
MessageSentDomainEvent ConversationId, MessageId, ZaloMessageId

Domain Event Handlers

  • MessageReceivedDomainEventHandler: Triggered on MessageReceivedDomainEvent. Skips bot messages (avoids loops). Finds matching chatbot rule via IChatbotRulesService (priority order). Records rule match, then handles action: SendText sends auto-response via SendMessageCommand, SendTemplate logs template ID (not yet implemented), ForwardToHuman logs forwarding.

Application Services

ChatbotRulesService (IChatbotRulesService)

  • FindMatchingRuleAsync: Gets active rules (cached via IChatbotRuleCacheService), evaluates rules in priority order, returns first match
  • GetResponseText: Returns response text for SendText actions, null for SendTemplate/ForwardToHuman

AiChatbotEngine (IChatbotEngine)

  • Status: Placeholder implementation (not connected to real AI)
  • Name: "AI"
  • CanHandleAsync: Always returns true (fallback engine)
  • GenerateResponseAsync: Builds conversation history from cache context, returns placeholder Vietnamese responses based on keyword matching. System prompt instructs friendly Vietnamese assistant behavior.
  • Placeholder responses: Thanks messages, question redirection, default acknowledgment

Caching Services

ChatbotRuleCacheService (IChatbotRuleCacheService)

  • Key: chatbot:rules:active
  • Expiration: 5 minutes (not fully implemented — always loads from DB in MVP)
  • InvalidateRulesCacheAsync: Removes cache key

ConversationCacheService (IConversationCacheService)

  • Key pattern: conversation:{conversationId}
  • Expiration: 30 minutes
  • ConversationContext: ConversationId, CustomerId, ZaloUserId, CustomerName, RecentMessages (list of CachedMessage), LastUpdated
  • GetOrSetAsync: Generic factory pattern for cache-aside

External Services

Zalo Official Account Client (IZaloOfficialAccountClient / ZaloOfficialAccountClient)

  • Base URL: https://openapi.zalo.me (configurable)
  • Authentication: access_token header
  • Resilience: Polly retry (configurable attempts, exponential backoff) + circuit breaker (5 failures, 30s break)
  • JSON: snake_case naming policy
  • Methods:
    • SendTextMessageAsync(zaloUserId, text) — POST /v3.0/oa/message/cs
    • SendTemplateMessageAsync(phoneNumber, templateId, parameters) — POST /v3.0/oa/message/template (ZNS)
    • GetUserProfileAsync(zaloUserId) — GET /v3.0/oa/user/detail?data={json}

Zalo Webhook Verifier (IZaloWebhookVerifier / ZaloWebhookVerifier)

  • Algorithm: HMAC-SHA256
  • Secret: Configured via ZaloOAOptions.WebhookSecret
  • VerifySignature: Computes HMAC-SHA256 of request body with secret, compares to X-ZaloOA-Signature header

Dependencies (NuGet)

API Project

  • MediatR 12.4.1
  • FluentValidation.DependencyInjectionExtensions 11.11.0
  • Swashbuckle.AspNetCore 7.2.0
  • Asp.Versioning.Mvc 8.1.0
  • Asp.Versioning.Mvc.ApiExplorer 8.1.0
  • Hellang.Middleware.ProblemDetails 6.5.1
  • Serilog.AspNetCore 8.0.3
  • AspNetCore.HealthChecks.NpgSql 8.0.2

Infrastructure Project

  • Microsoft.EntityFrameworkCore 10.0.0
  • Microsoft.EntityFrameworkCore.Design 10.0.0
  • Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0
  • MediatR 12.4.1
  • Dapper 2.1.35
  • Polly 8.5.0
  • Polly.Extensions.Http 3.0.0
  • StackExchange.Redis 2.8.16
  • Microsoft.Extensions.Caching.StackExchangeRedis 9.0.0

Configuration

Environment Variables / Settings

  • ConnectionStrings:DefaultConnection or DATABASE_URL — PostgreSQL connection
  • ConnectionStrings:Redis or REDIS_URL — Redis connection (optional, falls back to in-memory)
  • ZaloOA:AppId — Zalo application ID
  • ZaloOA:SecretKey — Zalo application secret key
  • ZaloOA:AccessToken — Zalo OA access token (1-year validity)
  • ZaloOA:WebhookSecret — Webhook signature verification secret
  • ZaloOA:BaseUrl — Zalo OpenAPI base URL (default: https://openapi.zalo.me)
  • ZaloOA:TimeoutSeconds — HTTP timeout (default: 30)
  • ZaloOA:MaxRetryAttempts — Retry attempts (default: 3)

MediatR Pipeline Behaviors

  1. LoggingBehavior — Request/response logging with Stopwatch
  2. ValidatorBehavior — FluentValidation in pipeline
  3. TransactionBehavior — Auto transaction for Commands (skips Queries)

Health Checks

  • PostgreSQL health check via NpgSql

Seed Data (DbSeeder)

  • Chatbot Rules (5 default rules):
    1. Welcome Greeting (priority 100) — Keywords: xin chào, hello, hi, chào
    2. FAQ: Price Inquiry (priority 80) — Keywords: giá, bao nhiêu, price, cost
    3. FAQ: Opening Hours (priority 80) — Keywords: giờ mở cửa, mấy giờ, opening, hours
    4. FAQ: Location (priority 80) — Keywords: địa chỉ, ở đâu, location, address
    5. Request Human Support (priority 90) — Keywords: gặp nhân viên, hỗ trợ, human, support, agent → ForwardToHuman action
  • Message Templates (2 default templates):
    1. Welcome Message (welcome_001) — Parameter: customer_name (required), pre-approved
    2. Order Confirmation (order_confirm_001) — Parameters: order_id, total_amount, delivery_date (all required), pre-approved

DI Registration (All Repositories Registered)

  • IConversationRepository -> ConversationRepository (scoped)
  • ICustomerRepository -> CustomerRepository (scoped)
  • IChatbotRuleRepository -> ChatbotRuleRepository (scoped)
  • IMessageTemplateRepository -> MessageTemplateRepository (scoped)
  • IZaloOfficialAccountClient -> ZaloOfficialAccountClient (HttpClient factory)
  • IZaloWebhookVerifier -> ZaloWebhookVerifier (singleton)
  • IConversationCacheService -> ConversationCacheService (scoped)
  • IChatbotRuleCacheService -> ChatbotRuleCacheService (scoped)
  • IRequestManager -> RequestManager (scoped)
  • IChatbotRulesService -> ChatbotRulesService (registered in Program.cs)