tuantrinitri/zalo-laravel

Complete Laravel package for Zalo integration including OAuth authentication and ZNS (Zalo Notification Service) with automatic token management and queue support

Installs: 8

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/tuantrinitri/zalo-laravel

dev-main 2025-10-05 15:48 UTC

This package is auto-updated.

Last update: 2025-12-05 16:10:15 UTC


README

Latest Version on Packagist Total Downloads License

Package Laravel chuyên nghiệp cho tích hợp Zalo, cung cấp OAuth AuthenticationZNS (Zalo Notification Service) với quản lý token tự động, hỗ trợ queue background và tích hợp seamless với Laravel ecosystem.

📋 Mục lục

🚀 Tính năng

🔐 Zalo OAuth Authentication

  • OAuth 2.0 Flow hoàn chỉnh với PKCE support
  • Lấy thông tin user từ Zalo profile
  • Session management tự động
  • Laravel-style configuration quen thuộc
  • ZaloProviderUser class để xử lý user data

📱 Zalo Notification Service (ZNS)

  • Gửi notification template theo chuẩn Zalo
  • Auto token refresh không cần can thiệp thủ công
  • Queue support cho background processing
  • Bulk messaging gửi hàng loạt
  • Database token storage an toàn
  • Retry mechanisms và error handling thông minh

🛠️ Laravel Integration

  • Auto-discovery service provider và facades
  • Facade patterns dễ sử dụng (Zalo, ZaloZns)
  • Helper functions tiện lợi
  • Artisan commands quản lý token
  • Migration files cho database
  • Event system comprehensive logging

⚡ Performance & Security

  • Smart caching token và template
  • Phone number validation chuẩn Việt Nam
  • Rate limiting tránh spam
  • Error logging chi tiết cho debugging
  • Health checks monitoring system

📋 Yêu cầu hệ thống

  • PHP: 7.4+ (khuyến nghị 8.1+)
  • Laravel: 8.0+ (hỗ trợ đến 11.x)
  • Database: MySQL 5.7+, PostgreSQL 10+, hoặc SQLite 3.8+
  • Queue Driver: Redis (khuyến nghị), Database, hoặc SQS
  • Extensions: ext-curl, ext-json, ext-mbstring

Tài khoản Zalo cần thiết:

  • Zalo Developer Account với App ID/Secret
  • Zalo Official Account (cho ZNS) với OA ID/Secret
  • ZNS Service được kích hoạt và có template

⚡ Cài đặt

Bước 1: Install Package

composer require tuantrinitri/zalo-laravel-integrated

Bước 2: Laravel Auto-Discovery

Package sẽ tự động đăng ký. Với Laravel < 5.5, thêm vào config/app.php:

'providers' => [
    // ...
    Tuantrinitri\\ZaloLaravel\\Providers\\ZaloServiceProvider::class,
],

'aliases' => [
    // ...
    'Zalo' => Tuantrinitri\\ZaloLaravel\\Facades\\Zalo::class,
    'ZaloZns' => Tuantrinitri\\ZaloLaravel\\Facades\\ZaloZns::class,
],

Bước 3: Publish Files

# Publish configuration file
php artisan vendor:publish --tag="zalo-config"

# Publish và chạy migrations
php artisan vendor:publish --tag="zalo-migrations"
php artisan migrate

⚙️ Cấu hình

Environment Variables

Thêm vào file .env:

# === ZALO OAUTH CONFIGURATION ===
ZALO_APP_ID=1234567890123456789
ZALO_APP_SECRET=abcdef1234567890abcdef1234567890
ZALO_REDIRECT_URL=https://yourdomain.com/auth/zalo/callback

# === ZALO OFFICIAL ACCOUNT (OPTIONAL) ===
ZALO_OA_ID=9876543210987654321
ZALO_OA_SECRET=fedcba0987654321fedcba0987654321

# === ZALO ZNS CONFIGURATION ===
ZALO_ZNS_APP_ID=1111222233334444
ZALO_ZNS_SECRET_KEY=xyzkeyabcd1234567890
ZALO_ZNS_BASE_URL=https://business.openapi.zalo.me
ZALO_ZNS_TIMEOUT=30

