diff --git a/src/stripe/subscription.service.ts b/src/stripe/subscription.service.ts index 6d5e112..df52ecd 100644 --- a/src/stripe/subscription.service.ts +++ b/src/stripe/subscription.service.ts @@ -1,12 +1,16 @@ import { Injectable, Logger } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { SubscriptionStatus } from '@prisma/client'; +import { StripeService } from './stripe.service'; @Injectable() export class SubscriptionService { private readonly logger = new Logger(SubscriptionService.name); - constructor(private prisma: PrismaService) {} + constructor( + private prisma: PrismaService, + private stripeService: StripeService, + ) {} async getActivePlan(userId: string) { return this.prisma.agentSubscription.findFirst({ @@ -129,28 +133,96 @@ export class SubscriptionService { } async getRevenueStats() { - const [activeCount, totalRevenue, monthlyRevenue] = await Promise.all([ - this.prisma.agentSubscription.count({ where: { status: 'ACTIVE' } }), - this.prisma.payment.aggregate({ - where: { status: 'succeeded' }, - _sum: { amount: true }, - }), - this.prisma.payment.aggregate({ - where: { - status: 'succeeded', - createdAt: { - gte: new Date(new Date().getFullYear(), new Date().getMonth(), 1), - }, - }, - _sum: { amount: true }, - }), - ]); + try { + const stripe = this.stripeService.getStripe(); - return { - activeSubscriptions: activeCount, - totalRevenue: (totalRevenue._sum.amount || 0) / 100, - monthlyRevenue: (monthlyRevenue._sum.amount || 0) / 100, - }; + // Count active subscriptions from Stripe + let activeCount = 0; + let hasMore = true; + let startingAfter: string | undefined; + while (hasMore) { + const subs = await stripe.subscriptions.list({ + status: 'active', + limit: 100, + ...(startingAfter ? { starting_after: startingAfter } : {}), + }); + activeCount += subs.data.length; + hasMore = subs.has_more; + if (subs.data.length > 0) { + startingAfter = subs.data[subs.data.length - 1].id; + } + } + + // Total revenue: sum of all successful charges + let totalRevenueCents = 0; + hasMore = true; + startingAfter = undefined; + while (hasMore) { + const charges = await stripe.charges.list({ + limit: 100, + ...(startingAfter ? { starting_after: startingAfter } : {}), + }); + totalRevenueCents += charges.data + .filter((c) => c.status === 'succeeded') + .reduce((sum, c) => sum + c.amount, 0); + hasMore = charges.has_more; + if (charges.data.length > 0) { + startingAfter = charges.data[charges.data.length - 1].id; + } + } + + // Monthly revenue: charges from start of current month + const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1); + let monthlyRevenueCents = 0; + hasMore = true; + startingAfter = undefined; + while (hasMore) { + const charges = await stripe.charges.list({ + limit: 100, + created: { gte: Math.floor(monthStart.getTime() / 1000) }, + ...(startingAfter ? { starting_after: startingAfter } : {}), + }); + monthlyRevenueCents += charges.data + .filter((c) => c.status === 'succeeded') + .reduce((sum, c) => sum + c.amount, 0); + hasMore = charges.has_more; + if (charges.data.length > 0) { + startingAfter = charges.data[charges.data.length - 1].id; + } + } + + return { + activeSubscriptions: activeCount, + totalRevenue: totalRevenueCents / 100, + monthlyRevenue: monthlyRevenueCents / 100, + }; + } catch (error) { + this.logger.warn(`Failed to fetch stats from Stripe, falling back to DB: ${error.message}`); + + // Fallback to local DB + const [activeCount, totalRevenue, monthlyRevenue] = await Promise.all([ + this.prisma.agentSubscription.count({ where: { status: 'ACTIVE' } }), + this.prisma.payment.aggregate({ + where: { status: 'succeeded' }, + _sum: { amount: true }, + }), + this.prisma.payment.aggregate({ + where: { + status: 'succeeded', + createdAt: { + gte: new Date(new Date().getFullYear(), new Date().getMonth(), 1), + }, + }, + _sum: { amount: true }, + }), + ]); + + return { + activeSubscriptions: activeCount, + totalRevenue: (totalRevenue._sum.amount || 0) / 100, + monthlyRevenue: (monthlyRevenue._sum.amount || 0) / 100, + }; + } } private async updateAgentSubscriptionStatus(userId: string, status: SubscriptionStatus) {