feat: implement audit logging system with service, controller, and interceptor to track user actions across core services
This commit is contained in:
@@ -14,6 +14,8 @@ import { StripeService } from './stripe.service';
|
||||
import { SubscriptionService } from './subscription.service';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import { SubscriptionStatus } from '@prisma/client';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { AuditAction } from '../audit/audit.constants';
|
||||
|
||||
@ApiTags('Stripe Webhooks')
|
||||
@Controller('stripe')
|
||||
@@ -23,6 +25,7 @@ export class StripeWebhookController {
|
||||
constructor(
|
||||
private stripeService: StripeService,
|
||||
private subscriptionService: SubscriptionService,
|
||||
private audit: AuditService,
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@@ -131,6 +134,14 @@ export class StripeWebhookController {
|
||||
currentPeriodEnd,
|
||||
});
|
||||
|
||||
this.audit.log({
|
||||
action: AuditAction.SUBSCRIPTION_CREATE,
|
||||
resourceType: 'AgentSubscription',
|
||||
resourceId: stripeSubscriptionId,
|
||||
metadata: { userId, planId, stripeCustomerId },
|
||||
actorOverride: { id: null, role: 'SYSTEM' },
|
||||
});
|
||||
|
||||
this.logger.log(`Created subscription for user ${userId}, plan ${planId}`);
|
||||
}
|
||||
|
||||
@@ -156,6 +167,19 @@ export class StripeWebhookController {
|
||||
receiptUrl: invoice.hosted_invoice_url,
|
||||
});
|
||||
|
||||
this.audit.log({
|
||||
action: AuditAction.PAYMENT_SUCCESS,
|
||||
resourceType: 'Payment',
|
||||
resourceId: invoice.id,
|
||||
metadata: {
|
||||
userId: subscription.userId,
|
||||
amount: invoice.amount_paid,
|
||||
currency: invoice.currency,
|
||||
stripeSubscriptionId,
|
||||
},
|
||||
actorOverride: { id: null, role: 'SYSTEM' },
|
||||
});
|
||||
|
||||
// Update subscription period
|
||||
const periodEnd = invoice.lines?.data?.[0]?.period?.end;
|
||||
const periodStart = invoice.lines?.data?.[0]?.period?.start;
|
||||
@@ -193,6 +217,19 @@ export class StripeWebhookController {
|
||||
SubscriptionStatus.PAST_DUE,
|
||||
);
|
||||
|
||||
this.audit.log({
|
||||
action: AuditAction.PAYMENT_FAILURE,
|
||||
resourceType: 'Payment',
|
||||
resourceId: invoice.id,
|
||||
metadata: {
|
||||
userId: subscription.userId,
|
||||
amount: invoice.amount_due,
|
||||
currency: invoice.currency,
|
||||
stripeSubscriptionId,
|
||||
},
|
||||
actorOverride: { id: null, role: 'SYSTEM' },
|
||||
});
|
||||
|
||||
this.logger.warn(`Payment failed for subscription ${stripeSubscriptionId}`);
|
||||
}
|
||||
|
||||
@@ -224,6 +261,14 @@ export class StripeWebhookController {
|
||||
{ canceledAt: new Date() },
|
||||
);
|
||||
|
||||
this.audit.log({
|
||||
action: AuditAction.SUBSCRIPTION_CANCEL,
|
||||
resourceType: 'AgentSubscription',
|
||||
resourceId: subscription.id,
|
||||
metadata: { source: 'stripe_webhook' },
|
||||
actorOverride: { id: null, role: 'SYSTEM' },
|
||||
});
|
||||
|
||||
this.logger.log(`Subscription ${subscription.id} cancelled`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Injectable, Logger, BadRequestException } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import Stripe from 'stripe';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { AuditAction } from '../audit/audit.constants';
|
||||
|
||||
@Injectable()
|
||||
export class StripeService {
|
||||
@@ -11,6 +13,7 @@ export class StripeService {
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private prisma: PrismaService,
|
||||
private audit: AuditService,
|
||||
) {
|
||||
const secretKey = this.configService.get<string>('stripe.secretKey');
|
||||
if (secretKey) {
|
||||
@@ -180,13 +183,20 @@ export class StripeService {
|
||||
stripeSubscriptionId: string,
|
||||
atPeriodEnd = true,
|
||||
): Promise<Stripe.Subscription> {
|
||||
if (atPeriodEnd) {
|
||||
return this.getStripe().subscriptions.update(stripeSubscriptionId, {
|
||||
cancel_at_period_end: true,
|
||||
});
|
||||
}
|
||||
const result = atPeriodEnd
|
||||
? await this.getStripe().subscriptions.update(stripeSubscriptionId, {
|
||||
cancel_at_period_end: true,
|
||||
})
|
||||
: await this.getStripe().subscriptions.cancel(stripeSubscriptionId);
|
||||
|
||||
return this.getStripe().subscriptions.cancel(stripeSubscriptionId);
|
||||
this.audit.log({
|
||||
action: AuditAction.SUBSCRIPTION_CANCEL,
|
||||
resourceType: 'AgentSubscription',
|
||||
resourceId: stripeSubscriptionId,
|
||||
metadata: { atPeriodEnd, source: 'agent_action' },
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async getSubscription(
|
||||
|
||||
Reference in New Issue
Block a user