# Tiers Controller Documentation **File:** `controllers/admin/tiers.controller.js` **Base URL:** `/api/admin/tiers` **Guards:** `authenticate → requireAdmin() → adminLimiter` --- ## Table of Contents - [Tier Plans](#tier-plans) - [Get All Plans](#get-all-plans) - [Get Single Plan](#get-single-plan) - [Create Plan](#create-plan) - [Update Plan](#update-plan) - [Archive Plan](#archive-plan) - [Bulk Archive Plans](#bulk-archive-plans) - [Restore Plan](#restore-plan) - [Bulk Restore Plans](#bulk-restore-plans) - [Get Plan Field Values](#get-plan-field-values) - [Plan Courses](#plan-courses) - [Get Plan Courses](#get-plan-courses) - [Sync Plan Courses](#sync-plan-courses) - [User Tiers](#user-tiers) - [Get User Tiers](#get-user-tiers) - [Grant Tier](#grant-tier) - [Revoke Tier](#revoke-tier) - [Payments](#payments) - [Get All Payments](#get-all-payments) - [Get Single Payment](#get-single-payment) - [Get Payment Field Values](#get-payment-field-values) --- ## Notes - Pending payments older than **60 minutes** are automatically expired before any payment list/detail call. - Revoking a tier immediately creates a new `free` tier row for the user (auto-downgrade). - `plan_id` is serialized as a string in create responses to avoid BigInt overflow in JS. - A plan's `tier` field cannot be updated — only `label`, `duration_days`, `price`, `currency`, and `is_active`. --- ## Tier Plans ### Get All Plans **`GET /api/admin/tiers`** Returns paginated plans. Pass `?archived=true` to see soft-deleted plans instead. ### Query Parameters | Parameter | Type | Required | Description | |-----------|---------|----------|-------------| | `archived` | boolean | No | `true` to show archived plans only | | `page` | number | No | Default: `1` | | `limit` | number | No | Default: `10` | ### Response `200` ```json { "status": "success", "message": "Plans retrieved.", "data": [...], "pagination": { "page": 1, "limit": 10, "total": 4, "totalPages": 1 } } ``` --- ### Get Single Plan **`GET /api/admin/tiers/:id`** ### Response `200` ```json { "status": "success", "message": "Plan retrieved.", "data": { "plan_id": 1, "tier": "premium", "label": "Premium Monthly", "duration_days": 30, "price": "9.99", "currency": "USD", "is_active": true, "createdAt": "...", "updatedAt": "..." } } ``` --- ### Create Plan **`POST /api/admin/tiers`** ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `tier` | string | **Yes** | `premium` or `exclusive` | | `label` | string | **Yes** | Human-readable plan name | | `duration_days` | number | **Yes** | Access duration in days | | `price` | number | **Yes** | Plan price (decimal) | | `currency` | string | No | ISO 4217. Default: `USD` | ### Response `201` ```json { "status": "success", "message": "Plan created.", "data": { "plan_id": "5", "tier": "premium", ... } } ``` --- ### Update Plan **`PUT /api/admin/tiers/:id`** Only `label`, `duration_days`, `price`, `currency`, and `is_active` are updatable. ### Response `200` ```json { "status": "success", "message": "Plan updated.", "data": { ... } } ``` --- ### Archive Plan **`DELETE /api/admin/tiers/:id`** Sets `is_active = false` then soft-deletes. ### Response `200` ```json { "status": "success", "message": "Plan archived successfully." } ``` --- ### Bulk Archive Plans **`POST /api/admin/tiers/bulk/archive`** ### Request Body ```json { "ids": [1, 2] } ``` ### Response `200` ```json { "status": "success", "message": "2 plan(s) archived successfully.", "archived_ids": [1, 2], "skipped_ids": [] } ``` --- ### Restore Plan **`POST /api/admin/tiers/:id/restore`** Restores plan and sets `is_active = true`. ### Response `200` ```json { "status": "success", "message": "Plan restored successfully." } ``` --- ### Bulk Restore Plans **`POST /api/admin/tiers/bulk/restore`** ### Request Body ```json { "ids": [1, 2] } ``` ### Response `200` ```json { "status": "success", "message": "2 plan(s) restored successfully.", "restored_ids": [1, 2], "skipped_ids": [] } ``` --- ### Get Plan Field Values **`GET /api/admin/tiers/field-values`** Returns distinct filterable field values for tier plans. --- ## Plan Courses ### Get Plan Courses **`GET /api/admin/tiers/:id/courses`** Returns the list of courses linked to this plan. ### Response `200` ```json { "status": "success", "message": "Plan courses retrieved.", "data": [ { "course_id": 1, "title": "Intro to Python", "course_code": "PY-101", "subscription": "premium", "level": "beginner" } ] } ``` --- ### Sync Plan Courses **`POST /api/admin/tiers/:id/courses`** Replaces the full set of courses for this plan. Removes all existing assignments first, then inserts the new set. A course can only belong to one plan at a time — existing assignments to other plans are cleared automatically. ### Request Body ```json { "course_ids": [1, 2, 3] } ``` Pass `[]` to remove all courses from the plan. ### Response `200` ```json { "status": "success", "message": "Plan courses updated." } ``` --- ## User Tiers ### Get User Tiers **`GET /api/admin/tiers/users/:id/tiers`** Returns the full tier history for a user, newest first. Includes `grantedByUser` and `revokedByUser` info. ### Response `200` ```json { "status": "success", "message": "User tiers retrieved.", "data": [ { "tier_id": 3, "user_id": 42, "tier": "premium", "status": "active", "starts_at": "2026-06-01T00:00:00.000Z", "expires_at": "2026-07-01T00:00:00.000Z", "granted_by": 1, "revoked_by": null, "revoked_at": null, "notes": "Trial promotion", "grantedByUser": { "user_id": 1, "email": "admin@example.com" }, "revokedByUser": null } ] } ``` --- ### Grant Tier **`POST /api/admin/tiers/users/tiers/grant`** - Expires all currently active tiers for the user before creating the new one. - `expires_at` is calculated as `now + plan.duration_days`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `user_id` | number | **Yes** | Target user | | `tier` | string | **Yes** | `premium` or `exclusive` | | `plan_id` | number | **Yes** | Must match the plan's tier | | `notes` | string | No | Optional admin note | ### Response `201` ```json { "status": "success", "message": "Tier granted.", "data": { "tier_id": 4, "user_id": 42, "tier": "premium", "status": "active", ... } } ``` ### Error Responses | Status | Message | |--------|---------| | `400` | `user_id, tier, and plan_id are required.` | | `400` | `Plan tier mismatch.` | | `404` | `User not found.` | | `404` | `Plan not found or inactive.` | --- ### Revoke Tier **`PATCH /api/admin/tiers/users/tiers/:tid/revoke`** - Sets the tier's status to `revoked` and records `revoked_by` / `revoked_at`. - Automatically creates a new `free` tier row for the user (auto-downgrade note: `"Auto-downgrade after revoke."`). ### Response `200` ```json { "status": "success", "message": "Tier revoked. User downgraded to free." } ``` ### Error Responses | Status | Message | |--------|---------| | `400` | `Tier is not active.` | | `404` | `Tier record not found.` | --- ## Payments ### Get All Payments **`GET /api/admin/tiers/payments`** Returns paginated payment records. Stale pending payments (>60 min old) are expired before the query runs. ### Query Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | `page` | number | No | Default: `1` | | `limit` | number | No | Default: `10` | | `filters` | array | No | JSON filter array | ### Response `200` ```json { "status": "success", "message": "Payments retrieved.", "data": [ { "payment_id": 1, "user_id": 42, "plan_id": 1, "status": "completed", "amount": "9.99", "currency": "USD", "promo_code": null, "discount": "0.00", "provider": "paypal", "paid_at": "2026-06-01T10:00:00.000Z", "user": { "user_id": 42, "email": "user@example.com" }, "plan": { "plan_id": 1, "label": "Premium Monthly", "tier": "premium", "duration_days": 30 } } ], "pagination": { ... } } ``` --- ### Get Single Payment **`GET /api/admin/tiers/payments/:id`** Returns full payment detail including `user`, `plan`, and `tier` associations. ### Response `200` ```json { "status": "success", "message": "Payment retrieved.", "data": { "payment_id": 1, "provider_payload": { "order_id": "...", "capture_id": "...", ... }, "user": { ... }, "plan": { ... }, "tier": { ... } } } ``` --- ### Get Payment Field Values **`GET /api/admin/tiers/payments/field-values`** Returns distinct filterable field values for payments. `provider_payload` is excluded.