# HTTP API

تنها نقطه‌ی ورود وب `public/index.php` است. مسیرها در `routes/web.php`.

| مسیر | متد | مصرف‌کننده | احراز |
|---|---|---|---|
| `/webhook/{bot}` | POST | Telegram | هدر `X-Telegram-Bot-Api-Secret-Token` |
| `/payment/callback/{gateway}?token=…` | GET / POST | درگاه پرداخت / مرورگر کاربر | توکن تصادفی پرداخت + Verify از درگاه (+ امضای IPN برای NowPayments) |
| `/health` | GET | مانیتورینگ | عمومی (خلاصه) / `Bearer HEALTH_TOKEN` (جزئیات) |
| `/admin/api/…` | GET / POST | ابزارهای داخلی | `Bearer <توکن مدیر>` + محدودیت IP |

همه‌ی مسیرها به‌جز Webhook برای هر IP محدودیت نرخ دارند (`app.http_rate_per_minute` در برنامه + `limit_req` در Nginx). پاسخ خطا هیچ‌وقت Stack Trace یا پیام PHP ندارد.

## Webhook

```http
POST /webhook/main
X-Telegram-Bot-Api-Secret-Token: <bots.webhook_secret>
Content-Type: application/json

{"update_id": 123, "message": {…}}
```

| پاسخ | معنی |
|---|---|
| `200 ok` | پذیرفته و در صف قرار گرفت — یا تکراری بود و نادیده گرفته شد |
| `400` | بدنه‌ی نامعتبر / بدون `update_id` |
| `403` | Secret نادرست (در لاگ `security` ثبت می‌شود) |
| `404` | ربات ناشناخته یا غیرفعال |
| `413` | بدنه‌ی بزرگ‌تر از ۱ مگابایت |
| `5xx` | Redis/DB در دسترس نیست — تلگرام دوباره ارسال می‌کند |

## Callback پرداخت

- مرورگر (ZarinPal / Payment4): یک صفحه‌ی HTML کوچک با نتیجه و دکمه‌ی بازگشت به ربات.
- سرور به سرور (NowPayments IPN، `POST` با JSON): `{"ok": true}` با کد ۲۰۰؛ درخواستِ بدون امضای معتبر یا توکن ناشناخته ← ۴۰۰.

ارسال دوباره‌ی همان Callback بی‌اثر است.

## Health

```bash
curl https://bot.example.com/health
# {"status":"ok"}

curl -H "Authorization: Bearer $HEALTH_TOKEN" https://bot.example.com/health
# {"status":"ok","services":{"database":{"status":"ok","detail":"3ms"}, …},"time":"…"}
```

کد ۵۰۳ یعنی حداقل یک سرویس `fail` است.

## Admin API (اختیاری)

با `ADMIN_API_ENABLED=true` فعال می‌شود. همان سرویس‌های پنل تلگرام را صدا می‌زند؛ پس **مجوزها، قواعد و Audit Log یکسان** است و IP درخواست‌کننده در Audit ثبت می‌شود.

### احراز هویت

1. پنل ← مدیران ← یک مدیر ← «🔑 توکن API». توکن فقط همان یک‌بار نمایش داده می‌شود و در دیتابیس فقط HMAC آن ذخیره است. صدور توکن جدید، قبلی را باطل می‌کند.
2. در هر درخواست: `Authorization: Bearer <token>`.
3. `ADMIN_API_ALLOWED_IPS` (IP یا CIDR، با ویرگول) را حتماً تنظیم کنید.

| کد | معنی |
|---|---|
| 401 | توکن نامعتبر (بیش از ۱۰ تلاش ناموفق از یک IP در ۱۵ دقیقه ← ۴۲۹) |
| 403 | IP غیرمجاز یا نداشتن Permission (`{"error":"forbidden","permission":"wallet.adjust"}`) |
| 404 | مسیر / موجودیت ناموجود |
| 422 | خطای اعتبارسنجی یا وضعیت (`{"error":"withdrawal.invalid_state","message":"…"}`) |
| 429 | بیش از حد مجاز درخواست |

