feat: Add free/exempt subscription status for organizations

- Add 'free' subscription status (exempt from fees)
- POST /admin/organizations/:id/set-free - Set org as free
- POST /admin/organizations/:id/remove-free - Remove free status
- Free orgs get enterprise plan, no expiry
This commit is contained in:
2026-03-13 06:01:29 +00:00
parent 40adeb15ee
commit 4fda22ecf0
2 changed files with 48 additions and 0 deletions

View File

@@ -38,6 +38,11 @@ export async function subscriptionMiddleware(ctx: Context, next: Next): Promise<
let message = "";
switch (org.subscription_status) {
case "free":
// Exempt from fees - always active
isActive = true;
break;
case "active":
// Check if subscription has ended
if (org.subscription_ends_at && new Date(org.subscription_ends_at) < now) {

View File

@@ -369,6 +369,49 @@ adminRouter.post("/organizations/:id/extend-trial", requireSuperAdmin, async (ct
ctx.response.body = { message: `Trial um ${extendDays} Tage verlängert` };
});
// Set organization as free (exempt from fees)
adminRouter.post("/organizations/:id/set-free", requireSuperAdmin, async (ctx) => {
const orgId = ctx.params.id;
const body = await ctx.request.body.json();
const { reason } = body;
await execute(
`UPDATE organizations SET
subscription_status = 'free',
subscription_plan = 'enterprise',
subscription_ends_at = NULL,
trial_ends_at = NULL,
subscription_paused_at = NULL,
subscription_pause_reason = NULL,
settings = settings || $1::jsonb
WHERE id = $2`,
[JSON.stringify({ free_reason: reason || "Freigestellt durch Admin", free_since: new Date().toISOString() }), orgId]
);
ctx.response.body = { message: "Organisation freigestellt (kostenlos)" };
});
// Remove free status (back to trial or active)
adminRouter.post("/organizations/:id/remove-free", requireSuperAdmin, async (ctx) => {
const orgId = ctx.params.id;
const body = await ctx.request.body.json();
const { set_trial_days } = body;
const trialDays = set_trial_days || 14;
await execute(
`UPDATE organizations SET
subscription_status = 'trial',
subscription_plan = 'starter',
trial_ends_at = NOW() + INTERVAL '${trialDays} days',
settings = settings - 'free_reason' - 'free_since'
WHERE id = $1`,
[orgId]
);
ctx.response.body = { message: `Freistellung aufgehoben, ${trialDays} Tage Trial gesetzt` };
});
// List all organizations with subscription info
adminRouter.get("/subscriptions", requireSuperAdmin, async (ctx) => {
const filter = ctx.request.url.searchParams.get("filter"); // all, trial, active, paused, expired