🔵 Zalo Mini App — BOM Gift Integration Plan

Phân tích chi tiết API backend cho Zalo Mini App, ưu tiên Type 2 (Collect/Thu thập)

Version 1.0 Priority: Type 2 Collect Setting: Super Admin

Mục lục 1. Kiến trúc tổng thể 2. Xác thực Zalo Mini App 3. BOM.ASIA Integration — Zalo User Sync 4. API Cấu hình (Super Admin) 5. Type 2: Collect Flow — API chi tiết 6. Type 1: Survey Flow — API chi tiết 7. Database Changes 8. Admin Setting UI 9. Bảo mật 10. Timeline & Phases

1. Kiến trúc tổng thể

Mô hình hoạt động — 3 lớp

┌─────────────────────────────────────────────────────────────┐
│                    ZALO SUPER APP                           │
│  ┌───────────────────────────────────┐                      │
│  │         ZALO MINI APP             │                      │
│  │  (React + ZaUI Components)        │                      │
│  │                                   │                      │
│  │  1. authorize() → Zalo User ID    │                      │
│  │  2. getAccessToken() → token      │                      │
│  │  3. getPhoneNumber() → SĐT       │                      │
│  │  4. getLocation() → GPS           │                      │
│  │                                   │                      │
│  │  Gọi BOM Gift API với token       │                      │
│  └──────────────┬────────────────────┘                      │
│                 │ HTTPS                                      │
└─────────────────┼───────────────────────────────────────────┘
                  │
     ┌────────────▼────────────────────────┐
     │       BOM GIFT BACKEND              │
     │    (Laravel — bom.gift)             │
     │                                     │
     │  /api/v1/zalo/*  (NEW endpoints)    │
     │                                     │
     │  ┌─ ZaloAuthMiddleware ───────────┐ │
     │  │ Nhận token từ Mini App         │ │
     │  │ → Gọi BOM.ASIA verify token   │ │
     │  │ → Nhận phone + Zalo user info  │ │
     │  │ → Resolve tenant + customer    │ │
     │  └──────────────┬────────────────┘ │
     │                 │                   │
     │  Campaign logic (existing):         │
     │  - Collect / Survey / Gift          │
     └────────────┬───────────────────────┘
                  │ sync Zalo user data
     ┌────────────▼────────────────────────┐
     │       BOM.ASIA SERVER               │
     │    (Zalo Ready — existing system)   │
     │                                     │
     │  ┌─ Zalo Integration Hub ─────────┐ │
     │  │                                │ │
     │  │  POST /api/v2/zalo/verify      │ │
     │  │  → Verify Zalo access token    │ │
     │  │  → Decode phone_token          │ │
     │  │  → Return verified user info   │ │
     │  │                                │ │
     │  │  POST /api/v2/zalo/user/sync   │ │
     │  │  → Lưu Zalo user profile       │ │
     │  │  → Map phone ↔ Zalo User ID    │ │
     │  │  → Lưu avatar, display name    │ │
     │  │                                │ │
     │  │  ZNS OTP (existing)            │ │
     │  │  ZNS Templates (existing)      │ │
     │  │  ZNS Notification (existing)   │ │
     │  └────────────────────────────────┘ │
     │                                     │
     │  Single source of truth cho:        │
     │  - Zalo user data                   │
     │  - Zalo OA connections              │
     │  - ZNS messaging                    │
     │  - App credentials (app_id/secret)  │
     └────────────────────────────────────┘
Nguyên tắc quan trọng:
  • BOM.ASIA = Zalo hub — mọi thao tác liên quan Zalo (verify token, decode phone, lưu user info, ZNS) đều qua bom.asia
  • BOM Gift = Campaign logic — chỉ xử lý business (collect, survey, gift, pharmacy)
  • Không lưu Zalo credentials trên BOM Gift — app_id, app_secret nằm trên bom.asia
  • BOM Gift gọi BOM.ASIA API bằng BomApiService (đã có sẵn, JWT auth)

Campaign trên Mini App — Thiết kế tối ưu

Nguyên tắc: Tại 1 thời điểm, mỗi kênh chỉ hiển thị 1 campaign:
Web landing: campaign active mới nhất (giữ nguyên)
Zalo Mini App: campaign được admin chọn hiển thị trên Mini App

Admin Setting: Toggle "Hiển thị trên Zalo Mini App"

Field mớiTrênMô tả
campaigns.zalo_miniappCampaign Edit → General tabBoolean toggle — campaign này có hiển thị trên Mini App không
// Quy tắc:
// - Web landing: Campaign::active()->latest()->first()
// - Zalo Mini App: Campaign::active()->where('zalo_miniapp', true)->first()
// - Nếu không có campaign nào bật zalo_miniapp → Mini App hiện "Chưa có chương trình"
// - Chỉ 1 campaign bật zalo_miniapp tại 1 thời điểm (per tenant)

// API:
GET /api/v1/zalo/campaign
// → Trả 1 campaign có zalo_miniapp=true (giống /campaigns/active nhưng filter thêm)

Lợi ích:

Ví dụ tenant Star:
┌──────────────────────────────────────────────────┐
│ Campaign "Trưng bày POSM" (collect)              │
│   status: active                                 │
│   zalo_miniapp: ✅ true   ← hiển thị trên Mini App│
│   web landing: ✅ (latest) ← hiển thị trên web   │
│                                                  │
│ Campaign "Khảo sát Q2" (survey)                  │
│   status: active                                 │
│   zalo_miniapp: ❌ false  ← không show Mini App  │
│   web landing: ❌ (không phải latest)             │
└──────────────────────────────────────────────────┘

So sánh Web SPA vs Zalo Mini App

Tính năngWeb SPA (hiện tại)Zalo Mini App (mới)
Xác thựcOTP qua Zalo ZNSZalo authorize() + getPhoneNumber() — không cần OTP
SĐTUser nhập thủ côngTự động từ Zalo (đã verified)
GPSBrowser Geolocation APIgetLocation() — chính xác hơn
User IDSession-basedZalo User ID (persistent)
UICustom CSSZaUI Components (50+ native)
PushKhông cóZalo notification API
PaymentKhông cóZalo Checkout SDK (tương lai)

2. Xác thực Zalo Mini App

Flow xác thực (thay thế OTP)

1
authorize()
User đồng ý trong Zalo
2
getAccessToken()
Nhận Zalo access token
3
getPhoneNumber()
Lấy SĐT verified
4
POST /zalo/auth
Gửi token + phone → Backend
5
Verify Token
Backend xác minh với Zalo API
6
JWT/Session
Trả app_token cho Mini App

Frontend (Zalo Mini App - React)

// Step 1-3: Trong Zalo Mini App
import { authorize, getAccessToken, getPhoneNumber } from 'zmp-sdk';

const auth = await authorize();
const { accessToken } = await getAccessToken();
const { token: phoneToken } = await getPhoneNumber();

// Step 4: Gửi lên backend
const res = await fetch('https://tenant.bom.gift/api/v1/zalo/auth', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        access_token: accessToken,
        phone_token: phoneToken,
    })
});
const { app_token, customer } = await res.json();
// Dùng app_token cho mọi API call sau đó

Backend (Laravel — NEW)

// POST /api/v1/zalo/auth
// 1. Nhận access_token + phone_token từ Mini App
// 2. Gọi Zalo API verify: GET graph.zalo.me/v2.0/me?access_token=...
// 3. Decode phone_token → lấy SĐT thật
// 4. Find/Create Customer by phone + tenant
// 5. Lưu zalo_user_id vào customer
// 6. Trả JWT app_token (dùng cho các API sau)

return response()->json([
    'app_token'  => $jwt,
    'customer'   => ['id', 'full_name', 'phone', ...],
    'campaign'   => [...], // active campaign data
]);
Lợi ích: Không cần OTP! User đã verified trong Zalo → SĐT đáng tin cậy 100%. Giảm chi phí ZNS OTP + UX nhanh hơn 3 bước.

3. BOM.ASIA Integration — Zalo User Sync

Vai trò BOM.ASIA trong hệ thống

BOM.ASIA (https://bom.asia/api/v2) là Zalo Hub trung tâm — đã có sẵn hệ thống Zalo (ZNS, OA, templates). Thông tin Zalo user sẽ lưu về bom.asia, BOM Gift chỉ lưu mapping reference.

Chức năngBOM.ASIA (Zalo Hub)BOM Gift (Campaign Logic)
Zalo token verify✅ Verify + decode❌ Gọi qua API
Phone decode✅ Decrypt phone_token❌ Nhận kết quả
Zalo user profile✅ Lưu avatar, name, IDChỉ lưu zalo_user_id
App credentials✅ app_id, app_secret❌ Không lưu
ZNS messaging✅ Send (existing)Gọi qua BomApiService
Campaign data✅ Toàn bộ
Customer data✅ Toàn bộ
Gift/Redeem✅ Toàn bộ

BOM.ASIA APIs đã có sẵn (từ api-information.html)

BOM.ASIA đã có đầy đủ Zalo Mini App module — không cần tạo mới!

Module: Zalo Mini App — Public (/api/miniapp)
Đã có sẵn, BOM Gift chỉ cần gọi.

API A: POST /api/miniapp/auth/decode-phone

ĐÃ CÓ Decode phone token từ Zalo SDK → SĐT thật

POST https://bom.asia/api/miniapp/auth/decode-phone
{
    "phone_token": "token_from_getPhoneNumber()"
}
// → Returns: decoded phone number

API B: POST /api/miniapp/auth/check-phone

ĐÃ CÓ Verify phone + lưu Mini App user IDs

POST https://bom.asia/api/miniapp/auth/check-phone
{
    "phone": "0938123456",
    "mini_app_user_id": "zalo_mini_app_uid",
    "oa_user_id": "zalo_oa_uid",
    "zalo_app_id": "app_id",
    "name": "Nguyễn Văn A",
    "avatar": "https://..."
}
// → Lưu/Update user trên bom.asia + trả user info

API C: POST /api/miniapp/auth/login

ĐÃ CÓ Login via Zalo token hoặc phone → Sanctum token

POST https://bom.asia/api/miniapp/auth/login
{
    "access_token": "zalo_access_token",
    // hoặc phone-based login
}
// → Returns: Bearer token cho các API sau

API D: POST /api/miniapp/checkin/decode-location-token

ĐÃ CÓ Decode location token → GPS coordinates

Các API khác đã sẵn sàng:

APIEndpointMô tả
ZNS SendPOST /api/v2/zns/sendGửi ZNS message (đang dùng cho OTP)
ZNS OTPPOST /api/v2/zns/send-otpGửi OTP qua ZNS
ZNS BulkPOST /api/v2/zns/send-bulkGửi hàng loạt
OA ContactsGET /api/v2/zalo-oa/contactsDS contacts OA
OA MessagePOST /api/v2/zalo-oa/sendGửi message qua OA
WebhooksPOST /api/zalo-oa/webhook83 event types (follow, message, etc.)
E-commerce/api/ecommerce/{appId}/*Cart, Orders, ZaloPay
Kết luận: BOM.ASIA đã có toàn bộ Zalo integration. BOM Gift chỉ cần:
1. Gọi /api/miniapp/auth/decode-phone để decode phone_token
2. Gọi /api/miniapp/auth/check-phone để sync user info
3. Không cần tạo API mới trên bom.asia!

Auth Flow (dùng BOM.ASIA APIs đã có)

1
Mini App
authorize() + getPhoneNumber() + getAccessToken()
2
BOM Gift
POST /zalo/auth nhận tokens
3
BOM.ASIA
/miniapp/auth/decode-phone → SĐT thật
4
BOM.ASIA
/miniapp/auth/check-phone → lưu Zalo profile
5
BOM Gift
Find/Create customer (local)
6
Mini App
Nhận JWT app_token

Code Backend (BOM Gift side)

// POST /api/v1/zalo/auth — ZaloController
public function auth(Request $request)
{
    $bomApi = app(BomApiService::class);
    $tenant = $request->attributes->get('current_tenant');

    // Step 1: Decode phone token → lấy SĐT thật (BOM.ASIA đã có API)
    $decode = $bomApi->decodeZaloPhone($request->phone_token);
    if (!$decode['ok']) abort(401, 'Phone decode failed');
    $phone = $decode['data']['phone'];

    // Step 2: Sync Zalo user info lên BOM.ASIA (lưu trên bom.asia)
    $bomApi->syncZaloMiniAppUser([
        'phone'            => $phone,
        'mini_app_user_id' => $request->zalo_user_id,
        'oa_user_id'       => $request->oa_user_id,
        'zalo_app_id'      => $request->zalo_app_id,
        'name'             => $request->display_name,
        'avatar'           => $request->avatar_url,
    ]);

    // Step 3: Find/Create LOCAL customer (bom.gift chỉ lưu campaign data)
    $campaign = Campaign::active()
        ->where('tenant_id', $tenant->id)->latest()->first();

    $customer = Customer::firstOrCreate(
        ['phone' => $phone, 'tenant_id' => $tenant->id, 'campaign_id' => $campaign?->id],
        [
            'source_type'   => $campaign?->type ?? 'collect',
            'full_name'     => $request->display_name,
            'zalo_user_id'  => $request->zalo_user_id,
            'verified_at'   => now(),
        ]
    );

    // Step 4: Issue JWT cho Mini App (dùng cho các API sau)
    $jwt = JwtService::issue($customer, $tenant);
    return response()->json([
        'app_token'  => $jwt,
        'customer'   => [...],
        'campaign'   => [...],  // active campaign + form_config + programs
    ]);
}
Phân chia dữ liệu rõ ràng:
  • BOM.ASIA lưu: Zalo profile (user_id, phone, name, avatar, OA follower status) — single source of truth cho Zalo data
  • BOM Gift lưu: Customer campaign data (thông tin đăng ký, chương trình, quà, kết quả KS) — giống landing page web hiện tại
  • customers.zalo_user_id = reference link giữa 2 hệ thống
Giống landing pages: Khi user đăng ký qua Zalo Mini App, data campaign (tên, tuổi, khu vực, chương trình chọn, size...) lưu tại BOM Gift customers + program_registrations — hoàn toàn giống khi đăng ký qua web. Chỉ khác: phone tự lấy từ Zalo (không nhập thủ công), Zalo profile sync về bom.asia.

BomApiService — Methods mới (gọi API đã có trên bom.asia)

// Thêm vào app/Services/BomApiService.php
// Base URL: /api/miniapp (KHÔNG cần JWT auth — module public)

public function decodeZaloPhone(string $phoneToken): array
{
    // Gọi API đã có: POST /api/miniapp/auth/decode-phone
    $response = Http::timeout(10)
        ->post("{$this->baseUrl}/../miniapp/auth/decode-phone", [
            'phone_token' => $phoneToken,
        ]);
    return $response->successful()
        ? ['ok' => true, 'data' => $response->json('data')]
        : ['ok' => false, 'error' => $response->json('message', 'Decode failed')];
}

public function syncZaloMiniAppUser(array $data): array
{
    // Gọi API đã có: POST /api/miniapp/auth/check-phone
    $response = Http::timeout(10)
        ->post("{$this->baseUrl}/../miniapp/auth/check-phone", $data);
    return $response->successful()
        ? ['ok' => true, 'data' => $response->json('data')]
        : ['ok' => false, 'error' => $response->json('message')];
}

Tái sử dụng HTTP client. Module /api/miniapp trên bom.asia là public (không cần JWT auth).

4. Kiến trúc Multi-Tenant & Setting

Vấn đề: Mỗi tenant = 1 brand = có thể có Zalo Mini App riêng

Ví dụ thực tế:
Star (star.bom.gift) → Zalo Mini App "Star Health" → app_id: 111
Dizzo (dizzo.bom.gift) → Zalo Mini App "Dizzo Fiber" → app_id: 222
• Mỗi app_id có OA riêng, user base riêng, ZNS template riêng

Giải pháp: 2 tầng setting

TầngAi quản lýỞ đâuNội dung
Tầng 1: Zalo Credentials Super Admin BOM.ASIA app_id, app_secret, OA config — cấu hình trên bom.asia cho từng tenant
Tầng 2: Campaign Config Tenant Admin BOM Gift Bật/tắt Mini App, mapping app_id, form fields, landing content

Tầng 1: Super Admin — BOM.ASIA Setting

Super Admin cấu hình Zalo credentials trên bom.asia (không phải bom.gift):

FieldPerMô tả
zalo_app_idPer Mini AppZalo Mini App ID từ Developer Console
zalo_app_secretPer Mini AppApp Secret (encrypted, chỉ bom.asia giữ)
zalo_oa_idPer OAOfficial Account ID
zalo_oa_secretPer OAOA Secret Key
Tại sao Super Admin? Zalo credentials là thông tin nhạy cảm (app_secret), chỉ Super Admin mới có quyền cấu hình. BOM.ASIA đã có hệ thống quản lý OA/App — tái sử dụng, không duplicate.

BOM.ASIA đã quản lý multi-app:

// bom.asia quản lý nhiều Zalo app:
// /api/miniapp/auth/decode-phone — tự detect app_id từ phone_token
// /api/miniapp/auth/check-phone — truyền zalo_app_id để phân biệt
// /api/v2/zalo-oa/accounts — list tất cả OA accounts
// → Mỗi tenant map với 1 zalo_app_id + 1 oa_id

Tầng 2: Tenant Admin — BOM Gift Setting

Tenant admin cấu hình tại BOM Gift, menu: /admin/branding hoặc /admin/setting

FieldAi settingMô tả
zalo_enabledSuper Admin / Tenant AdminBật/tắt Zalo Mini App cho tenant này
zalo_app_idSuper AdminMapping: tenant này dùng Zalo App nào (reference ID, không phải secret)
zalo_oa_idSuper AdminMapping: tenant này dùng OA nào
zalo_auto_createTenant AdminTự tạo customer khi user mở Mini App lần đầu
zalo_welcome_msgTenant AdminTin nhắn chào mừng qua OA (tùy chọn)

Lưu trữ: tenants.zalo_config (JSON)

// Per-tenant, không chứa secret
{
    "enabled": true,
    "app_id": "111222333",       // reference → bom.asia app
    "oa_id": "444555666",        // reference → bom.asia OA
    "auto_create": true,
    "welcome_msg": "Chào mừng bạn đến với Star!"
}

Multi-Tenant API Flow

Khi user mở Mini App của tenant Star:

// 1. Mini App gửi auth request
POST https://star.bom.gift/api/v1/zalo/auth
{
    "phone_token": "...",
    "zalo_user_id": "...",
    "zalo_app_id": "111222333"  // Mini App tự biết app_id của mình
}

// 2. BOM Gift middleware ResolveTenant
//    Domain star.bom.gift → tenant_id = 2 (Star)
//    Kiểm tra: tenants.zalo_config.enabled === true
//    Kiểm tra: tenants.zalo_config.app_id === request.zalo_app_id

// 3. BOM Gift → BOM.ASIA: decode phone
POST https://bom.asia/api/miniapp/auth/decode-phone
{ "phone_token": "..." }
// → phone: "0938123456"

// 4. BOM Gift → BOM.ASIA: sync user (truyền app_id để bom.asia biết app nào)
POST https://bom.asia/api/miniapp/auth/check-phone
{
    "phone": "0938123456",
    "zalo_app_id": "111222333",  // Star's app
    "mini_app_user_id": "...",
    "name": "Nguyễn Văn A"
}
// → bom.asia lưu user under app 111222333

// 5. BOM Gift: Create local customer
Customer::firstOrCreate(
    ['phone' => '0938123456', 'tenant_id' => 2],  // Star
    ['source_type' => 'collect', 'zalo_user_id' => '...', ...]
);

// 6. Issue JWT chứa tenant_id + customer_id
//    Mọi API sau đều scope by tenant_id trong JWT
Tenant isolation đảm bảo:
  • JWT chứa tenant_id — mọi query đều WHERE tenant_id = ?
  • Domain resolve — star.bom.gift chỉ trả campaign/data của Star
  • zalo_app_id validate — Mini App phải match với tenant config
  • Customer phone + tenant_id unique — cùng phone, 2 tenant = 2 customer records

Admin UI — Ai thấy gì

PageSuper AdminTenant Admin
/admin/setting → Zalo Mini App✅ Cấu hình zalo_app_id, oa_id cho từng tenant
Toggle enabled per tenant
Test connection
❌ Không thấy
/admin/branding → Zalo✅ Xem tất cả✅ Xem config của tenant mình
Toggle auto_create
Nhập welcome_msg
/admin/landing → Form Fields✅ Cấu hình field active/required
/admin/customer✅ Xem tất cả tenant✅ Chỉ thấy KH của mình (bao gồm KH từ Mini App)
Super Admin setting flow:
1. Tạo Zalo Mini App trên Zalo Developer Console
2. Cấu hình app_id + secret trên bom.asia (hệ thống Zalo hub)
3. Vào bom.gift/admin/setting → chọn tenant → nhập app_id + oa_id (reference)
4. Tenant Admin vào bom.gift/admin/branding → bật Zalo Mini App → cấu hình form fields tại Landing Pages
5. Deploy Mini App code (React + ZaUI) trỏ API về tenant.bom.gift/api/v1/zalo/*

4. Type 2: Collect Flow — API Chi Tiết (Ưu tiên)

Flow tổng quan

1
Zalo Auth
authorize + getPhone → /zalo/auth
2
Campaign
GET /zalo/campaign
3
Form
POST /zalo/collect/register
4
Programs
GET /zalo/collect/programs
5
Choose
POST /zalo/collect/choose
6
Summary
GET /zalo/collect/summary

API 1: POST /api/v1/zalo/auth

PUBLIC Xác thực Zalo user, trả app_token

Request

{
    "access_token": "zalo_access_token_from_sdk",
    "phone_token": "phone_number_token_from_getPhoneNumber"
}

Response 200

{
    "app_token": "eyJhbGci...",  // JWT, valid 24h
    "expires_at": "2026-03-19T12:00:00Z",
    "customer": {
        "id": 123,
        "full_name": "Nguyễn Văn A",  // null nếu mới
        "phone": "0938123456",
        "zalo_user_id": "8734526190",
        "is_new": true
    },
    "campaign": {
        "id": 2, "type": "collect", "name": "...",
        "form_config": {...},
        "landing_config": {...},
        "programs": [...]
    }
}

API 2: GET /api/v1/zalo/campaign

AUTH Lấy campaign active + form config + programs

Response

// Giống /campaigns/active hiện tại
// + form_config cho biết field nào active/required
// + programs với image, stock, input_type

API 3: POST /api/v1/zalo/collect/register

AUTH Đăng ký thông tin — phone tự lấy từ token, không cần nhập

Request

{
    "full_name": "Nguyễn Văn A",
    "gender": "male",          // theo form_config
    "age": 25,                  // theo form_config
    "ward_code": "27112",       // theo form_config
    "address": "120 NVL",       // theo form_config
    "pharmacy_name": "...",      // theo form_config
    "medical_rep_id": 5,         // theo form_config
    "notes": "...",              // theo form_config
    "lat": 10.776,               // từ getLocation()
    "lng": 106.700,
    "consent_health": true
}
// phone + zalo_user_id + campaign_id → tự lấy từ auth token

Response 200

{
    "customer_id": 123,
    "message": "Đăng ký thành công"
}
Khác web: phone không cần nhập, không cần check duplicate (Zalo user ID unique), không cần OTP.

API 4-6: Programs, Choose, Summary

AUTH Tái sử dụng logic hiện có

GET  /api/v1/zalo/collect/programs     // → Giống /collect/programs
POST /api/v1/zalo/collect/choose       // → Giống /collect/choose
GET  /api/v1/zalo/collect/summary      // → Giống /collect/summary
GET  /api/v1/zalo/wards/provinces      // → Giống /wards/provinces
GET  /api/v1/zalo/wards?province_code= // → Giống /wards
GET  /api/v1/zalo/collect/medical-reps // → Giống /collect/medical-reps

Sự khác biệt duy nhất: middleware xác thực bằng JWT (từ Zalo auth) thay vì session.

5. Type 1: Survey Flow — API Chi Tiết

Flow tổng quan

1
Zalo Auth
authorize + getPhone
2
Customer Info
POST /zalo/customers
3
Survey
GET /zalo/survey
4
Submit
POST /zalo/survey/submit
5
Gift Code
POST /zalo/gift/generate
6
Pharmacies
Nearby list
POST /api/v1/zalo/auth                // Xác thực (giống type 2)
POST /api/v1/zalo/customers           // Lưu thông tin KH
GET  /api/v1/zalo/survey              // Câu hỏi khảo sát
POST /api/v1/zalo/survey/submit       // Gửi đáp án → scoring
POST /api/v1/zalo/gift/generate       // Tạo gift code + QR
GET  /api/v1/zalo/gift/{code}         // Xem gift code status
Khác biệt lớn nhất: Không cần OTP! Zalo đã verify phone → bỏ toàn bộ bước OTP send/verify. Survey flow giảm từ 7 bước xuống 5 bước.

6. Database Changes

Migration cần tạo

// 1. Thêm zalo_config vào tenants
Schema::table('tenants', function ($table) {
    $table->json('zalo_config')->nullable();
    // {"app_id","app_secret","oa_id","enabled"}
});

// 2. Thêm zalo fields vào customers (zalo_user_id đã có)
// customers.zalo_user_id — đã tồn tại ✓

// 3. Tạo bảng zalo_sessions (JWT tracking)
Schema::create('zalo_sessions', function ($table) {
    $table->id();
    $table->foreignId('customer_id')->constrained();
    $table->foreignId('tenant_id')->constrained();
    $table->string('zalo_user_id');
    $table->string('token_hash', 64)->unique(); // SHA256 of JWT
    $table->timestamp('expires_at');
    $table->string('ip_address', 45)->nullable();
    $table->timestamps();
});

7. Admin Setting UI — Super Admin

Vị trí: /admin/setting → Section "Zalo Mini App"

Per-Tenant Config (Super Admin chọn tenant):
  • Zalo App ID — input text
  • Zalo App Secret — input password (encrypted in DB)
  • Zalo OA ID — input text
  • Enable Mini App — toggle switch
  • Auto Create Customer — toggle (tự tạo KH khi auth lần đầu)
  • Test Connection — button (verify app_id + secret)

Hoặc: Per-Tenant tại Branding page

Thêm tab "Zalo Mini App" tại /admin/branding — tenant admin tự cấu hình.

8. Bảo mật

LayerBiện pháp
Zalo Token VerifyBackend gọi Zalo API xác minh access_token, không tin frontend
Phone VerifyDecode phone_token server-side bằng app_secret, không nhận phone từ client
JWT App TokenSigned bằng APP_KEY, expire 24h, chứa customer_id + tenant_id + zalo_user_id
Tenant IsolationJWT chứa tenant_id, middleware validate tenant match domain
Rate Limitingthrottle:60/min cho authenticated, 20/min cho auth endpoint
CORSChỉ cho phép origin từ Zalo Mini App domain
App SecretEncrypted trong DB (Laravel encrypt), không bao giờ expose ra API

9. Timeline & Phases

Phase 1: Type 2 Collect (Ưu tiên)

#TaskChi tiết
1.1DB Migrationzalo_config trên tenants, zalo_sessions table
1.2ZaloAuthMiddlewareVerify Zalo token, decode phone, issue JWT
1.3POST /zalo/authAuth endpoint — core flow
1.4Zalo Collect APIsregister, programs, choose, summary, wards, medical-reps
1.5Admin Setting UIZalo config form tại Super Admin settings
1.6Test & DeployTest với Zalo Mini App sandbox

Phase 2: Type 1 Survey

#TaskChi tiết
2.1Zalo Survey APIscustomers, survey, submit, gift/generate
2.2Gift Code in Mini AppQR display, pharmacy list
2.3Test & DeployEnd-to-end survey flow

Phase 3: Nâng cao (Tương lai)

#Task
3.1Zalo Notification API — push gift expiry reminders
3.2Zalo Checkout SDK — thanh toán trong app
3.3Zalo Share — share campaign link trong Zalo
3.4Analytics — track Mini App events

API Route Summary

MethodPathAuthMô tả
POST/api/v1/zalo/authPublicXác thực Zalo → JWT
GET/api/v1/zalo/campaignJWTCampaign active + config
POST/api/v1/zalo/collect/registerJWTĐăng ký KH (type 2)
GET/api/v1/zalo/collect/programsJWTDS chương trình
POST/api/v1/zalo/collect/chooseJWTChọn chương trình
GET/api/v1/zalo/collect/summaryJWTTổng kết + QR
GET/api/v1/zalo/collect/medical-repsJWTDS TDV theo khu vực
GET/api/v1/zalo/wards/provincesJWTDS tỉnh/TP
GET/api/v1/zalo/wardsJWTDS phường/xã
POST/api/v1/zalo/customersJWTLưu thông tin KH (type 1)
GET/api/v1/zalo/surveyJWTCâu hỏi khảo sát (type 1)
POST/api/v1/zalo/survey/submitJWTGửi đáp án (type 1)
POST/api/v1/zalo/gift/generateJWTTạo gift code (type 1)

Tóm tắt quyết định kiến trúc

  1. Route group riêng /api/v1/zalo/* — không ảnh hưởng API hiện tại
  2. BOM.ASIA = Zalo Hub — verify token, decode phone, lưu Zalo user profile. BOM Gift KHÔNG giữ Zalo secrets
  3. JWT thay session — stateless, phù hợp Mini App (không có cookie)
  4. Không cần OTP — Zalo đã verify phone, tiết kiệm chi phí ZNS
  5. Tái sử dụng logic — Controller mới gọi Service/Model hiện có
  6. BomApiService mở rộng — thêm verifyZaloToken() + syncZaloUser(), tái sử dụng JWT auth + retry
  7. Config nhẹ per Tenant — chỉ lưu enabled + miniapp_id reference, không lưu secret

BOM Gift Platform · Zalo Mini App API Plan v1.0 · March 2026