# === QUEUE CONFIGURATION ===
ZALO_ZNS_QUEUE=zalo-notifications
ZALO_ZNS_MAX_TRIES=3
QUEUE_CONNECTION=redis

Configuration File

File config/zalo.php được tạo với cấu trúc:

return [
    'oauth' => [
        'app_id' => env('ZALO_APP_ID'),
        'app_secret' => env('ZALO_APP_SECRET'),
        'url_callback' => env('ZALO_REDIRECT_URL')
    ],
    
    'oa' => [
        'id' => env('ZALO_OA_ID'),
        'secret' => env('ZALO_OA_SECRET'),
    ],
    
    'zns' => [
        'app_id' => env('ZALO_ZNS_APP_ID'),
        'secret_key' => env('ZALO_ZNS_SECRET_KEY'),
        'base_url' => env('ZALO_ZNS_BASE_URL', 'https://business.openapi.zalo.me'),
        'timeout' => env('ZALO_ZNS_TIMEOUT', 30),
        'queue_name' => env('ZALO_ZNS_QUEUE', 'default'),
        'max_tries' => env('ZALO_ZNS_MAX_TRIES', 3),
        
        // Template IDs
        'templates' => [
            'order_confirmation' => 'your_order_template_id',
            'appointment_reminder' => 'your_appointment_template_id',
            'payment_confirmation' => 'your_payment_template_id',
        ],
    ],
];

Queue Setup (Khuyến nghị)

# Cài đặt Redis
composer require predis/predis

# Cấu hình trong config/queue.php
'connections' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'default',
        'queue' => env('REDIS_QUEUE', 'default'),
        'retry_after' => 90,
        'block_for' => null,
    ],
],

🔐 OAuth Authentication

Basic Login Flow

Tạo Routes

// routes/web.php
use Illuminate\Http\Request;
use Tuantrinitri\ZaloLaravel\Facades\Zalo;

// Redirect đến Zalo login
Route::get('/auth/zalo', function () {
    try {
        $loginUrl = Zalo::loginZalo();
        return redirect($loginUrl);
    } catch (Exception $e) {
        return redirect('/login')
            ->with('error', 'Không thể kết nối đến Zalo. Vui lòng thử lại.');
    }
})->name('auth.zalo');

// Xử lý callback từ Zalo
Route::get('/auth/zalo/callback', function (Request $request) {
    try {
        $zaloUser = Zalo::getInfoUserThenLogin();
        
        if (empty($zaloUser) || !isset($zaloUser['id'])) {
            throw new Exception('Không lấy được thông tin người dùng từ Zalo');
        }
        
        // Tìm hoặc tạo user trong database
        $user = App\Models\User::updateOrCreate(
            ['zalo_id' => $zaloUser['id']], 
            [
                'name' => $zaloUser['name'] ?? 'Zalo User',
                'email' => $zaloUser['id'] . '@zalo.local',
                'avatar' => $zaloUser['picture']['data']['url'] ?? null,
                'zalo_profile' => json_encode($zaloUser),
            ]
        );
        
        // Đăng nhập user
        Auth::login($user, true);
        
        return redirect()
            ->intended('/dashboard')
            ->with('success', 'Đăng nhập thành công với Zalo!');
            
    } catch (Exception $e) {
        Log::error('Zalo OAuth Error: ' . $e->getMessage());
        
        return redirect('/login')
            ->with('error', 'Đăng nhập thất bại. Vui lòng thử lại.');
    }
})->name('auth.zalo.callback');

Update User Model

Thêm migration cho Zalo fields:

// Migration: xxxx_add_zalo_fields_to_users_table.php
Schema::table('users', function (Blueprint $table) {
    $table->string('zalo_id')->nullable()->unique();
    $table->string('avatar')->nullable();
    $table->json('zalo_profile')->nullable();
});

Update User model:

