mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +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.
89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
import { NextRequest } from 'next/server';
|
|
import type Stripe from 'stripe';
|
|
import {
|
|
markSubscriptionCanceledByCustomerId,
|
|
syncStripeSubscriptionToUser,
|
|
} from '@/lib/billing';
|
|
import { getStripe, getStripeWebhookSecret } from '@/lib/stripe';
|
|
|
|
export const runtime = 'nodejs';
|
|
|
|
async function handleSubscriptionDeleted(subscription: Stripe.Subscription) {
|
|
const customerId =
|
|
typeof subscription.customer === 'string'
|
|
? subscription.customer
|
|
: subscription.customer.id;
|
|
|
|
const currentPeriodEnd =
|
|
'current_period_end' in subscription && typeof subscription.current_period_end === 'number'
|
|
? new Date(subscription.current_period_end * 1000)
|
|
: null;
|
|
const endedAt =
|
|
'ended_at' in subscription && typeof subscription.ended_at === 'number'
|
|
? new Date(subscription.ended_at * 1000)
|
|
: currentPeriodEnd;
|
|
|
|
await markSubscriptionCanceledByCustomerId(customerId, {
|
|
currentPeriodEnd,
|
|
endedAt,
|
|
});
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const signature = request.headers.get('stripe-signature');
|
|
if (!signature) {
|
|
return new Response('Missing Stripe signature', { status: 400 });
|
|
}
|
|
|
|
let event: Stripe.Event;
|
|
|
|
try {
|
|
const stripe = getStripe();
|
|
const body = await request.text();
|
|
event = stripe.webhooks.constructEvent(body, signature, getStripeWebhookSecret());
|
|
} catch (error) {
|
|
console.error('Failed to verify Stripe webhook:', error);
|
|
return new Response('Invalid webhook signature', { status: 400 });
|
|
}
|
|
|
|
try {
|
|
const stripe = getStripe();
|
|
|
|
switch (event.type) {
|
|
case 'checkout.session.completed': {
|
|
const session = event.data.object as Stripe.Checkout.Session;
|
|
if (session.mode === 'subscription' && session.subscription) {
|
|
const subscriptionId =
|
|
typeof session.subscription === 'string'
|
|
? session.subscription
|
|
: session.subscription.id;
|
|
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
|
|
await syncStripeSubscriptionToUser(subscription);
|
|
}
|
|
break;
|
|
}
|
|
case 'customer.subscription.created':
|
|
case 'customer.subscription.updated': {
|
|
const subscription = event.data.object as Stripe.Subscription;
|
|
await syncStripeSubscriptionToUser(subscription);
|
|
break;
|
|
}
|
|
case 'customer.subscription.deleted': {
|
|
const subscription = event.data.object as Stripe.Subscription;
|
|
await handleSubscriptionDeleted(subscription);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return new Response(JSON.stringify({ received: true }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to process Stripe webhook:', error);
|
|
return new Response('Webhook processing failed', { status: 500 });
|
|
}
|
|
}
|