Files
OpenFrame/app/api/billing/checkout/route.ts
T
Yusuf İpek 6f22b0bf8b feat(billing): integrate Stripe for subscription management and billing access
- Added billing-related fields to the User model in the database.
- Implemented functions for managing billing access, including trial periods and subscription statuses.
- Created new billing utility functions for Stripe integration.
- Updated onboarding page to include billing overview and workspace creation eligibility.
- Enhanced route access checks to require billing access for certain actions.
- Implemented cleanup scripts for expired billing workspaces and associated media.
- Updated header component to conditionally show app navigation based on billing access.
- Added new migrations for billing-related database changes.
2026-04-08 17:50:40 +03:00

81 lines
2.5 KiB
TypeScript

import { NextRequest } from 'next/server';
import { auth } from '@/lib/auth';
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
import {
DEFAULT_TRIAL_PERIOD_DAYS,
getOrCreateStripeCustomerId,
getStripeCheckoutState,
} from '@/lib/billing';
import { rateLimit } from '@/lib/rate-limit';
import { getStripe, getStripePriceId, isStripeConfigured } from '@/lib/stripe';
import { isTrustedSameOriginRequest } from '@/lib/request-origin';
function getAppOrigin(request: NextRequest) {
if (isTrustedSameOriginRequest(request)) {
const origin = request.headers.get('origin');
if (origin) {
return new URL(origin).origin;
}
}
return request.nextUrl.origin;
}
export async function POST(request: NextRequest) {
try {
const limited = await rateLimit(request, 'mutate');
if (limited) return limited;
if (!isTrustedSameOriginRequest(request)) {
return apiErrors.forbidden('Invalid request origin');
}
const session = await auth();
if (!session?.user?.id) {
return apiErrors.unauthorized();
}
if (!isStripeConfigured()) {
return apiErrors.internalError('Stripe billing is not configured');
}
const checkoutState = await getStripeCheckoutState(session.user.id);
if (checkoutState.hasActiveSubscription) {
return apiErrors.badRequest('An active subscription already exists for this account');
}
const stripe = getStripe();
const priceId = getStripePriceId();
const customerId = await getOrCreateStripeCustomerId(session.user.id);
const appOrigin = getAppOrigin(request);
const checkoutSession = await stripe.checkout.sessions.create({
mode: 'subscription',
customer: customerId,
line_items: [{ price: priceId, quantity: 1 }],
allow_promotion_codes: true,
success_url: `${appOrigin}/settings?billing=success`,
cancel_url: `${appOrigin}/settings?billing=canceled`,
metadata: {
userId: session.user.id,
},
subscription_data: {
metadata: {
userId: session.user.id,
},
...(checkoutState.isTrialEligible ? { trial_period_days: DEFAULT_TRIAL_PERIOD_DAYS } : {}),
},
});
if (!checkoutSession.url) {
throw new Error('Stripe did not return a checkout URL');
}
const response = successResponse({ url: checkoutSession.url });
return withCacheControl(response, 'private, no-store');
} catch (error) {
console.error('Error creating Stripe checkout session:', error);
return apiErrors.internalError('Failed to start checkout');
}
}