// app/Models/User.php
protected $fillable = [
    'name', 'email', 'password', 'zalo_id', 'avatar', 'zalo_profile'
];

protected $casts = [
    'zalo_profile' => 'array',
];

public function hasZaloAccount(): bool
{
    return !is_null($this->zalo_id);
}

Login Button UI

<!-- resources/views/auth/login.blade.php -->
<div class="social-login">
    <a href="{{ route('auth.zalo') }}" class="btn btn-zalo">
        <img src="/images/zalo-icon.png" alt="Zalo" width="20">
        Đăng nhập bằng Zalo
    </a>
</div>

<style>
.btn-zalo {
    background: #0068ff;
    color: white;
    padding: 10px 20px;
    text-decoration: none;
    border-radius: 5px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.btn-zalo:hover {
    background: #0056cc;
}
</style>

Advanced OAuth Usage

Custom Parameters

use Tuantrinitri\ZaloLaravel\Facades\Zalo;

// Generate login URL with custom parameters
$metadata = Zalo::getLoginUrl(
    'https://yourdomain.com/custom-callback', // Custom callback URL
    'custom-state-data', // Custom state
    'custom-verifier' // Custom PKCE verifier
);

$loginUrl = $metadata['url'];
$state = $metadata['state'];
$codeVerifier = $metadata['code_verifier'];

// Store trong session để sử dụng sau
session([
    'zalo_verifier' => $codeVerifier, 
    'zalo_state' => $state
]);

Get User Info từ Access Token

// Nếu đã có access token
$accessToken = 'your_access_token';
$userProfile = Zalo::getInfoUserFromAccessToken($accessToken);

// Sử dụng helper tạo user object
$user = zalo_provider_user($userProfile);

echo $user->getId();
echo $user->getName();  
echo $user->getEmail();

📱 ZNS Notification Service

Setup Token Ban Đầu

Trước khi sử dụng ZNS, cần setup token:

# Tạo artisan command để setup token
php artisan make:command SetupZnsToken
// app/Console/Commands/SetupZnsToken.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Tuantrinitri\ZaloLaravel\Facades\ZaloZns;

class SetupZnsToken extends Command
{
    protected $signature = 'zns:setup-token {access_token} {refresh_token} {--expires=3600}';
    protected $description = 'Setup initial ZNS tokens';

    public function handle()
    {
        $accessToken = $this->argument('access_token');
        $refreshToken = $this->argument('refresh_token');
        $expiresIn = $this->option('expires');
        
        try {
            $token = ZaloZns::storeTokens($accessToken, $refreshToken, $expiresIn);
            $this->info("ZNS tokens stored successfully!");
            $this->info("Expires at: " . $token->expires_at);
        } catch (Exception $e) {
            $this->error("Failed to store tokens: " . $e->getMessage());
        }
    }
}

Chạy command:

php artisan zns:setup-token "your_access_token" "your_refresh_token" --expires=86400

Basic ZNS Usage

Gửi Notification Ngay Lập Tức

use Tuantrinitri\ZaloLaravel\Facades\ZaloZns;

// Gửi immediately (synchronous)
$success = ZaloZns::sendNow(
    '0123456789', // Phone number
    [
        'customer_name' => 'Nguyễn Văn A',
        'order_code' => 'ORDER123',
        'amount' => '500,000 VND',
        'order_date' => '05/10/2024 14:30'
    ],
    'your_template_id' // Template ID từ Zalo
);

if ($success) {
    echo "Thông báo đã được gửi thành công!";
} else {
    echo "Gửi thông báo thất bại!";
}

Queue Notification (Khuyến nghị)

// Queue để gửi background (asynchronous)
ZaloZns::sendLater(
    '0123456789',
    [
        'customer_name' => 'Nguyễn Văn A', 
        'appointment_time' => '14:30 25/10/2024',
        'service_name' => 'Cắt tóc + Gội đầu'
    ],
    'appointment_template_id',
    60 // Delay 60 giây
);

// Hoặc gửi ngay trong queue
ZaloZns::sendLater('0123456789', $data, 'template_id'); // No delay

Bulk Messaging

$recipients = [
    [
        'phone' => '0123456789',
        'data' => [
            'name' => 'Khách hàng 1',
            'code' => 'ORDER001',
            'amount' => '300,000 VND'
        ]
    ],
    [
        'phone' => '0987654321',
        'data' => [
            'name' => 'Khách hàng 2', 
            'code' => 'ORDER002',
            'amount' => '500,000 VND'
        ]
    ],
    // ... thêm nhiều recipients
];

// Gửi bulk với delay 30 giây
ZaloZns::sendBulk($recipients, 'order_template_id', 30);

Real-world Examples

Order Confirmation Service

// app/Services/NotificationService.php
<?php

namespace App\Services;

use Tuantrinitri\ZaloLaravel\Facades\ZaloZns;
use Illuminate\Support\Facades\Log;

class NotificationService
{
    public function sendOrderConfirmation($order)
    {
        try {
            $customer = $order->customer;
            
            if (!$this->isValidVietnamesePhone($customer->phone)) {
                Log::warning("Invalid phone number for customer: " . $customer->id);
                return false;
            }
            
            $data = [
                'customer_name' => $customer->name,
                'order_code' => $order->code,
                'total_amount' => number_format($order->total) . ' VND',
                'order_date' => $order->created_at->format('d/m/Y H:i'),
                'items_count' => $order->items->count(),
            ];
            
            // Queue để gửi sau 30 giây
            ZaloZns::sendLater(
                $customer->phone, 
                $data,
                config('zalo.zns.templates.order_confirmation'),
                30
            );
            
            Log::info("Order confirmation queued for: " . $customer->phone);
            return true;
            
        } catch (Exception $e) {
            Log::error("Failed to queue order confirmation: " . $e->getMessage());
            return false;
        }
    }
    
    public function sendAppointmentReminder($appointment)
    {
        try {
            $customer = $appointment->customer;
            
            $data = [
                'customer_name' => $customer->name,
                'service_name' => $appointment->service->name,
                'appointment_time' => $appointment->start_time->format('H:i d/m/Y'),
                'location' => $appointment->location,
                'staff_name' => $appointment->staff->name ?? 'Nhân viên',
            ];
            
            // Gửi trước giờ hẹn 2 tiếng
            $sendTime = $appointment->start_time->subHours(2);
            $delaySeconds = now()->diffInSeconds($sendTime, false);
            
            if ($delaySeconds > 0) {
                ZaloZns::sendLater(
                    $customer->phone,
                    $data,
                    config('zalo.zns.templates.appointment_reminder'),
                    $delaySeconds
                );
                
                Log::info("Appointment reminder scheduled for: " . $sendTime);
                return true;
            }
            
        } catch (Exception $e) {
            Log::error("Failed to schedule appointment reminder: " . $e->getMessage());
            return false;
        }
    }
    
    private function isValidVietnamesePhone($phone): bool
    {
        $phone = preg_replace('/[^\d]/', '', $phone);
        return preg_match('/^(0|84)(3[2-9]|5[689]|7[06-9]|8[1-689]|9[0-46-9])[0-9]{7}$/', $phone);
    }
}

Tích hợp trong Controller

// app/Http/Controllers/OrderController.php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Services\NotificationService;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    protected $notificationService;
    
    public function __construct(NotificationService $notificationService)
    {
        $this->notificationService = $notificationService;
    }
    
    public function store(Request $request)
    {
        $validated = $request->validate([
            'customer_id' => 'required|exists:customers,id',
            'items' => 'required|array',
            'items.*.product_id' => 'required|exists:products,id',
            'items.*.quantity' => 'required|integer|min:1',
        ]);
        
        $order = Order::create([
            'customer_id' => $validated['customer_id'],
            'code' => 'ORD' . time(),
            'total' => $this->calculateTotal($validated['items']),
            'status' => 'pending',
        ]);
        
        // Tạo order items
        foreach ($validated['items'] as $item) {
            $order->items()->create($item);
        }
        
        // Gửi notification xác nhận đơn hàng
        $this->notificationService->sendOrderConfirmation($order);
        
        return response()->json([
            'message' => 'Đơn hàng đã được tạo thành công',
            'order' => $order->load(['customer', 'items'])
        ], 201);
    }
}

