diff --git a/apps/webapp/app/models/user.server.ts b/apps/webapp/app/models/user.server.ts index f5aa837..962a1b0 100644 --- a/apps/webapp/app/models/user.server.ts +++ b/apps/webapp/app/models/user.server.ts @@ -2,6 +2,7 @@ import type { Prisma, User } from "@core/database"; import type { GoogleProfile } from "@coji/remix-auth-google"; import { prisma } from "~/db.server"; import { env } from "~/env.server"; +import { ensureBillingInitialized } from "~/services/billing.server"; export type { User } from "@core/database"; type FindOrCreateMagicLink = { @@ -156,11 +157,6 @@ export async function findOrCreateGoogleUser({ authIdentifier, email, authenticationMethod: "GOOGLE", - UserUsage: { - create: { - availableCredits: 200, - }, - }, }, }); diff --git a/apps/webapp/app/models/workspace.server.ts b/apps/webapp/app/models/workspace.server.ts index 25dbfd7..a0716b9 100644 --- a/apps/webapp/app/models/workspace.server.ts +++ b/apps/webapp/app/models/workspace.server.ts @@ -1,5 +1,6 @@ import { type Workspace } from "@core/database"; import { prisma } from "~/db.server"; +import { ensureBillingInitialized } from "~/services/billing.server"; import { sendEmail } from "~/services/email.server"; import { logger } from "~/services/logger.service"; import { SpaceService } from "~/services/space.server"; @@ -40,6 +41,8 @@ export async function createWorkspace( }, }); + await ensureBillingInitialized(workspace.id); + await spaceService.createSpace({ name: "Profile", description: profileRule, diff --git a/apps/webapp/app/services/billing.server.ts b/apps/webapp/app/services/billing.server.ts index 842ebe0..fc0244b 100644 --- a/apps/webapp/app/services/billing.server.ts +++ b/apps/webapp/app/services/billing.server.ts @@ -6,11 +6,7 @@ */ import { prisma } from "~/db.server"; -import { - BILLING_CONFIG, - isBillingEnabled, - getPlanConfig, -} from "~/config/billing.server"; +import { getPlanConfig } from "~/config/billing.server"; import type { PlanType, Subscription } from "@prisma/client"; export type CreditOperation = "addEpisode" | "search" | "chatMessage"; @@ -114,7 +110,7 @@ export async function initializeSubscription( /** * Ensure workspace has billing records initialized */ -async function ensureBillingInitialized(workspaceId: string) { +export async function ensureBillingInitialized(workspaceId: string) { const workspace = await prisma.workspace.findUnique({ where: { id: workspaceId }, include: { diff --git a/apps/webapp/app/trigger/utils/utils.ts b/apps/webapp/app/trigger/utils/utils.ts index 834fc7a..c0ed90f 100644 --- a/apps/webapp/app/trigger/utils/utils.ts +++ b/apps/webapp/app/trigger/utils/utils.ts @@ -760,7 +760,7 @@ export async function hasCredits( } const userUsage = workspace.user.UserUsage; - const subscription = workspace.Subscription; + // const subscription = workspace.Subscription; // If has available credits, return true if (userUsage.availableCredits >= creditCost) { @@ -768,9 +768,9 @@ export async function hasCredits( } // If overage is enabled (Pro/Max), return true - if (subscription.enableUsageBilling) { - return true; - } + // if (subscription.enableUsageBilling) { + // return true; + // } // Free plan with no credits left return false; diff --git a/apps/webapp/prisma/schema.prisma b/apps/webapp/prisma/schema.prisma index b105f2c..ae4af39 100644 --- a/apps/webapp/prisma/schema.prisma +++ b/apps/webapp/prisma/schema.prisma @@ -574,8 +574,19 @@ model UserUsage { updatedAt DateTime @updatedAt deleted DateTime? + // Current period tracking availableCredits Int @default(0) usedCredits Int @default(0) + overageCredits Int @default(0) // Credits used beyond monthly allocation + + // Last reset tracking + lastResetAt DateTime @default(now()) + nextResetAt DateTime? + + // Usage breakdown (optional analytics) + episodeCreditsUsed Int @default(0) + searchCreditsUsed Int @default(0) + chatCreditsUsed Int @default(0) user User @relation(fields: [userId], references: [id]) userId String @unique @@ -614,6 +625,69 @@ model WebhookDeliveryLog { createdAt DateTime @default(now()) } +model Subscription { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Stripe integration + stripeCustomerId String? @unique + stripeSubscriptionId String? @unique + stripePriceId String? + stripeCurrentPeriodEnd DateTime? + + // Plan details + planType PlanType @default(FREE) + status SubscriptionStatus @default(ACTIVE) + + // Monthly credits allocation + monthlyCredits Int @default(0) + + // Billing cycle tracking + currentPeriodStart DateTime @default(now()) + currentPeriodEnd DateTime + + // Usage-based pricing (for PRO plan) + enableUsageBilling Boolean @default(false) + usagePricePerCredit Float? // Price per credit after monthly quota + + // Overage tracking + overageCreditsUsed Int @default(0) + overageAmount Float @default(0) + + // Relations + workspace Workspace @relation(fields: [workspaceId], references: [id]) + workspaceId String @unique + BillingHistory BillingHistory[] +} + +model BillingHistory { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + + // Billing period + periodStart DateTime + periodEnd DateTime + + // Credits tracking + monthlyCreditsAllocated Int + creditsUsed Int + overageCreditsUsed Int + + // Charges + subscriptionAmount Float + usageAmount Float // Overage charges + totalAmount Float + + // Stripe integration + stripeInvoiceId String? @unique + stripePaymentStatus String? + + // Relations + subscription Subscription @relation(fields: [subscriptionId], references: [id]) + subscriptionId String +} + model Workspace { id String @id @default(uuid()) createdAt DateTime @default(now()) @@ -643,6 +717,21 @@ model Workspace { RecallLog RecallLog[] Space Space[] MCPSession MCPSession[] + Subscription Subscription? +} + +enum PlanType { + FREE + PRO + MAX +} + +enum SubscriptionStatus { + ACTIVE + CANCELED + PAST_DUE + TRIALING + PAUSED } enum AuthenticationMethod { @@ -656,6 +745,7 @@ enum IngestionStatus { COMPLETED FAILED CANCELLED + NO_CREDITS } enum UserType {