# Traffic Pool Manager — مستندات کامل سیستم

> نسخه ۲.۰ | معماری جدید | آخرین بروزرسانی: ۱۴۰۵

---

## فهرست مطالب

1. [دیدکلی — سیستم چگونه کار می‌کند](#۱-دیدکلی)
2. [ساختار دیتابیس](#۲-ساختار-دیتابیس)
3. [جریان خرید ترافیک](#۳-خرید-ترافیک--شارژ-کیف-پول)
4. [جریان اعمال ترافیک روی VPS](#۴-اعمال-ترافیک-روی-vps)
5. [ریست دوره‌ای](#۵-ریست-دورهای-monthly-reset)
6. [Auto Top-up (تاپ‌آپ خودکار)](#۶-auto-top-up)
7. [کران‌جاب‌ها](#۷-کران‌جاب‌ها)
8. [پنل ادمین](#۸-پنل-ادمین)
9. [پنل کلاینت](#۹-پنل-کلاینت)
10. [اطلاعات فنی — کدهای خطا](#۱۰-کدهای-خطا)
11. [راهنمای راه‌اندازی اولیه](#۱۱-راهاندازی-اولیه)

---

## ۱. دیدکلی

### مفهوم اصلی: **Pool (پول)**

هر کاربر یک **کیف‌پول ترافیک (Pool Balance)** دارد.
این کیف‌پول در واحد **گیگابایت (GB)** شارژ می‌شود.

```
کاربر → خرید پکیج ترافیک → فاکتور پرداخت → کیف‌پول شارژ می‌شود
کاربر → درخواست اضافه ترافیک روی VPS → از کیف‌پول کسر می‌شود → VZ API فراخوانی می‌شود
```

### چرخه زندگی یک سرویس VPS در این سیستم

```
سرویس خریده می‌شود
        │
        ▼
[mod_tpm_service_state] ساخته می‌شود
  base_gb  = ظرفیت پایه پلن (مثلاً 1000GB)
  extra_gb = 0
  vz_limit_gb = null (هنوز ست نشده)
        │
        ▼
کاربر ترافیک اضافه می‌خرد
(فاکتور پرداخت می‌کند)
        │
        ▼
کیف‌پول شارژ می‌شود: balance_gb += X GB
        │
        ▼
کاربر «اضافه ترافیک» می‌زند روی VPS
        │
        ▼
balance_gb -= X GB
extra_gb   += X GB
VZ API: setLimitGb(base_gb + extra_gb)
        │
        ▼
آخر ماه (روز تولد VPS) → ریست دوره:
  extra_gb   = 0
  VZ API: setLimitGb(base_gb)
  VZ API: resetBandwidth()
```

---

## ۲. ساختار دیتابیس

### ۷ جدول سیستم

#### `mod_tpm_pools` — تعریف پول‌ها
```sql
id          INT  -- شناسه پول
name        VARCHAR -- نام (مثلاً "پول ایران")
created_at  INT  -- timestamp ساخت
```
> هر پول می‌تواند به چند محصول WHMCS لینک شود.
> لینک از طریق `configoption5` محصول (شناسه عددی پول) تعریف می‌شود.

---

#### `mod_tpm_balances` — کیف‌پول کاربران
```sql
client_id   INT  -- شناسه کاربر WHMCS (کلید اصلی ترکیبی)
pool_id     INT  -- شناسه پول (کلید اصلی ترکیبی)
balance_gb  DECIMAL(12,3) -- موجودی فعلی به گیگابایت
updated_at  INT  -- آخرین زمان تغییر
```
> یک ردیف برای هر کاربر × هر پول.
> موجودی هرگز منفی نمی‌شود (سیستم قبل از کسر چک می‌کند).

---

#### `mod_tpm_packages` — پکیج‌های قابل فروش
```sql
id          INT
pool_id     INT    -- این پکیج به کدام پول تعلق دارد
name        VARCHAR -- نام نمایشی
amount_gb   INT    -- چند GB اضافه می‌کند به کیف‌پول
price       DECIMAL -- قیمت به ریال
taxed       TINYINT -- آیا مشمول مالیات است؟ (0/1)
active      TINYINT -- فعال/غیرفعال
created_at  INT
```
> وقتی کاربر این پکیج را می‌خرد و فاکتور پرداخت می‌شود،
> `amount_gb` به `mod_tpm_balances.balance_gb` اضافه می‌شود.

---

#### `mod_tpm_steps` — مراحل اضافه کردن ترافیک
```sql
id         INT
name       VARCHAR  -- نام نمایشی (مثلاً "100 گیگ")
amount_gb  INT      -- چند GB هر بار اضافه شود
```
> Step = واحد اضافه کردن ترافیک.
> مثال: Step "100 گیگ" → کاربر این را انتخاب می‌کند و 100GB از کیف‌پولش کسر شده به VPS اضافه می‌شود.

---

#### `mod_tpm_service_state` ← **مهم‌ترین جدول**
```sql
service_id      INT PRIMARY KEY  -- شناسه سرویس WHMCS
client_id       INT
pool_id         INT              -- به کدام پول وصل است
base_gb         INT              -- ظرفیت پایه پلن (از configurable option)
extra_gb        DECIMAL(12,3)    -- ترافیک اضافه‌شده در این دوره
period_day      TINYINT          -- روز ماه برای ریست (از تاریخ ساخت VPS)
last_reset_ts   INT              -- آخرین باری که ریست شد
vz_limit_gb     INT              -- آخرین لیمیت ست‌شده روی VZ (از ما)
auto_enabled    TINYINT(1)       -- آیا تاپ‌آپ خودکار فعال است؟
auto_step_id    INT              -- کدام step برای auto top-up
auto_threshold  TINYINT          -- آستانه مصرف برای تاپ‌آپ (پیش‌فرض: 90%)
updated_at      INT
```

> **این جدول حافظه سیستم است.**
>
> - `base_gb + extra_gb` = لیمیت فعلی که باید روی VZ باشد
> - `period_day` کش است — فقط یک بار از API می‌خواهیم
> - `vz_limit_gb` به ما می‌گوید آخرین باری که ما ست کردیم چقدر بود

---

#### `mod_tpm_ledger` — دفتر کل تمام رویدادها
```sql
id             BIGINT AUTO_INCREMENT
trace_id       VARCHAR(36)  -- UUID هر عملیات (برای ردیابی)
type           ENUM         -- نوع: credit | apply | reset | auto_apply | refund
client_id      INT
service_id     INT NULL     -- برای credit می‌تواند null باشد
pool_id        INT
gb             DECIMAL      -- مثبت = واریز | منفی = برداشت
balance_before DECIMAL      -- موجودی قبل از عملیات
balance_after  DECIMAL      -- موجودی بعد از عملیات
vz_before      INT NULL     -- لیمیت VZ قبل
vz_after       INT NULL     -- لیمیت VZ بعد
status         ENUM         -- ok | fail
error_code     VARCHAR      -- کد خطا در صورت شکست
meta           JSON         -- اطلاعات اضافی (invoice_id، step_id، ...)
ip             VARBINARY(16)
created_at     DATETIME
```

> همه رویدادها اینجا ثبت می‌شوند:
> خرید، اعمال، ریست، خودکار، بازگشت وجه.

---

#### `mod_tpm_settings` — تنظیمات
```sql
scope      ENUM(global, server_group)
ref_id     INT NULL       -- شناسه server group (اگر scope=server_group)
namespace  VARCHAR        -- گروه‌بندی تنظیمات (مثلاً 'proxy', 'telegram')
skey       VARCHAR        -- کلید
svalue     LONGTEXT       -- مقدار
```

---

## ۳. خرید ترافیک / شارژ کیف‌پول

### جریان کامل

```
کاربر → صفحه buyPackage → انتخاب پکیج → کلیک "Order Now"
                                │
                                ▼
              WHMCS فاکتور ساخته می‌شود:
              - tblinvoices: فاکتور اصلی
              - tblinvoiceitems:
                  type  = 'TPM'
                  relid = package_id (شناسه پکیج از mod_tpm_packages)
                                │
                کاربر فاکتور را پرداخت می‌کند
                                │
                                ▼
              Hook: InvoicePaid فیر می‌شود
                                │
                                ▼
              TPM::credit() فراخوانی می‌شود:
              1. mod_tpm_balances را با lockForUpdate قفل می‌کند
              2. balance_gb += amount_gb
              3. در mod_tpm_ledger ثبت می‌کند (type='credit')
                                │
                                ▼
              موجودی کاربر آپدیت شد ✓
```

### نکته مهم: Idempotency (یک‌بارگی)

قبل از کردیت، سیستم چک می‌کند آیا این فاکتور قبلاً کردیت شده:
```sql
SELECT * FROM mod_tpm_ledger
WHERE type='credit' AND status='ok'
AND JSON_EXTRACT(meta, '$.invoice_id') = ?
```
اگر وجود داشت، عملیات تکرار نمی‌شود — برای جلوگیری از دوبله شارژ.

---

## ۴. اعمال ترافیک روی VPS

### جریان کامل — همزمان (Synchronous)

کاربر روی تب "ترافیک" → انتخاب step → کلیک "Add Traffic"

```
POST به trafficTab handler
        │
        ▼
TPM::apply(clientId, serviceId, gb, 'apply', stepId)
        │
        ├─ ① اعتبارسنجی ورودی‌ها (gb > 0)
        │
        ├─ ② دریافت اطلاعات سرور:
        │    - getServerDetailsByServiceId() → IP, user, pass سرور Virtualizor
        │    - getVpsIdCustomFieldValues()   → vpsId (شناسه VPS در VZ)
        │    - getProxyForServer()           → تنظیمات پروکسی (اگر داشت)
        │
        ├─ ③ ensureState() → ردیف mod_tpm_service_state گرفته/ساخته می‌شود
        │
        ├─ ④ تراکنش دیتابیس (با lockForUpdate):
        │    - mod_tpm_balances.balance_gb -= gb    ← کسر از کیف‌پول
        │    - mod_tpm_service_state.extra_gb += gb  ← ثبت ترافیک اضافه‌شده
        │    اگر موجودی کافی نبود → INSUFFICIENT_BALANCE
        │
        ├─ ⑤ محاسبه لیمیت جدید:
        │    new_limit = base_gb + extra_gb (بعد از آپدیت)
        │
        ├─ ⑥ فراخوانی Virtualizor API:
        │    setLimitGb(vpsId, new_limit)
        │
        ├─ اگر API موفق بود:
        │    ├─ mod_tpm_service_state.vz_limit_gb = new_limit
        │    ├─ mod_tpm_ledger ثبت (type='apply', status='ok')
        │    └─ پیام Telegram ارسال می‌شود
        │
        └─ اگر API شکست خورد (Compensating Rollback):
             ├─ mod_tpm_balances.balance_gb += gb     ← برگشت به کیف‌پول
             ├─ mod_tpm_service_state.extra_gb -= gb  ← برگشت extra_gb
             ├─ mod_tpm_ledger ثبت (type='apply', status='fail', error='VZ_API_FAIL')
             └─ کاربر پیام خطا می‌بیند | پول کاربر برگشت خورد
```

### چرا "Compensating Rollback"؟

دیتابیس و API خارجی نمی‌توانند در یک تراکنش باشند.
راه‌حل:
1. DB را commit کن
2. API را صدا بزن
3. اگر API شکست خورد → DB را **دستی** برگردان (compensating transaction)

این pattern رایج‌ترین روش مدیریت تراکنش‌های توزیع‌شده است.

---

## ۵. ریست دوره‌ای (Monthly Reset)

### مفهوم

هر VPS یک **روز ماه (period_day)** دارد که برابر روز ساخت آن VPS است.
مثال: VPS در روز ۱۵ ماه ساخته شده → هر ماه روز ۱۵ ریست می‌شود.

### جریان کران‌جاب `tpm-reset.php`

```
هر روز ساعت ۰۰:۰۵ اجرا می‌شود
        │
        ▼
امروز چند‌شنبه است؟ → todayDay = day(now)
        │
        ├─ Step 1: سرویس‌هایی که period_day = todayDay دارند
        │    AND (last_reset_ts IS NULL OR last_reset_ts < امروز ۰۰:۰۰)
        │    → اینها باید ریست شوند
        │
        ├─ Step 2: سرویس‌هایی که period_day = NULL دارند
        │    → برای هر کدام از VZ API می‌پرسیم VPS کی ساخته شده
        │    → period_day محاسبه و در DB کش می‌شود
        │    → اگر امروز روز ریستشان بود → اضافه به لیست ریست
        │
        └─ Step 3: برای هر سرویس در لیست:
             TPM::resetPeriod(serviceId)
                    │
                    ├─ VZ API: setLimitGb(vpsId, base_gb)
                    ├─ VZ API: resetBandwidth(vpsId)
                    ├─ DB: extra_gb = 0
                    ├─ DB: last_reset_ts = now()
                    ├─ DB: vz_limit_gb = base_gb
                    └─ mod_tpm_ledger ثبت (type='reset')
```

### مثال عملی

```
VPS کاربر در روز ۱۰ ماه ساخته شده.
کاربر در طول ماه 200GB اضافه کرده:
  base_gb  = 1000
  extra_gb = 200
  vz_limit = 1200

روز ۱۰ ماه بعد:
  extra_gb  → 0
  vz_limit  → 1000  (فقط base_gb)
  VZ: setLimitGb(vpsId, 1000)
  VZ: resetBandwidth(vpsId)  ← کانتر مصرف صفر می‌شود
```

> **موجودی کیف‌پول ریست نمی‌شود!**
> فقط `extra_gb` (ترافیک اعمال‌شده این دوره) صفر می‌شود.

---

## ۶. Auto Top-up

### مفهوم

کاربر تنظیم می‌کند: «وقتی مصرف VPS به X% رسید، خودکار Y GB اضافه کن.»

### پیکربندی (در تب ترافیک)

```
Package انتخاب می‌شود (step)
Threshold درصد مصرف انتخاب می‌شود (پیش‌فرض: 90%)
→ Enable Automatic Traffic
```

این تنظیمات در `mod_tpm_service_state` ذخیره می‌شود:
```
auto_enabled   = 1
auto_step_id   = ID استپ انتخابی
auto_threshold = 90
```

### جریان کران‌جاب `tpm-auto-topup.php`

```
هر ۱۰ دقیقه یک بار اجرا می‌شود
        │
        ▼
سرویس‌هایی که auto_enabled=1 دارند را بگیر
        │
        └─ برای هر سرویس:
             TPM::checkAutoTopup(serviceId)
                    │
                    ├─ VZ API: bandwidthData(vpsId)
                    │    → used_gb = مصرف فعلی
                    │    → limit_gb = لیمیت فعلی
                    │
                    ├─ چک: used_gb >= limit_gb × (threshold/100)؟
                    │
                    ├─ اگر بله → TPM::apply(..., 'auto_apply', stepId)
                    │    ← همان جریان کامل apply اجرا می‌شود
                    │
                    └─ اگر خیر → هیچ کاری نمی‌کند (skip)
```

### مثال

```
VPS با limit=1000GB
کاربر تنظیم کرده: threshold=90%, step=100GB

کران‌جاب چک می‌کند:
  used_gb = 912 GB
  limit_gb = 1000 GB
  912 >= 1000 × 0.9 = 900 ✓

→ TPM::apply(100GB)
  balance_gb -= 100
  extra_gb   += 100
  VZ: setLimitGb(vpsId, 1100)
```

> اگر موجودی کافی نباشد (`INSUFFICIENT_BALANCE`)،
> کران‌جاب شکست را لاگ می‌کند و continue می‌دهد.

---

## ۷. کران‌جاب‌ها

### فایل‌ها

| فایل | زمان اجرا | کار |
|---|---|---|
| `crons/tpm-reset.php` | هر روز `05 0 * * *` | ریست دوره ماهانه |
| `crons/tpm-auto-topup.php` | هر ۱۰ دقیقه `*/10 * * * *` | بررسی و اعمال تاپ‌آپ خودکار |

### نصب کران‌جاب

```bash
# ویرایش crontab
crontab -e

# اضافه کن:
5 0 * * * php /var/www/whmcs/modules/addons/trafficpoolmanager/crons/tpm-reset.php >> /var/log/tpm-reset.log 2>&1
*/10 * * * * php /var/www/whmcs/modules/addons/trafficpoolmanager/crons/tpm-auto-topup.php >> /var/log/tpm-topup.log 2>&1
```

> مسیر را با مسیر واقعی WHMCS خود جایگزین کن.

### محدودیت CLI

هر دو کران‌جاب از `php_sapi_name() !== 'cli'` محافظت شده‌اند.
اگر از مرورگر فراخوانی شوند → `403 Forbidden` برمی‌گردند.

---

## ۸. پنل ادمین

### دسترسی

```
WHMCS Admin → Addons → Traffic Pool Manager
```

### صفحات ادمین

#### Pools Management
- ساخت پول جدید با نام
- حذف پول
- مشاهده محصولاتی که به هر پول لینک هستند

#### Step Traffic Packages
- ساخت Step: نام + مقدار GB
- ویرایش / حذف
- مثال: "10 گیگ" → 10GB

#### Traffic Package Sell
- ساخت پکیج قابل فروش: پول + مقدار GB + قیمت + مالیات
- ویرایش / حذف
- این پکیج‌ها در صفحه `buyPackage` کلاینت نمایش داده می‌شوند

#### User Pool Management
- مشاهده همه کاربران و موجودی کیف‌پولشان

#### Logs Management
- مشاهده `mod_tpm_ledger`
- فیلتر: کاربر، سرویس، پول، نوع رویداد، تاریخ

#### Health Check
- بررسی اتصال به سرورهای Virtualizor

#### Settings
- تنظیمات Telegram (برای اعلان‌های پرداخت و خطا)
- تنظیمات پروکسی

---

## ۹. پنل کلاینت

### صفحه اصلی ماژول

```
clientarea.php?m=trafficpoolmanager
```

- نمایش همه پول‌های کاربر با موجودی
- آمار: تعداد خریدها، مجموع مصرف

### صفحه خرید ترافیک

```
clientarea.php?m=trafficpoolmanager&page=buyPackage&poolid=X
```

- نمایش پکیج‌های فعال پول انتخابی
- کلیک "Order Now" → فاکتور WHMCS ساخته می‌شود

### صفحه مدیریت محصول

```
clientarea.php?m=trafficpoolmanager&page=productManager&poolid=X
```

- لیست سرویس‌های Active/Suspended کاربر در این پول
- اضافه کردن ترافیک (step انتخاب کن → Add Traffic)
- فعال/غیرفعال کردن auto top-up

### تب ترافیک (Embedded Tab)

```
clientarea.php?action=productdetails&id=SERVICE_ID
تب "ترافیک"
```

این تب مستقیماً در صفحه جزئیات سرویس VPS قرار دارد.
محتوای آن از این endpoint بارگذاری می‌شود:

```
index.php?m=trafficpoolmanager&page=trafficTab&service_id=X
```

**بار اول کلیک:** AJAX Request → HTML پنل برگشت داده می‌شود
**بار دوم کلیک:** از حافظه نمایش می‌دهد (lazy loaded)

محتوای تب ترافیک:
- **کارت موجودی:** اسم پول + موجودی GB + دکمه خرید ترافیک
- **وضعیت VZ:** Base GB / Extra این دوره / لیمیت فعلی VZ
- **فرم Apply:** انتخاب step + دکمه "Add Traffic" (فوری، بدون صف)
- **Auto Top-up:** تنظیم پکیج + آستانه درصد / فعال‌سازی/غیرفعال‌سازی
- **تاریخچه اخیر:** آخرین ۱۰ رویداد از `mod_tpm_ledger`

---

## ۱۰. کدهای خطا

| کد | معنی | راه‌حل |
|---|---|---|
| `INSUFFICIENT_BALANCE` | موجودی کیف‌پول کافی نیست | کاربر باید ترافیک بخرد |
| `SERVICE_DATA_MISSING` | سرور VPS یا vpsId پیدا نشد | Custom Field سرویس را بررسی کن |
| `VZ_API_FAIL` | API ویرتوالایزر پاسخ نداد | IP/User/Pass سرور را بررسی کن |
| `STATE_ERROR` | mod_tpm_service_state ساخته نشد | pool_id را در configoption5 محصول بررسی کن |
| `INVALID_AMOUNT` | مقدار GB منفی یا صفر | step تعریف‌شده را بررسی کن |
| `DB_ERROR` | خطای دیتابیس | لاگ WHMCS را بررسی کن |
| `ROLLBACK_FAILED` | **بحرانی:** rollback هم شکست خورد | فوری دستی تصحیح کن + لاگ را ببین |
| `AUTO_NOT_CONFIGURED` | auto top-up پیکربندی ندارد | نرمال است — سرویس skip می‌شود |

---

## ۱۱. راه‌اندازی اولیه

### قدم ۱: ساخت جداول

از ادمین WHMCS: **Setup → Addon Modules → Traffic Pool Manager → Activate**

یا مستقیم SQL (مراجعه به `trafficpoolmanager_activate()` در `trafficpoolmanager.php`).

### قدم ۲: ساخت پول

```sql
INSERT INTO mod_tpm_pools (name, created_at) VALUES ('پول ایران', UNIX_TIMESTAMP());
-- شناسه پول: 1
```

### قدم ۳: ساخت step ها

```sql
INSERT INTO mod_tpm_steps (name, amount_gb) VALUES
('10 گیگ', 10),
('50 گیگ', 50),
('100 گیگ', 100),
('200 گیگ', 200);
```

یا از پنل ادمین: **Step Traffic Packages → Add Step**

### قدم ۴: ساخت پکیج‌های فروشی

از پنل ادمین: **Traffic Package Sell → Create Package**
- Pool: پول ایران
- Amount: مثلاً 100GB
- Price: قیمت به ریال

### قدم ۵: لینک کردن محصول WHMCS به پول

1. ادمین WHMCS → **Products/Services → [محصول VPS]**
2. تب **Other**
3. فیلد **Config Option 5** = شناسه پول (مثلاً `1`)

> این کار می‌گوید سرویس‌های خریداری‌شده با این محصول به پول ۱ متصل هستند.

### قدم ۶: نصب کران‌جاب‌ها

```bash
crontab -e

# ریست ماهانه — هر روز ساعت ۰۰:۰۵
5 0 * * * php /var/www/html/billing/modules/addons/trafficpoolmanager/crons/tpm-reset.php

# تاپ‌آپ خودکار — هر ۱۰ دقیقه
*/10 * * * * php /var/www/html/billing/modules/addons/trafficpoolmanager/crons/tpm-auto-topup.php
```

### قدم ۷: تست

1. یک کاربر تست بساز
2. یک سرویس VPS با محصول لینک‌شده به پول بساز
3. از ادمین مستقیم `mod_tpm_balances` را دستی شارژ کن:
   ```sql
   INSERT INTO mod_tpm_balances (client_id, pool_id, balance_gb, updated_at)
   VALUES (CLIENT_ID, 1, 500, UNIX_TIMESTAMP());
   ```
4. به صفحه جزئیات سرویس کلاینت برو
5. تب "ترافیک" باید ظاهر شود
6. یک step را اعمال کن → باید فوری تغییر لیمیت روی VZ ببینی

---

## معماری کلی — نمودار

```
┌─────────────────────────────────────────────────────────────┐
│                         WHMCS CLIENT AREA                    │
│                                                              │
│  صفحه اصلی ماژول        تب ترافیک (در جزئیات سرویس)         │
│  (buyPackage, dashboard)  (trafficTab — lazy AJAX)           │
└──────────────┬─────────────────────────┬────────────────────┘
               │                         │
               ▼                         ▼
┌──────────────────────────────────────────────────────────────┐
│               trafficpoolmanager.php (Router + Pages)        │
│               TPM.php (Core Business Logic)                  │
└──────┬──────────────────────────────────┬────────────────────┘
       │                                  │
       ▼                                  ▼
┌─────────────┐                  ┌────────────────────┐
│  MySQL DB   │                  │  Virtualizor API   │
│             │                  │  (port 4083/4085)  │
│ mod_tpm_pools   │◄─────────────────┤  setLimitGb()      │
│ mod_tpm_balances│                  │  bandwidthData()   │
│ tpm_service │                  │  resetBandwidth()  │
│ mod_tpm_ledger  │                  │  getVPSInfo()      │
│ ...         │                  └────────────────────┘
└─────────────┘

       ┌──────────────────────┐
       │     CRON JOBS        │
       │                      │
       │  tpm-reset.php       │  ← روزانه ۰۰:۰۵
       │  tpm-auto-topup.php  │  ← هر ۱۰ دقیقه
       └──────────────────────┘

       ┌──────────────────────┐
       │     HOOKS            │
       │                      │
       │  InvoicePaid         │  ← TPM::credit()
       │  FooterOutput        │  ← تب ترافیک (سایر تم‌ها)
       └──────────────────────┘
```

---

*سند آخرین بار توسط Claude Code به‌روز شده است.*