🔑 Quản lý Token

Automatic Token Refresh

Package tự động schedule refresh token hàng ngày:

// Trong ZaloServiceProvider.php - đã được setup
$schedule->command('zns:refresh-token')->dailyAt('02:00');

Manual Token Management

# Refresh token thủ công
php artisan zns:refresh-token

# Kiểm tra token hiện tại
php artisan tinker
>>> ZaloZns::getCurrentToken()
>>> ZaloZns::hasValidToken()

Token Health Check

// Tạo health check endpoint
Route::get('/health/zns', function () {
    $status = [
        'has_valid_token' => ZaloZns::hasValidToken(),
        'current_token' => ZaloZns::getCurrentToken(),
        'queue_size' => Queue::size('zalo-notifications'),
        'last_refresh' => ZaloZns::getCurrentToken()?->updated_at,
    ];
    
    return response()->json($status);
})->middleware('auth:api');

Custom Token Refresh Logic

// app/Console/Commands/MonitorZnsToken.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Tuantrinitri\ZaloLaravel\Facades\ZaloZns;
use Illuminate\Support\Facades\Log;

class MonitorZnsToken extends Command
{
    protected $signature = 'zns:monitor-token';
    protected $description = 'Monitor ZNS token and refresh if needed';

    public function handle()
    {
        if (!ZaloZns::hasValidToken()) {
            $this->warn('ZNS token is expired or invalid');
            
            $refreshed = ZaloZns::refreshToken();
            if ($refreshed) {
                $this->info('ZNS token refreshed successfully');
                Log::info('ZNS token auto-refreshed by monitor');
            } else {
                $this->error('Failed to refresh ZNS token');
                Log::critical('ZNS token refresh failed - manual intervention required');
                
                // Gửi alert đến admin (email, Slack, etc.)
                // Mail::to(config('app.admin_email'))->send(new ZnsTokenFailedMail());
            }
        } else {
            $token = ZaloZns::getCurrentToken();
            $expiresAt = $token->expires_at;
            $this->info("ZNS token is valid until: {$expiresAt}");
        }
    }
}

