Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions app/api/pymthouse/plans/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextResponse } from "next/server";
import { listDashboardBillingPlans } from "@/lib/console/pymthouse-billing-bff";
import {
PYMTHOUSE_NO_STORE_HEADERS,
pymthouseErrorResponse,
} from "@/app/api/pymthouse/route-helpers";

export const runtime = "nodejs";

export async function GET() {
try {
const plans = await listDashboardBillingPlans();
return NextResponse.json(
{ plans },
{ headers: PYMTHOUSE_NO_STORE_HEADERS }
);
} catch (error) {
return pymthouseErrorResponse(error, "Failed to list billing plans");
}
}
42 changes: 42 additions & 0 deletions app/api/pymthouse/subscribe/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { NextRequest, NextResponse } from "next/server";
import { startDashboardBillingCheckout } from "@/lib/console/pymthouse-billing-bff";
import { requireConsoleSession } from "@/lib/console/session-user";
import {
checkoutReturnOrigin,
PYMTHOUSE_NO_STORE_HEADERS,
pymthouseErrorResponse,
} from "@/app/api/pymthouse/route-helpers";

export const runtime = "nodejs";

export async function POST(request: NextRequest) {
let body: { planId?: string; successUrl?: string; cancelUrl?: string };
try {
body = (await request.json()) as typeof body;
} catch {
return NextResponse.json({ error: "invalid_json" }, { status: 400 });
}

const planId = body.planId?.trim();
if (!planId) {
return NextResponse.json({ error: "planId is required" }, { status: 400 });
}

const origin = checkoutReturnOrigin(request);
const successUrl =
body.successUrl?.trim() || `${origin}/usage?checkout=success`;
const cancelUrl = body.cancelUrl?.trim() || `${origin}/usage?checkout=cancel`;

try {
const session = await requireConsoleSession();
const result = await startDashboardBillingCheckout({
planId,
externalUserId: session.externalUserId,
successUrl,
cancelUrl,
});
return NextResponse.json(result, { headers: PYMTHOUSE_NO_STORE_HEADERS });
} catch (error) {
return pymthouseErrorResponse(error, "Failed to start checkout");
}
}
32 changes: 32 additions & 0 deletions app/api/pymthouse/subscription/cancel/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from "next/server";
import { cancelDashboardUserSubscription } from "@/lib/console/pymthouse-billing-bff";
import { requireConsoleSession } from "@/lib/console/session-user";
import {
PYMTHOUSE_NO_STORE_HEADERS,
pymthouseErrorResponse,
} from "@/app/api/pymthouse/route-helpers";

export const runtime = "nodejs";

export async function POST(request: NextRequest) {
let body: { timing?: string; effectiveAt?: string };
try {
body = (await request.json()) as typeof body;
} catch {
return NextResponse.json({ error: "invalid_json" }, { status: 400 });
}

try {
const session = await requireConsoleSession();
await cancelDashboardUserSubscription(session.externalUserId, {
timing: body.timing?.trim(),
effectiveAt: body.effectiveAt?.trim(),
});
return NextResponse.json(
{ ok: true },
{ headers: PYMTHOUSE_NO_STORE_HEADERS }
);
} catch (error) {
return pymthouseErrorResponse(error, "Failed to cancel subscription");
}
}
59 changes: 59 additions & 0 deletions app/api/pymthouse/subscription/change/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { NextRequest, NextResponse } from "next/server";
import { PmtHouseError } from "@pymthouse/builder-sdk";
import { changeDashboardBillingSubscription } from "@/lib/console/pymthouse-billing-bff";
import { requireConsoleSession } from "@/lib/console/session-user";
import {
PYMTHOUSE_NO_STORE_HEADERS,
pymthouseErrorResponse,
} from "@/app/api/pymthouse/route-helpers";

export const runtime = "nodejs";

