mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
- 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.
81 lines
2.5 KiB
TypeScript
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');
|
|
}
|
|
}
|