Schedule monitor:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // Kiểm tra token mỗi 30 phút
    $schedule->command('zns:monitor-token')
             ->everyThirtyMinutes()
             ->withoutOverlapping();
             
    // Kiểm tra queue health mỗi 5 phút
    $schedule->call(function () {
        $queueSize = Queue::size('zalo-notifications');
        if ($queueSize > 100) {
            Log::warning("ZNS queue backlog: $queueSize jobs");
        }
    })->everyFiveMinutes();
}

🎯 Helper Functions

Package cung cấp nhiều helper functions tiện lợi:

OAuth Helpers

// Tạo user object từ Zalo profile
$profile = ['id' => '123', 'name' => 'John Doe', /* ... */];
$user = zalo_provider_user($profile);

echo $user->getId();    // '123'
echo $user->getName();  // 'John Doe'
echo $user->getEmail(); // null (Zalo không cung cấp email)

ZNS Helpers

// Gửi notification nhanh
$success = zns_send('0123456789', [
    'name' => 'Nguyễn Văn A',
    'code' => 'ORDER123'
], 'template_id');

// Queue notification
zns_send_later('0123456789', [
    'name' => 'Nguyễn Văn A',
    'time' => '14:30 25/10/2024'
], 'template_id', 120); // Delay 2 phút

// Bulk send
$recipients = [
    ['phone' => '0123456789', 'data' => ['name' => 'Customer 1']],
    ['phone' => '0987654321', 'data' => ['name' => 'Customer 2']],
];
zns_bulk($recipients, 'template_id');

// Get ZNS service instance
$znsService = zalo_zns();
$token = $znsService->getCurrentToken();

