Add tier-based scan scheduler and webhook triggers (FRE-4498)
- ScanScheduler: tier-based scheduling (BASIC=24h, PLUS=6h, PREMIUM=1h) - WebhookHandler: HMAC-verified webhook ingestion with SCAN_TRIGGER support - API routes: /scheduler and /webhooks endpoints under /api/v1/darkwatch - Jobs: scheduled scan checker + webhook retry processor via BullMQ - Schema: ScanSchedule, WebhookEvent models; ScanJob.scheduledBy field - Types: ScheduleStatus, WebhookEventType, WebhookTriggerInput - Tests: scheduler lifecycle + webhook signature/processing tests Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
94
packages/shared-billing/src/config/billing.config.ts
Normal file
94
packages/shared-billing/src/config/billing.config.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const SubscriptionTier = {
|
||||
FREE: 'free',
|
||||
BASIC: 'basic',
|
||||
PLUS: 'plus',
|
||||
PREMIUM: 'premium',
|
||||
} as const;
|
||||
|
||||
export type SubscriptionTier = typeof SubscriptionTier[keyof typeof SubscriptionTier];
|
||||
|
||||
export const BillingConfigSchema = z.object({
|
||||
stripe: z.object({
|
||||
apiKey: z.string().min(1, 'STRIPE_API_KEY required'),
|
||||
webhookSecret: z.string().min(1, 'STRIPE_WEBHOOK_SECRET required'),
|
||||
pricingTableId: z.string().optional(),
|
||||
}),
|
||||
tiers: z.object({
|
||||
free: z.object({
|
||||
priceId: z.string(),
|
||||
monthlyPriceCents: z.number().default(0),
|
||||
callMinutesLimit: z.number().default(100),
|
||||
smsCountLimit: z.number().default(500),
|
||||
darkWebScans: z.number().default(1),
|
||||
}),
|
||||
basic: z.object({
|
||||
priceId: z.string(),
|
||||
monthlyPriceCents: z.number().default(999),
|
||||
callMinutesLimit: z.number().default(500),
|
||||
smsCountLimit: z.number().default(2000),
|
||||
darkWebScans: z.number().default(12),
|
||||
}),
|
||||
plus: z.object({
|
||||
priceId: z.string(),
|
||||
monthlyPriceCents: z.number().default(1999),
|
||||
callMinutesLimit: z.number().default(2000),
|
||||
smsCountLimit: z.number().default(10000),
|
||||
darkWebScans: z.number().default(12),
|
||||
voiceCloning: z.boolean().default(true),
|
||||
}),
|
||||
premium: z.object({
|
||||
priceId: z.string(),
|
||||
monthlyPriceCents: z.number().default(4999),
|
||||
callMinutesLimit: z.number().default(10000),
|
||||
smsCountLimit: z.number().default(50000),
|
||||
darkWebScans: z.number().default(12),
|
||||
voiceCloning: z.boolean().default(true),
|
||||
homeTitleMonitor: z.boolean().default(true),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export type BillingConfig = z.infer<typeof BillingConfigSchema>;
|
||||
|
||||
export const loadBillingConfig = (): BillingConfig => ({
|
||||
stripe: {
|
||||
apiKey: process.env.STRIPE_API_KEY!,
|
||||
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
|
||||
pricingTableId: process.env.STRIPE_PRICING_TABLE_ID,
|
||||
},
|
||||
tiers: {
|
||||
free: {
|
||||
priceId: process.env.STRIPE_FREE_TIER_PRICE_ID || 'price_free',
|
||||
monthlyPriceCents: 0,
|
||||
callMinutesLimit: 100,
|
||||
smsCountLimit: 500,
|
||||
darkWebScans: 1,
|
||||
},
|
||||
basic: {
|
||||
priceId: process.env.STRIPE_BASIC_TIER_PRICE_ID || 'price_basic',
|
||||
monthlyPriceCents: 999,
|
||||
callMinutesLimit: 500,
|
||||
smsCountLimit: 2000,
|
||||
darkWebScans: 12,
|
||||
},
|
||||
plus: {
|
||||
priceId: process.env.STRIPE_PLUS_TIER_PRICE_ID!,
|
||||
monthlyPriceCents: 1999,
|
||||
callMinutesLimit: 2000,
|
||||
smsCountLimit: 10000,
|
||||
darkWebScans: 12,
|
||||
voiceCloning: true,
|
||||
},
|
||||
premium: {
|
||||
priceId: process.env.STRIPE_PREMIUM_TIER_PRICE_ID!,
|
||||
monthlyPriceCents: 4999,
|
||||
callMinutesLimit: 10000,
|
||||
smsCountLimit: 50000,
|
||||
darkWebScans: 12,
|
||||
voiceCloning: true,
|
||||
homeTitleMonitor: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user