FRE-5401: Migrate webhook idempotency to distributed Redis store
Replace in-memory Map<string, number> with Redis-based idempotency using setIfNotExists (NX) for distributed multi-instance deployments. Removes cleanupOldEvents (no longer needed with Redis TTL). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,21 +1,12 @@
|
||||
import Stripe from 'stripe';
|
||||
import { loadBillingConfig, SubscriptionTier } from '../config/billing.config';
|
||||
import { RedisService } from '@shieldsai/shared-notifications';
|
||||
import type { Subscription, SubscriptionCreateSchema, SubscriptionUpdateSchema } from '../models/subscription.model';
|
||||
|
||||
const config = loadBillingConfig();
|
||||
const stripe = new Stripe(config.stripe.apiKey, { apiVersion: '2023-10-16' });
|
||||
|
||||
const processedEvents = new Map<string, number>();
|
||||
const IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
function cleanupOldEvents(): void {
|
||||
const now = Date.now();
|
||||
for (const [eventId, timestamp] of processedEvents.entries()) {
|
||||
if (now - timestamp > IDEMPOTENCY_TTL_MS) {
|
||||
processedEvents.delete(eventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
const redis = RedisService.getInstance();
|
||||
const IDEMPOTENCY_TTL_SECONDS = 24 * 60 * 60;
|
||||
|
||||
export class BillingService {
|
||||
private static instance: BillingService;
|
||||
@@ -266,15 +257,17 @@ export class BillingService {
|
||||
body: Buffer
|
||||
): Promise<Stripe.Event | null> {
|
||||
const event = stripe.webhooks.constructEvent(body, sig, config.stripe.webhookSecret);
|
||||
|
||||
cleanupOldEvents();
|
||||
|
||||
if (processedEvents.has(event.id)) {
|
||||
const wasNew = await redis.setIfNotExists(
|
||||
`stripe:event:${event.id}`,
|
||||
'1',
|
||||
IDEMPOTENCY_TTL_SECONDS
|
||||
);
|
||||
|
||||
if (!wasNew) {
|
||||
return null;
|
||||
}
|
||||
|
||||
processedEvents.set(event.id, Date.now());
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user