feat(sase): plan change + subscription cancel/resume — Phase D
Completes the billing surface; pairs with sase.tr#29.
Admin SDK
- changePlan(input) + cancelSubscriptionById(input) + resumeSubscription(input).
- PlanChangeResult + SubscriptionStateChange types exported.
- SASE_ADMIN_ENDPOINTS lists the three new spoke routes.
Route
- /api/sase/subscriptions/[subId] now accepts change-plan/cancel/resume
in addition to trial-extend/activate. newPlanId required when
action=change-plan. Reason ≥ 5 chars enforced. Audit captures the
action + days + newPlanId + reasonLen on both paths.
Repo
- getUser() also returns currentPlanId for the plan picker.
UI
- BillingActions extended:
active/trial → [Plan değiştir][İptal et]
cancelled → [Devam ettir]
- Change-plan modal lists active plans (current excluded) with brand
count hint. The spoke flags brand-reassignment via response field;
v1 surfaces only the confirmation.
- Cancel uses destructive variant + clear messaging.
Phase D ships the user-management mutation surface — A/B/C/D all live.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import { writeAudit } from "@/lib/audit";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const ALLOWED = new Set(["trial-extend", "activate"]);
|
||||
const ALLOWED = new Set(["trial-extend", "activate", "change-plan", "cancel", "resume"]);
|
||||
|
||||
export async function POST(
|
||||
req: Request,
|
||||
@@ -25,6 +25,7 @@ export async function POST(
|
||||
action?: string;
|
||||
days?: number;
|
||||
reason?: string;
|
||||
newPlanId?: string;
|
||||
};
|
||||
if (!body.action || !ALLOWED.has(body.action)) {
|
||||
return NextResponse.json({ ok: false, error: "invalid_action" }, { status: 400 });
|
||||
@@ -38,6 +39,9 @@ export async function POST(
|
||||
return NextResponse.json({ ok: false, error: "invalid_days" }, { status: 400 });
|
||||
}
|
||||
}
|
||||
if (body.action === "change-plan" && !body.newPlanId) {
|
||||
return NextResponse.json({ ok: false, error: "newPlanId_required" }, { status: 400 });
|
||||
}
|
||||
|
||||
const endpoint = `/api/sase/subscriptions/${subId}/${body.action}`;
|
||||
try {
|
||||
@@ -50,17 +54,41 @@ export async function POST(
|
||||
reason,
|
||||
founderId: session.user.id,
|
||||
})
|
||||
: await sdk.activateSubscription({
|
||||
subscriptionId: subId,
|
||||
reason,
|
||||
founderId: session.user.id,
|
||||
});
|
||||
: body.action === "activate"
|
||||
? await sdk.activateSubscription({
|
||||
subscriptionId: subId,
|
||||
reason,
|
||||
founderId: session.user.id,
|
||||
})
|
||||
: body.action === "change-plan"
|
||||
? await sdk.changePlan({
|
||||
subscriptionId: subId,
|
||||
newPlanId: body.newPlanId!,
|
||||
reason,
|
||||
founderId: session.user.id,
|
||||
})
|
||||
: body.action === "cancel"
|
||||
? await sdk.cancelSubscriptionById({
|
||||
subscriptionId: subId,
|
||||
reason,
|
||||
founderId: session.user.id,
|
||||
})
|
||||
: await sdk.resumeSubscription({
|
||||
subscriptionId: subId,
|
||||
reason,
|
||||
founderId: session.user.id,
|
||||
});
|
||||
|
||||
await writeAudit({
|
||||
projectKey: "sase",
|
||||
endpoint,
|
||||
method: "POST",
|
||||
requestPayload: { action: body.action, days: body.days, reasonLen: reason.length },
|
||||
requestPayload: {
|
||||
action: body.action,
|
||||
days: body.days,
|
||||
newPlanId: body.newPlanId,
|
||||
reasonLen: reason.length,
|
||||
},
|
||||
responseStatus: 200,
|
||||
});
|
||||
|
||||
@@ -72,7 +100,12 @@ export async function POST(
|
||||
projectKey: "sase",
|
||||
endpoint,
|
||||
method: "POST",
|
||||
requestPayload: { action: body.action, days: body.days, reasonLen: reason.length },
|
||||
requestPayload: {
|
||||
action: body.action,
|
||||
days: body.days,
|
||||
newPlanId: body.newPlanId,
|
||||
reasonLen: reason.length,
|
||||
},
|
||||
responseStatus: status,
|
||||
});
|
||||
return NextResponse.json({ ok: false, error: message }, { status });
|
||||
|
||||
Reference in New Issue
Block a user