Task 2: - lib/billing/config.ts: reads 4 Razorpay env vars (throws at call time, not boot), premium grant constants (50GB / 6 members / 3 children / ₹199), razorpayAuthHeader() Basic-auth helper - POST /api/admin/seed-plan: admin-only idempotent upsert of the Premium plan row from env + constants (GET shows current plans). Re-runnable, no DB shell Task 3: - lib/billing/entitlements.ts: grantPremium() / revokeToFree() sync onto families.tier + max_members + max_children. Existing quota.ts guards UNCHANGED — they already read these via isPaidFamily(). revoke = limit downgrade only, data untouched (freeze-not-demote) - ENTITLED_STATUSES (active/authenticated/pending=grace) + TERMINAL_STATUSES No guard refactor needed: chosen "sync to families.tier" approach means the 3 existing guards (storage/member/child) keep working as-is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
2 KiB
TypeScript
59 lines
2 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { sql } from "@/db";
|
|
import { requireAdmin } from "@/lib/admin-auth";
|
|
import {
|
|
getRazorpayConfig,
|
|
PREMIUM_PLAN_NAME,
|
|
PREMIUM_PRICE_PAISE,
|
|
PREMIUM_STORAGE_BYTES,
|
|
PREMIUM_MEMBER_LIMIT,
|
|
PREMIUM_CHILD_LIMIT,
|
|
} from "@/lib/billing/config";
|
|
|
|
/**
|
|
* POST /api/admin/seed-plan — idempotent upsert of the Tia Premium plan row.
|
|
*
|
|
* Admin-only. Reads RAZORPAY_PLAN_ID + the premium grant constants and writes
|
|
* one subscription_plans row. Re-runnable: ON CONFLICT updates the grant values
|
|
* so you can tweak storage/member limits and re-seed without a DB shell.
|
|
*
|
|
* GET shows the current plan row(s) for verification.
|
|
*/
|
|
export async function GET(request: Request) {
|
|
const auth = await requireAdmin(request);
|
|
if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
|
|
|
const rows = await sql`SELECT * FROM subscription_plans ORDER BY created_at DESC`;
|
|
return NextResponse.json({ plans: rows });
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const auth = await requireAdmin(request);
|
|
if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status });
|
|
|
|
let cfg;
|
|
try {
|
|
cfg = getRazorpayConfig();
|
|
} catch (e) {
|
|
return NextResponse.json({ error: String(e) }, { status: 500 });
|
|
}
|
|
|
|
const rows = await sql`
|
|
INSERT INTO subscription_plans
|
|
(razorpay_plan_id, name, price_paise, storage_bytes, member_limit, child_limit, is_active)
|
|
VALUES (
|
|
${cfg.planId}, ${PREMIUM_PLAN_NAME}, ${PREMIUM_PRICE_PAISE},
|
|
${PREMIUM_STORAGE_BYTES}, ${PREMIUM_MEMBER_LIMIT}, ${PREMIUM_CHILD_LIMIT}, true
|
|
)
|
|
ON CONFLICT (razorpay_plan_id) DO UPDATE SET
|
|
name = EXCLUDED.name,
|
|
price_paise = EXCLUDED.price_paise,
|
|
storage_bytes = EXCLUDED.storage_bytes,
|
|
member_limit = EXCLUDED.member_limit,
|
|
child_limit = EXCLUDED.child_limit,
|
|
is_active = true
|
|
RETURNING *
|
|
`;
|
|
|
|
return NextResponse.json({ success: true, plan: rows[0] });
|
|
}
|