8.8 KiB
Tiers Controller Documentation
File: controllers/admin/tiers.controller.js
Base URL: /api/admin/tiers
Guards: authenticate → requireAdmin() → adminLimiter
Table of Contents
Notes
- Pending payments older than 60 minutes are automatically expired before any payment list/detail call.
- Revoking a tier immediately creates a new
freetier row for the user (auto-downgrade). plan_idis serialized as a string in create responses to avoid BigInt overflow in JS.- A plan's
tierfield cannot be updated — onlylabel,duration_days,price,currency, andis_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
{
"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
{
"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
{
"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
{ "status": "success", "message": "Plan updated.", "data": { ... } }
Archive Plan
DELETE /api/admin/tiers/:id
Sets is_active = false then soft-deletes.
Response 200
{ "status": "success", "message": "Plan archived successfully." }
Bulk Archive Plans
POST /api/admin/tiers/bulk/archive
Request Body
{ "ids": [1, 2] }
Response 200
{
"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
{ "status": "success", "message": "Plan restored successfully." }
Bulk Restore Plans
POST /api/admin/tiers/bulk/restore
Request Body
{ "ids": [1, 2] }
Response 200
{
"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
{
"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
{ "course_ids": [1, 2, 3] }
Pass [] to remove all courses from the plan.
Response 200
{ "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
{
"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_atis calculated asnow + 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
{
"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
revokedand recordsrevoked_by/revoked_at. - Automatically creates a new
freetier row for the user (auto-downgrade note:"Auto-downgrade after revoke.").
Response 200
{ "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
{
"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
{
"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.