### Endpointها

فهرست‌ها: `?page=1&per_page=25` (حداکثر ۱۰۰) + فیلترها. پاسخ: `{"data":[…],"meta":{"page":1,"per_page":25,"total":120,"pages":5}}`. ستون‌های حساس (`*_encrypted`، هش‌ها، توکن‌ها) هرگز برگردانده نمی‌شوند.

| متد و مسیر | Permission | توضیح |
|---|---|---|
| `GET /admin/api/dashboard` | `reports.view` | آمار لحظه‌ای |
| `GET /admin/api/reports/{daily\|weekly\|monthly}` | `reports.view` | گزارش دوره |
| `GET /admin/api/search?q=` | حداقل یکی از `*.view` | جستجوی یکپارچه (`123456`, `@user`, `o:15`, `c:7`, `p:31`, `t:900`, `ch:-100…`, TX Hash) |
| `GET /admin/api/users?q=&status=` | `users.view` | |
| `GET /admin/api/users/{id}` | `users.view` (+`wallet.view` برای موجودی) | |
| `POST /admin/api/users/{id}/block` `{reason}` | `users.block` | |
| `POST /admin/api/users/{id}/unblock` | `users.block` | |
| `POST /admin/api/users/{id}/adjust` `{amount, reason}` + هدر `Idempotency-Key` | `wallet.adjust` | `amount` مثبت = افزایش، منفی = کاهش |
| `GET /admin/api/transactions?user_id=&type=&reference_type=&reference_id=` | `wallet.view` | Ledger |
| `GET /admin/api/orders?status=&user_id=` · `GET /admin/api/orders/{id}` | `orders.view` | |
| `POST /admin/api/orders/{id}/status` `{status}` · `POST …/refund` | `orders.edit` | |
| `GET /admin/api/payments?status=&gateway=&tx_hash=` · `GET …/{id}` | `payments.view` | |
| `POST /admin/api/payments/{id}/reverify` | `payments.refund` | |
| `GET /admin/api/withdrawals?status=` · `GET …/{id}` | `withdrawals.view` | `destination` کامل فقط با `withdrawals.approve` |
| `POST /admin/api/withdrawals/{id}/approve` · `…/processing` · `…/complete` `{tx_hash, note}` | `withdrawals.approve` | |
| `POST /admin/api/withdrawals/{id}/reject` `{reason}` | `withdrawals.reject` | |
| `GET /admin/api/campaigns?status=&owner_id=` · `GET …/{id}` | `campaigns.view` | |
| `POST /admin/api/campaigns/{id}/approve` · `…/reject` `{reason}` | `campaigns.approve` | |
| `POST /admin/api/campaigns/{id}/pause` · `resume` · `cancel` · `complete` | `campaigns.edit` | |
| `GET /admin/api/channels` · `GET /admin/api/links?campaign_id=` | `channels.view` / `campaigns.view` | |
| `GET /admin/api/risks?all=1` | `risk.view` | |
| `GET /admin/api/export/0/{users\|orders\|transactions\|withdrawals\|campaigns}?format=csv\|xls` | `reports.export` | دانلود فایل |
| `GET /admin/api/settings` · `POST /admin/api/settings` `{key, value}` | `settings.edit` | |
| `GET /admin/api/audit?admin_id=&action=&target_type=&target_id=` | `logs.view` | |

عملیات تغییردهنده فقط با `POST` پذیرفته می‌شوند. بدنه می‌تواند JSON یا Form باشد.

### نمونه

```bash
curl -s -H "Authorization: Bearer $TOKEN" "https://bot.example.com/admin/api/withdrawals?status=pending"

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
     -d '{"tx_hash":"TRACK-99812","note":"پرداخت از بانک ملت"}' \
     https://bot.example.com/admin/api/withdrawals/42/complete

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: fix-2026-10-03-a" \
     -H "Content-Type: application/json" -d '{"amount":50000,"reason":"جبران خطای پرداخت"}' \
     https://bot.example.com/admin/api/users/17/adjust
```
