feat: migrate getRevenueStats to fetch directly from Stripe API with database fallback
This commit is contained in:
@@ -1,12 +1,16 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { SubscriptionStatus } from '@prisma/client';
|
import { SubscriptionStatus } from '@prisma/client';
|
||||||
|
import { StripeService } from './stripe.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SubscriptionService {
|
export class SubscriptionService {
|
||||||
private readonly logger = new Logger(SubscriptionService.name);
|
private readonly logger = new Logger(SubscriptionService.name);
|
||||||
|
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
|
private stripeService: StripeService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async getActivePlan(userId: string) {
|
async getActivePlan(userId: string) {
|
||||||
return this.prisma.agentSubscription.findFirst({
|
return this.prisma.agentSubscription.findFirst({
|
||||||
@@ -129,28 +133,96 @@ export class SubscriptionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getRevenueStats() {
|
async getRevenueStats() {
|
||||||
const [activeCount, totalRevenue, monthlyRevenue] = await Promise.all([
|
try {
|
||||||
this.prisma.agentSubscription.count({ where: { status: 'ACTIVE' } }),
|
const stripe = this.stripeService.getStripe();
|
||||||
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 {
|
// Count active subscriptions from Stripe
|
||||||
activeSubscriptions: activeCount,
|
let activeCount = 0;
|
||||||
totalRevenue: (totalRevenue._sum.amount || 0) / 100,
|
let hasMore = true;
|
||||||
monthlyRevenue: (monthlyRevenue._sum.amount || 0) / 100,
|
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) {
|
private async updateAgentSubscriptionStatus(userId: string, status: SubscriptionStatus) {
|
||||||
|
|||||||
Reference in New Issue
Block a user