mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
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.
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
import { auth, checkProjectAccess, checkWorkspaceAccess } from '@/lib/auth';
|
||||
import { hasBillingAccess } from '@/lib/billing';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
type AccessIntent = 'view' | 'manage';
|
||||
|
||||
const LOGIN_REDIRECT = '/login';
|
||||
const FORBIDDEN_REDIRECT = '/dashboard';
|
||||
const BILLING_REDIRECT = '/settings';
|
||||
|
||||
function redirectForMissingAuth() {
|
||||
redirect(LOGIN_REDIRECT);
|
||||
@@ -15,6 +17,10 @@ function redirectForForbidden() {
|
||||
redirect(FORBIDDEN_REDIRECT);
|
||||
}
|
||||
|
||||
function redirectForBilling() {
|
||||
redirect(BILLING_REDIRECT);
|
||||
}
|
||||
|
||||
function ensureGuestPolicy(options: { userId?: string; intent: AccessIntent; allowPublicView: boolean }) {
|
||||
const { userId, intent, allowPublicView } = options;
|
||||
if (userId) return;
|
||||
@@ -60,6 +66,92 @@ export async function requireAuthOrRedirect() {
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function requireBillingAccessOrRedirect(options?: {
|
||||
userId?: string;
|
||||
}) {
|
||||
const resolvedUserId = options?.userId ?? (await auth())?.user?.id;
|
||||
|
||||
if (!resolvedUserId) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: resolvedUserId },
|
||||
select: {
|
||||
subscriptionStatus: true,
|
||||
trialEndsAt: true,
|
||||
stripeCurrentPeriodEnd: true,
|
||||
billingAccessEndedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user || !hasBillingAccess(user)) {
|
||||
redirectForBilling();
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function hasCollaboratorBillingBackedAccess(userId: string) {
|
||||
const now = new Date();
|
||||
|
||||
const [workspaceCount, projectCount] = await Promise.all([
|
||||
db.workspace.count({
|
||||
where: {
|
||||
owner: {
|
||||
OR: [
|
||||
{ subscriptionStatus: { in: ['ACTIVE', 'TRIALING'] } },
|
||||
{ trialEndsAt: { gt: now } },
|
||||
{ stripeCurrentPeriodEnd: { gt: now } },
|
||||
],
|
||||
},
|
||||
OR: [
|
||||
{ ownerId: userId },
|
||||
{ members: { some: { userId } } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
db.project.count({
|
||||
where: {
|
||||
workspace: {
|
||||
owner: {
|
||||
OR: [
|
||||
{ subscriptionStatus: { in: ['ACTIVE', 'TRIALING'] } },
|
||||
{ trialEndsAt: { gt: now } },
|
||||
{ stripeCurrentPeriodEnd: { gt: now } },
|
||||
],
|
||||
},
|
||||
},
|
||||
OR: [
|
||||
{ ownerId: userId },
|
||||
{ members: { some: { userId } } },
|
||||
{ workspace: { members: { some: { userId } } } },
|
||||
],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return workspaceCount > 0 || projectCount > 0;
|
||||
}
|
||||
|
||||
export async function hasAppNavigationAccess(userId: string) {
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
subscriptionStatus: true,
|
||||
trialEndsAt: true,
|
||||
stripeCurrentPeriodEnd: true,
|
||||
billingAccessEndedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (user && hasBillingAccess(user)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasCollaboratorBillingBackedAccess(userId);
|
||||
}
|
||||
|
||||
export async function requireWorkspaceAccessOrRedirect(options: {
|
||||
workspaceId: string;
|
||||
userId?: string;
|
||||
@@ -84,10 +176,16 @@ export async function requireWorkspaceAccessOrRedirect(options: {
|
||||
const access = await checkWorkspaceAccess(workspace, resolvedUserId);
|
||||
|
||||
if (!access.hasAccess) {
|
||||
if (!access.ownerBillingActive) {
|
||||
redirectForBilling();
|
||||
}
|
||||
redirectForForbidden();
|
||||
}
|
||||
|
||||
if (intent === 'manage' && !access.canEdit) {
|
||||
if (!access.ownerBillingActive) {
|
||||
redirectForBilling();
|
||||
}
|
||||
redirectForForbidden();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user