feat: Implement Stripe integration for subscription management, including new services, controllers, Prisma models, and webhook handling.

This commit is contained in:
pradeepkumar
2026-03-07 10:09:50 +05:30
parent b3f43bf2e5
commit d1e4e12510
13 changed files with 860 additions and 9 deletions

View File

@@ -80,6 +80,15 @@ enum SupportChatStatus {
CLOSED
}
enum SubscriptionStatus {
ACTIVE
PAST_DUE
CANCELED
UNPAID
TRIALING
INCOMPLETE
}
// ===========================================
// USER - Authentication Only
@@ -143,6 +152,10 @@ model User {
// Support Chat
supportChats SupportChat[]
// Subscriptions & Payments
subscriptions AgentSubscription[]
payments Payment[]
@@index([email])
@@index([role])
@@index([status])
@@ -231,6 +244,9 @@ model AgentProfile {
isFeatured Boolean @default(false)
isAvailable Boolean @default(true) // Agent availability status for connect requests
// Subscription (denormalized for quick access)
subscriptionStatus String? // "ACTIVE", "NONE", etc.
// Stats (denormalized for performance)
totalReviews Int @default(0)
averageRating Float @default(0)
@@ -617,3 +633,72 @@ model SupportMessage {
@@index([chatId])
@@map("support_messages")
}
// ===========================================
// SUBSCRIPTION & PAYMENTS
// ===========================================
model SubscriptionPlan {
id String @id @default(uuid())
name String // "Professional Annual"
description String? @db.Text
stripePriceId String @unique // Stripe Price ID (price_xxx)
amount Int // Amount in cents (49900)
currency String @default("usd")
interval String @default("year") // "month" | "year"
features Json? // ["Feature 1", "Feature 2"]
isActive Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
subscriptions AgentSubscription[]
@@index([isActive])
@@map("subscription_plans")
}
model AgentSubscription {
id String @id @default(uuid())
userId String
planId String
stripeCustomerId String // cus_xxx
stripeSubscriptionId String? @unique // sub_xxx
status SubscriptionStatus @default(INCOMPLETE)
currentPeriodStart DateTime?
currentPeriodEnd DateTime?
cancelAtPeriodEnd Boolean @default(false)
canceledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
plan SubscriptionPlan @relation(fields: [planId], references: [id])
payments Payment[]
@@index([userId])
@@index([status])
@@index([stripeCustomerId])
@@map("agent_subscriptions")
}
model Payment {
id String @id @default(uuid())
subscriptionId String?
userId String
stripePaymentIntentId String? @unique // pi_xxx
stripeInvoiceId String? @unique // in_xxx
amount Int // cents
currency String @default("usd")
status String // "succeeded", "failed", "pending"
receiptUrl String?
createdAt DateTime @default(now())
subscription AgentSubscription? @relation(fields: [subscriptionId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([subscriptionId])
@@index([status])
@@map("payments")
}

View File

@@ -1533,6 +1533,45 @@ async function main() {
console.log('');
// =============================================
// Seed Subscription Plans
// =============================================
console.log('💳 Seeding Subscription Plans...');
const subscriptionPlans = [
{
name: 'Professional Annual',
description: 'Full access to all agent features',
stripePriceId: process.env.STRIPE_PRICE_ID,
amount: 49900,
currency: 'usd',
interval: 'year',
features: JSON.stringify([
'Agent Co-Marketing',
'Priority 24/7 Support',
'Whitelabel Client Portals',
'Advanced CRM Tools & Analytics',
]),
isActive: true,
sortOrder: 1,
},
];
for (const plan of subscriptionPlans) {
const existingPlan = await prisma.subscriptionPlan.findUnique({
where: { stripePriceId: plan.stripePriceId },
});
if (existingPlan) {
console.log(` ⚠️ Plan "${plan.name}" already exists`);
} else {
await prisma.subscriptionPlan.create({ data: plan });
console.log(` ✅ Created plan: ${plan.name} ($${(plan.amount / 100).toFixed(2)}/${plan.interval})`);
}
}
console.log('');
// =============================================
// Summary
// =============================================
@@ -1544,6 +1583,7 @@ async function main() {
const systemSectionCount = await prisma.profileSection.count({ where: { isSystem: true } });
const agentTypeSectionCount = await prisma.agentTypeSection.count();
const cmsContentCount = await prisma.cmsContent.count();
const planCount = await prisma.subscriptionPlan.count();
console.log('📊 Database Summary:');
console.log(` Total Users: ${userCount}`);
@@ -1553,6 +1593,7 @@ async function main() {
console.log(` Profile Fields: ${fieldCount}`);
console.log(` Section Assignments: ${agentTypeSectionCount}`);
console.log(` CMS Content: ${cmsContentCount}`);
console.log(` Plans: ${planCount}`);
console.log('\n🌱 Seeding completed!\n');
await prisma.$disconnect();