# Hướng Dẫn Tích Hợp Zalo Official Account

Hướng dẫn đầy đủ để tích hợp với Zalo Official Account API.

## Yêu Cầu Tiên Quyết

### 1. Zalo Official Account

Đăng ký và xác minh tài khoản doanh nghiệp tại https://oa.zalo.me/

**Yêu cầu**:
- Giấy phép kinh doanh
- Đăng ký kinh doanh Việt Nam
- 3-5 ngày làm việc để xác minh

### 2. Zalo Application

Tạo ứng dụng tại https://developers.zalo.me/

1. Tạo ứng dụng mới
2. Liên kết với Official Account của bạn
3. Ghi chú `App ID` và `Secret Key`

### 3. Access Token

Lấy OA Access Token (hợp lệ 1 năm):

**Qua Portal** (Development):
1. Vào https://developers.zalo.me/tools/explorer
2. Chọn ứng dụng và OA của bạn
3. Click "Get Access Token"
4. Copy token

**Qua API** (Production):
```http
POST https://oauth.zaloapp.com/v4/oa/access_token
Content-Type: application/x-www-form-urlencoded

app_id={app_id}&secret_key={secret_key}&code={authorization_code}
```

---

## Cấu Hình Webhook

### 1. Đặt Webhook URL

Trong Zalo Developers portal:
1. Vào tab "Webhooks"
2. Đặt URL: `https://yourdomain.com/api/v1/zalo/webhooks`
3. Đặt secret key để xác thực chữ ký

### 2. Bật Events

Events cần thiết:
- ✅ `user_send_text` - Tin nhắn text
- ✅ `user_send_image` - Tin nhắn hình ảnh
- ✅ `follow` - Người theo dõi mới
- ✅ `unfollow` - Bỏ theo dõi

### 3. Xác Thực Chữ Ký

```csharp
public class ZaloWebhookVerifier
{
    private readonly string _secretKey;
    
    public bool VerifySignature(string body, string signature)
    {
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_secretKey));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));
        var computed = BitConverter.ToString(hash).Replace("-", "").ToLower();
        
        return signature.Equals(computed, StringComparison.OrdinalIgnoreCase);
    }
}
```

**Headers**:
```
X-Zalo-Signature: <hmac_sha256_signature>
```

### 4. Yêu Cầu Response

**QUAN TRỌNG**: Phải trả về `200 OK` trong vòng 2 giây

```csharp
[HttpPost("webhooks")]
public async Task<IActionResult> ReceiveWebhook([FromBody] ZaloWebhookEvent webhook)
{
    // 1. Xác thực chữ ký (NHANH)
    if (!_verifier.VerifySignature(RequestBody, Request.Headers["X-Zalo-Signature"]))
        return Unauthorized();
    
    // 2. Queue để xử lý async (NHANH)
    await _messageQueue.PublishAsync(webhook);
    
    // 3. Trả về ngay (< 2s)
    return Ok(new { success = true });
}
```

---

## Zalo API Endpoints

### Base URL
```
https://openapi.zalo.me
```

### 1. Gửi Tin Nhắn Text

```http
POST /v3.0/oa/message/cs
Authorization: access_token {your_access_token}
Content-Type: application/json

{
  "recipient": {
    "user_id": "1234567890123456789"
  },
  "message": {
    "text": "Xin chào! Tôi có thể giúp gì cho bạn?"
  }
}
```

**Response**:
```json
{
  "error": 0,
  "message": "Success",
  "data": {
    "message_id": "zalo_msg_xyz123"
  }
}
```

### 2. Lấy Thông Tin User

```http
GET /v3.0/oa/user/detail?data={"user_id":"1234567890123456789"}
Authorization: access_token {your_access_token}
```

**Response**:
```json
{
  "error": 0,
  "data": {
    "user_id": "1234567890123456789",
    "display_name": "Nguyễn Văn A",
    "avatar": "https://s.zalo.me/avatar.jpg",
    "user_gender": 1
  }
}
```

### 3. Gửi ZNS Template

```http
POST /v3.0/oa/message/template
Authorization: access_token {your_access_token}

{
  "phone": "+84901234567",
  "template_id": "283746",
  "template_data": {
    "order_id": "ORD-12345",
    "total_amount": "500000"
  }
}
```

---

## Loại Events

### user_send_text

```json
{
  "event_name": "user_send_text",
  "sender": { "id": "1234567890123456789" },
  "message": {
    "text": "Xin chào",
    "msg_id": "msg_123456"
  }
}
```

### user_send_image

```json
{
  "event_name": "user_send_image",
  "message": {
    "attachments": [{
      "type": "image",
      "payload": {
        "url": "https://zalo-api.zadn.vn/...",
        "thumbnail": "https://..."
      }
    }]
  }
}
```

### follow

```json
{
  "event_name": "follow",
  "follower": { "id": "1234567890123456789" }
}
```

---

## Giới Hạn Rate

| API | Giới Hạn |
|-----|----------|
| Gửi Message (CS) | 1000 requests/ngày mỗi user |
| Gửi ZNS | 10000 requests/ngày |
| Lấy User Profile | 5000 requests/ngày |

---

## Xử Lý Lỗi

### Mã Lỗi Thường Gặp

| Mã | Thông Báo | Giải Pháp |
|----|-----------|-----------|
| -201 | Invalid access token | Làm mới token |
| -213 | User đã chặn OA | Đánh dấu khách hàng inactive |
| -214 | User không hoạt động 7+ ngày | Dùng ZNS thay vì CS |
| -300 | Vượt giới hạn rate | Đợi và thử lại |

### Chiến Lược Retry

```csharp
var retryPolicy = Policy
    .Handle<HttpRequestException>()
    .OrResult<HttpResponseMessage>(r => (int)r.StatusCode >= 500)
    .WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
```

---

## Best Practices

### Xử Lý Webhook

✅ **NÊN**:
- Xác thực chữ ký mỗi webhook
- Trả về 200 trong vòng 2 giây
- Xử lý bất đồng bộ qua queue
- Lưu raw events trong 7 ngày

❌ **KHÔNG NÊN**:
- Query database trong handler
- Gọi API bên ngoài đồng bộ
- Xử lý LLM trong webhook handler

### Chất Lượng Tin Nhắn

✅ **NÊN**:
- Giữ tin nhắn dưới 2000 ký tự
- Dùng ZNS cho thông báo quan trọng
- Tôn trọng trạng thái chặn OA

❌ **KHÔNG NÊN**:
- Gửi spam
- Gửi marketing không có sự đồng ý

---

## Kiểm Thử

### Kiểm Thử Thủ Công

```bash
# Gửi test webhook
curl -X POST http://localhost:8080/api/v1/zalo/webhooks \
  -H "Content-Type: application/json" \
  -H "X-Zalo-Signature: <signature>" \
  -d '{
    "event_name": "user_send_text",
    "sender": {"id": "test_user"},
    "message": {"text": "xin chào"}
  }'
```

---

**Phiên Bản Tài Liệu**: 1.0  
**Cập Nhật Lần Cuối**: 2026-01-18
