feat: Implement Stripe integration for subscription management, including new services, controllers, Prisma models, and webhook handling.
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user