Specialized Helpers

// Order notification helper
zns_order_notification($order, $customer, 'order_template', 60);

// Appointment notification helper  
zns_appointment_notification($schedule, 'appointment_template', 30);

// Universal ZNS helper
zalo_zns('0123456789', ['name' => 'John'], 'template_id'); // Auto queue
$service = zalo_zns(); // Get service instance

🚀 Advanced Usage

Custom Queue Configuration

// Sử dụng custom queue và connection
use Tuantrinitri\ZaloLaravel\Jobs\SendZnsNotification;

$job = new SendZnsNotification('0123456789', $data, 'template_id');

// Custom queue settings
$job->onQueue('high-priority-zns');
$job->onConnection('redis-cluster');
$job->delay(now()->addMinutes(5));

// Dispatch job
dispatch($job);

Laravel Event Integration

// In your Order model
class Order extends Model
{
    protected static function booted()
    {
        static::created(function ($order) {
            if ($order->status === 'paid') {
                zns_order_notification($order, $order->customer);
            }
        });
    }
}

// In your Schedule model  
class Schedule extends Model
{
    protected static function booted()
    {
        static::created(function ($schedule) {
            zns_appointment_notification($schedule);
        });
    }
}

Laravel Observers

class OrderObserver
{
    public function updated(Order $order)
    {
        if ($order->wasChanged('status') && $order->status === 'completed') {
            $data = [
                'customer_name' => $order->customer->name,
                'order_code' => $order->code,
                'completion_date' => now()->format('d/m/Y H:i')
            ];
            
            ZaloZns::sendLater(
                $order->customer->phone, 
                $data, 
                'order_completion'
            );
        }
    }
}

Complete Configuration Options

// config/zalo-zns.php
return [
    // API URLs
    'api_url' => env('ZALO_ZNS_API_URL', 'https://business.openapi.zalo.me/message/template'),
    'oauth_url' => env('ZALO_ZNS_OAUTH_URL', 'https://oauth.zaloapp.com/v4/oa/access_token'),
    
    // Credentials
    'app_id' => env('ZALO_ZNS_APP_ID'),
    'secret_key' => env('ZALO_ZNS_SECRET_KEY'),
    
    // Templates
    'default_template_id' => env('ZALO_ZNS_DEFAULT_TEMPLATE_ID'),
    'templates' => [
        'order_confirmation' => env('ZALO_ZNS_TEMPLATE_ORDER'),
        'appointment_reminder' => env('ZALO_ZNS_TEMPLATE_APPOINTMENT'),
        // Add more templates...
    ],
    
    // Queue Configuration
    'queue_connection' => env('ZALO_ZNS_QUEUE_CONNECTION', 'default'),
    'queue_name' => env('ZALO_ZNS_QUEUE_NAME', 'zalo-zns'),
    'max_tries' => env('ZALO_ZNS_MAX_TRIES', 3),
    'retry_delay' => env('ZALO_ZNS_RETRY_DELAY', 60),
    
    // Phone Formatting
    'phone_country_code' => env('ZALO_ZNS_PHONE_COUNTRY_CODE', '84'),
    'phone_prefix_remove' => env('ZALO_ZNS_PHONE_PREFIX_REMOVE', '0'),
    
    // Rate Limiting
    'rate_limit_per_minute' => env('ZALO_ZNS_RATE_LIMIT', 100),
    'default_delay_seconds' => env('ZALO_ZNS_DEFAULT_DELAY', 5),
    
    // Logging
    'log_channel' => env('ZALO_ZNS_LOG_CHANNEL', 'stack'),
    'log_success' => env('ZALO_ZNS_LOG_SUCCESS', true),
    'log_errors' => env('ZALO_ZNS_LOG_ERRORS', true),
];

✨ Best Practices

1. Queue Configuration for Production

# Using Supervisor
sudo apt install supervisor

