- Build complete Next.js CRM for commercial real estate - Add authentication with JWT sessions and role-based access - Add GoHighLevel API integration for contacts, conversations, opportunities - Add AI-powered Control Center with tool calling - Add Setup page with onboarding checklist (/setup) - Add sidebar navigation with Setup menu item - Fix type errors in onboarding API, GHL services, and control center tools - Add Prisma schema with SQLite for local development - Add UI components with clay morphism design system Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
102 lines
2.4 KiB
TypeScript
102 lines
2.4 KiB
TypeScript
import { getStripeClient } from './client';
|
|
import { getDFYProduct } from './dfy-products';
|
|
import { prisma } from '@/lib/db';
|
|
|
|
export interface CreateCheckoutParams {
|
|
userId: string;
|
|
productId: string;
|
|
successUrl: string;
|
|
cancelUrl: string;
|
|
}
|
|
|
|
export async function createDFYCheckoutSession(params: CreateCheckoutParams) {
|
|
const { userId, productId, successUrl, cancelUrl } = params;
|
|
|
|
const product = getDFYProduct(productId);
|
|
if (!product) {
|
|
throw new Error(`Invalid product ID: ${productId}`);
|
|
}
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { email: true, firstName: true, lastName: true },
|
|
});
|
|
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
const stripe = await getStripeClient();
|
|
|
|
const session = await stripe.checkout.sessions.create({
|
|
mode: 'payment',
|
|
customer_email: user.email,
|
|
line_items: [
|
|
{
|
|
price_data: {
|
|
currency: 'usd',
|
|
product_data: {
|
|
name: product.name,
|
|
description: product.description,
|
|
metadata: {
|
|
product_id: productId,
|
|
delivery_days: product.deliveryDays.toString(),
|
|
},
|
|
},
|
|
unit_amount: product.priceInCents,
|
|
},
|
|
quantity: 1,
|
|
},
|
|
],
|
|
metadata: {
|
|
user_id: userId,
|
|
product_id: productId,
|
|
type: 'dfy_service',
|
|
},
|
|
success_url: successUrl,
|
|
cancel_url: cancelUrl,
|
|
});
|
|
|
|
// Create pending DFY request
|
|
await prisma.dFYRequest.create({
|
|
data: {
|
|
userId,
|
|
serviceType: productId,
|
|
status: 'PENDING_PAYMENT',
|
|
stripePaymentId: session.id,
|
|
amountPaid: product.priceInCents,
|
|
},
|
|
});
|
|
|
|
return session;
|
|
}
|
|
|
|
export async function handleCheckoutComplete(sessionId: string) {
|
|
const stripe = await getStripeClient();
|
|
const session = await stripe.checkout.sessions.retrieve(sessionId);
|
|
|
|
if (session.payment_status !== 'paid') {
|
|
return { success: false, error: 'Payment not completed' };
|
|
}
|
|
|
|
const userId = session.metadata?.user_id;
|
|
const productId = session.metadata?.product_id;
|
|
|
|
if (!userId || !productId) {
|
|
return { success: false, error: 'Missing metadata' };
|
|
}
|
|
|
|
// Update DFY request status
|
|
await prisma.dFYRequest.updateMany({
|
|
where: {
|
|
stripePaymentId: sessionId,
|
|
},
|
|
data: {
|
|
status: 'PAID',
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
|
|
return { success: true, userId, productId };
|
|
}
|