#Plans
A plan is what can be bought. It is not a pricing card. It's the definition behind a relationship:
UserOrganizationSubscriptionPlanPlan limitsPlan featuresActual usage
#What V7 sells (reference only, do not hard-code)
These values show the shape the V8 API must return. They are V7 code constants, not V8 decisions.
| Concept | V7 values |
|---|---|
| Tiers | Free, Newbie, Pro, Master, Business (+ Legacy). Restructured new users only see Newbie (shown as "Managed") and Business (shown as "Self Managed") |
| Cycles | Monthly, Yearly (charged as 10 months), Lifetime |
| Plan types | server_based (old: one subscription per server), subscription_based (current: one account subscription), Lifetime |
| Limits | allowed_servers, allowed_applications per plan (e.g. Free 0 servers / 1 app, others 200 apps) |
| Features | Permission rows enabled per tier (<tier>_default = 1) |
| Trial | trial_mode + trial_days from settings. Newbie/Free need no payment |
| Discounts | Recurring and lifetime discounts per tier (env config), promo codes, discount-percentage endpoint |
| Rate limits | API rate limit per tier (free 60 … business 180 req/min) |
#What the plans screen needs from the API
| Data | Why | Status |
|---|---|---|
| Plan catalog: id, name, display name, description, cycles + price per cycle, currency, limits, feature list, trial availability, is-current, can-select | Render plans without hard-coding | Missing |
| Current subscription: plan, cycle, status, renews/expires at, cancel-at-period-end | Current-plan card | Missing |
| Usage: servers used / allowed, applications used / allowed | Usage bars, limit warnings | Missing (V7 new-subscription-plan returns serverCount, applicationCount) |
| Upgrade quote: amount now, credit applied, tax, promo, next renewal | Checkout summary | Missing (V7 remaining-credit) |
| Eligibility: which plans this account may choose (e.g. restructured tiers) | Disable or hide options | Missing |
#Plan screens
- Plans (
/[locale]/plans): cycle toggle (only cycles the API offers), plan cards built only from API data, the current plan marked, upgrade/downgrade buttons enabled by the API'scan_select, feature comparison table. - Current plan card (dashboard + subscription page): name, cycle, status badge, renewal date, usage bars, "Upgrade" call to action when usage ≥ 80% or a limit is hit.
- Enterprise / contact: V7 has
enterprise-contact-request. Optional. Open question
#Upgrade, downgrade, renewal, cancellation, expiration
| Action | V7 behaviour | V8 |
|---|---|---|
| Upgrade | PATCH change-subscription with plan + cycle. Requires enough credits (shortfall > $1 → "Your credit is not enough"). Charges the difference | Missing |
| Downgrade | Same endpoint. Validity recalculated from remaining credit (remainingPlanCredit). Can't go below current usage Assumption | Missing Open question |
| Renewal | PATCH renew-subscription, paid from credits. If expired 16+ days, the new period starts today. Otherwise the remaining days are added | Missing |
| Cancel | PATCH cancel-subscription sets a cancel flag (subscription_plan_cancel). Lifetime can't be cancelled. resume-subscription undoes it | Missing |
| Expiration | Daily job decreases expires_in_days. Reminders when < 8 days left. Expired (< 0) → server actions blocked except server delete | Missing |
| Free plan | 0 servers / 1 application in the new standard table. A free-forever single server exists for some old users | Open question |
| Lifetime | Separate controller: create, upgrade, advanced lifetime | Open question keep for V8? |
#Usage limits
Action that adds usage (add server, create VPS, create app)BE: count usage in organization/accountCompare with plan limitWithin → allow | Over → 403/422 with code PLAN_LIMIT_REACHED + upgrade info
- The backend counts and decides. The frontend only shows the numbers and disables buttons as a hint.
- Applications live on OSS panels, so counting applications needs a call to every server (or a cached count). This is a real cost. Decision D-6: do application limits still apply in V8?
#Rules for the frontend
- Never hard-code prices, limits, features, cycles or currency. Every number comes from the API.
- Format money with next-intl
useFormatter().number(value, {style:'currency', currency})using the API's currency. - Show plan names from the API (
display_name) to avoid the V7 rename confusion (Newbie → "Managed").