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 Stripe from 'stripe';
|
||||||
import { loadBillingConfig, SubscriptionTier } from '../config/billing.config';
|
import { loadBillingConfig, SubscriptionTier } from '../config/billing.config';
|
||||||
|
import { RedisService } from '@shieldsai/shared-notifications';
|
||||||
import type { Subscription, SubscriptionCreateSchema, SubscriptionUpdateSchema } from '../models/subscription.model';
|
import type { Subscription, SubscriptionCreateSchema, SubscriptionUpdateSchema } from '../models/subscription.model';
|
||||||
|
|
||||||
const config = loadBillingConfig();
|
const config = loadBillingConfig();
|
||||||
const stripe = new Stripe(config.stripe.apiKey, { apiVersion: '2023-10-16' });
|
const stripe = new Stripe(config.stripe.apiKey, { apiVersion: '2023-10-16' });
|
||||||
|
const redis = RedisService.getInstance();
|
||||||
const processedEvents = new Map<string, number>();
|
const IDEMPOTENCY_TTL_SECONDS = 24 * 60 * 60;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BillingService {
|
export class BillingService {
|
||||||
private static instance: BillingService;
|
private static instance: BillingService;
|
||||||
@@ -267,14 +258,16 @@ export class BillingService {
|
|||||||
): Promise<Stripe.Event | null> {
|
): Promise<Stripe.Event | null> {
|
||||||
const event = stripe.webhooks.constructEvent(body, sig, config.stripe.webhookSecret);
|
const event = stripe.webhooks.constructEvent(body, sig, config.stripe.webhookSecret);
|
||||||
|
|
||||||
cleanupOldEvents();
|
const wasNew = await redis.setIfNotExists(
|
||||||
|
`stripe:event:${event.id}`,
|
||||||
|
'1',
|
||||||
|
IDEMPOTENCY_TTL_SECONDS
|
||||||
|
);
|
||||||
|
|
||||||
if (processedEvents.has(event.id)) {
|
if (!wasNew) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
processedEvents.set(event.id, Date.now());
|
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user