# Create config file: /etc/supervisor/conf.d/zalo-zns-worker.conf
[program:zalo-zns-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/your/app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --queue=zalo-zns
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/path/to/your/app/storage/logs/zns-worker.log

# Start supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start zalo-zns-worker:*

2. Cron Jobs

# Add to crontab
* * * * * cd /path/to/your/app && php artisan schedule:run >> /dev/null 2>&1

3. Environment Variables cho Production

QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

ZALO_ZNS_APP_ID=your_production_app_id
ZALO_ZNS_SECRET_KEY=your_production_secret
ZALO_ZNS_DEFAULT_TEMPLATE_ID=your_production_template

4. Error Handling

use Tuantrinitri\ZaloLaravel\Jobs\SendZnsNotification;

class CustomZnsJob extends SendZnsNotification
{
    public function failed(\Throwable $exception): void
    {
        parent::failed($exception);
        
        // Your custom error handling
        Mail::to('admin@yourapp.com')->send(
            new ZnsFailureNotification($this->phone, $exception)
        );
    }
}

5. Database Schema

ZNS Tokens Table

CREATE TABLE `zns_tokens` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `access_token` text NOT NULL COMMENT 'Zalo access token for API calls',
  `refresh_token` text NOT NULL COMMENT 'Refresh token to get new access token',
  `expires_at` timestamp NULL DEFAULT NULL COMMENT 'Token expiration time',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `zns_tokens_expires_at_index` (`expires_at`)
);

ZNS Logs Table (Optional)

CREATE TABLE `zns_logs` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `phone` varchar(20) NOT NULL COMMENT 'Phone number that received the message',
  `template_id` varchar(100) NOT NULL COMMENT 'Template ID used',
  `template_data` json NOT NULL COMMENT 'Data sent with template',
  `tracking_id` varchar(100) DEFAULT NULL COMMENT 'Tracking ID for the message',
  `message_id` varchar(100) DEFAULT NULL COMMENT 'Zalo message ID if successful',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '0: pending, 1: sent, 2: failed',
  `response_data` text COMMENT 'Full response from Zalo API',
  `error_message` text COMMENT 'Error message if failed',
  `sent_at` timestamp NULL DEFAULT NULL COMMENT 'When message was actually sent',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `zns_logs_phone_created_at_index` (`phone`,`created_at`),
  KEY `zns_logs_status_created_at_index` (`status`,`created_at`),
  KEY `zns_logs_template_id_index` (`template_id`)
);

🔧 Troubleshooting

Common Issues

Token Issues

# Check current token status
php artisan zns:refresh-token

# Clear cache if needed
php artisan config:clear
php artisan cache:clear

Queue Not Processing

# Check queue status
php artisan queue:work --once

# Check failed jobs
php artisan queue:failed

# Retry failed jobs
php artisan queue:retry all

Phone Number Format

The package automatically formats Vietnamese phone numbers:

  • 090123456784901234567
  • +8490123456784901234567

Debug Mode

Enable detailed logging:

ZALO_ZNS_LOG_SUCCESS=true
ZALO_ZNS_LOG_ERRORS=true
LOG_LEVEL=debug

Artisan Commands

# Refresh token if expired
php artisan zns:refresh-token

# Force refresh even if valid
php artisan zns:refresh-token --force

Test Connection

use Tuantrinitri\ZaloLaravel\Services\ZaloZnsService;

$znsService = app(ZaloZnsService::class);
$result = $znsService->testConnection();

if ($result['success']) {
    echo "✅ ZNS connection is working!";
} else {
    echo "❌ Error: " . $result['message'];
}

Job Failures

The package automatically handles failed jobs and logs errors:

// Check logs
tail -f storage/logs/laravel.log | grep ZNS

// Failed jobs table
php artisan queue:failed

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This package is open-sourced software licensed under the MIT license.

Credits

  • Developed by Tuantrinitri Development Team
  • Based on Zalo ZNS API documentation
  • Inspired by Laravel notification patterns

Changelog

v1.0.0

  • Initial release
  • Basic ZNS sending functionality
  • Token management
  • Queue support
  • Artisan commands
  • Comprehensive documentation