export async function POST(request: NextRequest) {
let body: {
planId?: string;
successUrl?: string;
cancelUrl?: string;
timing?: string;
effectiveAt?: string;
confirmReplaceScheduled?: boolean;
};
try {
body = (await request.json()) as typeof body;
} catch {
return NextResponse.json({ error: "invalid_json" }, { status: 400 });
}

const planId = body.planId?.trim();
if (!planId) {
return NextResponse.json({ error: "planId is required" }, { status: 400 });
}

try {
const session = await requireConsoleSession();
const result = await changeDashboardBillingSubscription({
planId,
externalUserId: session.externalUserId,
successUrl: body.successUrl?.trim(),
cancelUrl: body.cancelUrl?.trim(),
timing: body.timing?.trim(),
effectiveAt: body.effectiveAt?.trim(),
confirmReplaceScheduled: body.confirmReplaceScheduled === true,
});
return NextResponse.json(result, { headers: PYMTHOUSE_NO_STORE_HEADERS });
} catch (error) {
if (error instanceof PmtHouseError) {
return NextResponse.json(
{
error: error.message,
code: error.code,
...(error.details && typeof error.details === "object"
? (error.details as Record<string, unknown>)
: {}),
},
{ status: error.status, headers: PYMTHOUSE_NO_STORE_HEADERS }
);
}
return pymthouseErrorResponse(error, "Failed to change subscription");
}
}
38 changes: 38 additions & 0 deletions app/api/pymthouse/subscription/resume/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { NextResponse } from "next/server";
import { PmtHouseError } from "@pymthouse/builder-sdk";
import { resumeDashboardUserSubscription } from "@/lib/console/pymthouse-billing-bff";
import { requireConsoleSession } from "@/lib/console/session-user";
import {
PYMTHOUSE_NO_STORE_HEADERS,
pymthouseErrorResponse,
} from "@/app/api/pymthouse/route-helpers";

export const runtime = "nodejs";

function upstreamCode(error: PmtHouseError): string {
const details = error.details;
if (details && typeof details === "object" && "code" in details) {
const code = (details as { code?: unknown }).code;
if (typeof code === "string" && code.trim()) return code;
}
return error.code;
}

export async function POST() {
try {
const session = await requireConsoleSession();
await resumeDashboardUserSubscription(session.externalUserId);
return NextResponse.json(
{ ok: true },
{ headers: PYMTHOUSE_NO_STORE_HEADERS }
);
} catch (error) {
if (error instanceof PmtHouseError) {
return NextResponse.json(
{ error: error.message, code: upstreamCode(error) },
{ status: error.status, headers: PYMTHOUSE_NO_STORE_HEADERS }
);
}
return pymthouseErrorResponse(error, "Failed to resume subscription");
}
}
24 changes: 24 additions & 0 deletions app/api/pymthouse/subscription/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NextResponse } from "next/server";
import { getDashboardUserSubscription } from "@/lib/console/pymthouse-billing-bff";
import { requireConsoleSession } from "@/lib/console/session-user";
import {
PYMTHOUSE_NO_STORE_HEADERS,
pymthouseErrorResponse,
} from "@/app/api/pymthouse/route-helpers";

export const runtime = "nodejs";

export async function GET() {
try {
const session = await requireConsoleSession();
const subscription = await getDashboardUserSubscription(
session.externalUserId
);
return NextResponse.json(
{ subscription },
{ headers: PYMTHOUSE_NO_STORE_HEADERS }
);
} catch (error) {
return pymthouseErrorResponse(error, "Failed to load subscription");
}
}
19 changes: 19 additions & 0 deletions app/api/pymthouse/subscriptions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from "next/server";
import { listDashboardUserSubscriptions } from "@/lib/console/pymthouse-billing-bff";
import { requireConsoleSession } from "@/lib/console/session-user";
import {
PYMTHOUSE_NO_STORE_HEADERS,
pymthouseErrorResponse,
} from "@/app/api/pymthouse/route-helpers";

export const runtime = "nodejs";

export async function GET() {
try {
const session = await requireConsoleSession();
const result = await listDashboardUserSubscriptions(session.externalUserId);
return NextResponse.json(result, { headers: PYMTHOUSE_NO_STORE_HEADERS });
} catch (error) {
return pymthouseErrorResponse(error, "Failed to load subscription history");
}
}
Loading