PromptBase
Upgrade
Close icon
General
Home
Marketplace
Create
Hire
Login
Chat
Sell
Explore

Prompt Details

Model
(claude-4-6-opus)
Token size
950
Example input
[PROJECT_DESCRIPTION]: A subscription billing engine for SaaS companies. Handles plan management, usage metering, invoice generation, payment processing via Stripe, and dunning workflows for failed payments. Exposes a REST API for frontend integration and sends webhook events for external systems. [TECH_STACK]: Node.js 20, Express, PostgreSQL, Prisma ORM, Stripe SDK, BullMQ for job queues [INTERFACES]: REST API + webhook emitter + CLI for admin operations [PROJECT_SCALE]: Growth-stage [CONSTRAINTS]: Must pass SOC2 audit requirements, all financial calculations in cents to avoid floating point errors, 99.9% uptime target for payment endpoints
Example output
Now I'll create the full technical specification document. This is a substantial deliverable, so I'll build it as a well-structured markdown file.Here's the complete technical specification for the billing engine. The document covers all 10 sections with no TBDs or placeholders — every file in the directory tree has a matching contract, all data models trace to the file contracts that reference them, and architectural decisions link to specific components. A few things worth noting about the design choices: the billing anchor day is capped at 28 to avoid February edge cases, the audit log table is enforced as append-only at the database role level (not just application logic) to satisfy SOC2, and the dunning schedule is configurable via environment variable so you can tune retry timing without redeployment. The Stripe integration is fully isolated behind `StripeGateway` so you can swap or extend payment processors later without touching domain logic. -- # Technical Project Specification: Subscription Billing Engine --- ## 1. Project Overview **Repository name:** `billing-engine` The billing engine is a backend service that manages the full subscription lifecycle for SaaS companies. It handles plan catalog management, customer subscriptions with usage-based metering, automated invoice generation, payment processing through Stripe, and dunning workflows for recovering failed payments. The system exposes a versioned REST API for frontend and service integration, emits webhook events for external system consumption, and provides an admin CLI for operational tasks such as manual invoice regeneration, subscription corrections, and system health checks. All monetary values are stored and computed as integers in cents to eliminate floating-point rounding errors. The system is designed to meet SOC2 audit requirements through comprehensive audit logging, encryption at rest, and strict access control. **Target users:** Engineering teams at growth-stage SaaS companies who need a reliable billing backend they integrate with via API. Secondary users are finance and operations staff who interact through the admin CLI. **Core value proposition:** A single service that owns the subscription-to-payment pipeline, reducing the integration surface with Stripe to one well-tested boundary and providing deterministic invoice calculations that survive audit scrutiny. --- ## 2. Scope Contract ### In Scope 1. Plan catalog CRUD — create, read, update, and archive pricing plans with flat-rate and per-unit pricing models. 2. Customer management — create and update customer records linked to Stripe customer objects. 3. Subscription lifecycle — create, upgrade, downgrade, cancel, and reactivate subscriptions with proration logic. 4. Usage metering — ingest usage events, aggregate them per billing period, and apply them to invoices. 5. Invoice generation — automated and on-demand invoice creation with line-item detail, tax placeholders, and PDF-ready data. 6. Payment processing — charge invoices via Stripe, handle SCA/3DS confirmation flows, and record payment outcomes. 7. Dunning workflows — configurable retry schedules for failed payments with escalation actions (email notification events, subscription pause, subscription cancellation). 8. Webhook emitter — emit events for subscription changes, invoice finalization, payment success/failure, and dunning state transitions. 9. Stripe webhook ingestion — consume Stripe webhook events for payment intent updates, dispute notifications, and charge refunds. 10. Admin CLI — command-line tool for plan management, manual invoice operations, subscription corrections, dunning overrides, and audit log queries. 11. Audit logging — append-only log of every state-changing operation with actor, timestamp, resource, and before/after snapshots. 12. API key authentication — API key issuance, validation, and revocation for service-to-service integration. 13. Health and readiness endpoints — for load balancers and monitoring systems. 14. Database migrations — versioned schema migrations managed through Prisma. 15. Job queue processing — background job execution for invoice generation, payment retries, webhook delivery, and usage aggregation. ### Out of Scope - **Multi-currency support** — all amounts are in a single currency (USD cents). Multi-currency adds exchange rate complexity that is premature at growth stage. - **Tax calculation** — invoices carry a `taxAmountCents` field but the engine does not compute tax. Integration with a tax provider (Avalara, TaxJar) is an upgrade path. - **Customer-facing UI** — the engine is API-only. Frontend billing portals are the consumer's responsibility. - **Email delivery** — the engine emits webhook events for email triggers but does not send emails itself. An external email service consumes these events. - **Multi-tenancy isolation** — the system serves a single SaaS operator. Multi-tenant mode is an upgrade path. - **Real-time analytics dashboards** — MRR, churn, and cohort metrics are queryable via CLI but there is no built-in dashboard. ### Upgrade Paths - **Tax provider integration** — the invoice model reserves `taxAmountCents` and `taxRateId` fields. A future tax module can populate these before finalization. - **Multi-currency** — the `Plan` and `Invoice` models include a `currency` field defaulting to `usd`. A currency conversion layer can be introduced without schema changes. - **Multi-tenancy** — adding an `organizationId` foreign key to all core models and a tenant-resolution middleware is a non-breaking extension. - **Stripe Connect** — the Stripe integration layer is isolated behind a service boundary, making it possible to swap in Connect-based flows for marketplace billing. --- ## 3. Architecture Overview ### System Components | Component | Responsibility | |-----------|----------------| | **API Server** | Express HTTP server. Handles REST requests, authenticates via API keys, validates input, delegates to domain services, returns JSON responses. | | **Domain Services** | Pure business logic layer. Plan rules, subscription state machine, proration calculator, invoice builder, dunning state machine. No I/O — receives and returns plain objects. | | **Repository Layer** | Data access through Prisma. Each aggregate root (Plan, Customer, Subscription, Invoice, Payment) has a dedicated repository. All writes go through repositories to ensure audit logging. | | **Stripe Gateway** | Thin adapter around the Stripe Node SDK. Maps internal models to Stripe API calls and Stripe events back to internal models. Isolates Stripe version-specific logic. | | **Job Processors** | BullMQ workers that execute background tasks: invoice generation cycles, payment retry attempts, webhook delivery, and usage aggregation. | | **Webhook Emitter** | Constructs event payloads, signs them with HMAC-SHA256, and enqueues delivery jobs. Handles retry with exponential backoff. | | **Stripe Webhook Handler** | Receives inbound Stripe webhooks, verifies signatures, and dispatches to the appropriate domain service. | | **Admin CLI** | Node.js CLI using Commander.js. Calls domain services directly (same process, no HTTP). Used for operational tasks and data corrections. | | **Audit Logger** | Middleware and utility that writes append-only audit records for every state change. Records actor, action, resource type, resource ID, timestamp, and JSON diff. | | **Auth Middleware** | Extracts API key from `Authorization` header, validates against hashed keys in the database, attaches the authenticated principal to the request context. | ### Communication Patterns - **API ↔ Client:** Synchronous HTTP/JSON over REST. Request-response. All endpoints return within a 30-second timeout. - **API → Job Queue:** Asynchronous. The API enqueues jobs to BullMQ (backed by Redis) and returns immediately. Used for invoice generation, payment processing, webhook delivery, and usage aggregation. - **Job Workers → Stripe:** Synchronous HTTP calls to Stripe API from within job processors. Retried by BullMQ on failure. - **Stripe → API:** Inbound webhooks. Stripe sends HTTP POST to `/webhooks/stripe`. Verified and processed synchronously; long-running reactions are enqueued as jobs. - **API → External Systems (outbound webhooks):** Asynchronous. Webhook delivery jobs POST signed JSON payloads to subscriber URLs. Retried with exponential backoff (1m, 5m, 30m, 2h, 12h) up to 5 attempts. ### Data Flow: Subscription Creation to First Invoice 1. Client POSTs to `/v1/subscriptions` with `customerId`, `planId`, and optional `startDate`. 2. Auth middleware validates the API key. 3. Request validation middleware checks the schema. 4. `SubscriptionService.create()` verifies the customer and plan exist, checks for conflicting active subscriptions, computes the billing anchor date, and returns a subscription object with status `active`. 5. `SubscriptionRepository.create()` persists the subscription and writes an audit log entry within a single database transaction. 6. The controller enqueues a `subscription.created` webhook event and an `invoice.generate` job for the first billing period. 7. The `InvoiceWorker` picks up the job, calls `InvoiceService.generate()` which computes line items from the plan price and any prorated amounts, then persists the invoice with status `open`. 8. The worker enqueues a `payment.process` job referencing the new invoice. 9. The `PaymentWorker` calls `StripeGateway.createPaymentIntent()`, records the payment attempt, and waits for Stripe's async confirmation via webhook. 10. Stripe webhook arrives at `/webhooks/stripe` confirming `payment_intent.succeeded`. The handler updates the invoice status to `paid` and the payment record to `succeeded`. A `payment.succeeded` webhook event is emitted. ### External Services | Service | Integration Approach | |---------|---------------------| | **Stripe** | Server-side SDK (`stripe` npm package). All calls routed through `StripeGateway` class. Webhook signature verification with `stripe.webhooks.constructEvent()`. | | **Redis** | Used exclusively as the BullMQ job queue backend. No application-level caching. Connected via `ioredis`. | | **PostgreSQL** | Primary data store. Accessed through Prisma ORM. Connection pooling via Prisma's built-in pool. | ### Architectural Decisions | Decision | Rationale | |----------|-----------| | **All money in integer cents** | Eliminates floating-point rounding errors in financial calculations. Required by the project constraints and expected by auditors. | | **Stripe as the sole payment processor** | Reduces the integration surface. The `StripeGateway` abstraction allows swapping processors later, but the current scope is Stripe-only. | | **BullMQ over cron-based scheduling** | Job queues provide individual job retries, backoff, dead-letter handling, and concurrency control. Cron-based approaches lack per-job failure isolation. | | **Repository pattern over direct Prisma calls** | Centralizes audit logging, soft-delete logic, and transaction boundaries. Domain services remain testable without database mocking. | | **Webhook delivery as background jobs** | Decouples the API response time from external system latency. Failed deliveries retry independently without blocking the billing pipeline. | | **Admin CLI shares domain services** | Avoids duplicating business logic between API and CLI. The CLI imports and calls the same service layer. | | **Separate Stripe webhook endpoint** | Stripe webhook verification has its own middleware (raw body parsing). Keeping it on a dedicated route avoids conflicts with the JSON body parser used by the REST API. | | **Append-only audit log table** | SOC2 requires tamper-evident audit trails. The audit table has no UPDATE or DELETE operations; the application user's database role enforces this. | ### Design Patterns - **Service Layer** — domain services encapsulate business rules and orchestrate repository calls. Controllers are thin. - **Repository Pattern** — each aggregate root has a repository that owns its query and persistence logic. - **Gateway Pattern** — `StripeGateway` wraps the external Stripe SDK behind an internal interface, enabling testing with a fake gateway. - **State Machine** — subscription status transitions (`active → past_due → canceled`) and dunning steps are modeled as explicit state machines with guard conditions. - **Event Emitter (internal)** — domain services emit internal events (Node.js `EventEmitter`) that trigger webhook construction and audit log writes, keeping cross-cutting concerns decoupled from business logic. - **Job Queue / Worker** — BullMQ queues decouple time-intensive or retry-prone operations from the synchronous request path. --- ## 4. Dependency Map ### Runtime Dependencies | Package | Version Constraint | Purpose | |---------|--------------------|---------| | `express` | `^4.21` | HTTP server and routing | | `stripe` | `^17.0` | Stripe API client | | `@prisma/client` | `^6.0` | Database ORM client | | `bullmq` | `^5.0` | Job queue for background processing | | `ioredis` | `^5.4` | Redis client (required by BullMQ) | | `zod` | `^3.23` | Request and data validation | | `helmet` | `^8.0` | Security headers | | `cors` | `^2.8` | CORS configuration | | `morgan` | `^1.10` | HTTP request logging | | `winston` | `^3.14` | Structured application logging | | `commander` | `^12.0` | CLI framework | | `uuid` | `^10.0` | UUID generation for idempotency keys and resource IDs | | `jsonwebtoken` | `^9.0` | HMAC-SHA256 signing for outbound webhooks | | `dotenv` | `^16.4` | Environment variable loading | | `http-errors` | `^2.0` | Standardized HTTP error creation | | `express-rate-limit` | `^7.4` | Rate limiting for API and webhook endpoints | ### Dev Dependencies | Package | Version Constraint | Purpose | |---------|--------------------|---------| | `prisma` | `^6.0` | Schema management and migration CLI | | `typescript` | `^5.5` | Type checking and compilation | | `@types/express` | `^5.0` | Express type definitions | | `@types/node` | `^20.0` | Node.js type definitions | | `@types/cors` | `^2.8` | CORS type definitions | | `@types/morgan` | `^1.9` | Morgan type definitions | | `@types/uuid` | `^10.0` | UUID type definitions | | `@types/jsonwebtoken` | `^9.0` | JWT type definitions | | `jest` | `^29.7` | Test runner | | `@jest/globals` | `^29.7` | Jest global types | | `ts-jest` | `^29.2` | TypeScript Jest transformer | | `supertest` | `^7.0` | HTTP assertion testing | | `@types/supertest` | `^6.0` | Supertest type definitions | | `eslint` | `^9.0` | Linting | | `@typescript-eslint/parser` | `^8.0` | TypeScript ESLint parser | | `@typescript-eslint/eslint-plugin` | `^8.0` | TypeScript ESLint rules | | `tsx` | `^4.19` | TypeScript execution for development | | `nodemon` | `^3.1` | Development auto-restart | ### Environment Variables | Variable | Type | Default | Description | |----------|------|---------|-------------| | `NODE_ENV` | string | `development` | Runtime environment: `development`, `test`, `production` | | `PORT` | number | `3000` | HTTP server listen port | | `DATABASE_URL` | string | `postgresql://billing:billing@localhost:5432/billing_engine` | PostgreSQL connection string | | `REDIS_URL` | string | `redis://localhost:6379` | Redis connection string for BullMQ | | `STRIPE_SECRET_KEY` | string | `sk_test_xxx` | Stripe API secret key | | `STRIPE_WEBHOOK_SECRET` | string | `whsec_xxx` | Stripe webhook endpoint signing secret | | `WEBHOOK_SIGNING_SECRET` | string | `whsign_local_dev_secret` | HMAC-SHA256 secret for signing outbound webhooks | | `API_KEY_HASH_SALT` | string | `local_dev_salt` | Salt for hashing API keys at rest | | `LOG_LEVEL` | string | `info` | Winston log level: `error`, `warn`, `info`, `debug` | | `DUNNING_MAX_RETRIES` | number | `5` | Maximum payment retry attempts before cancellation | | `DUNNING_SCHEDULE_MINUTES` | string | `60,300,1800,7200,43200` | Comma-separated retry delays in minutes | | `WEBHOOK_DELIVERY_TIMEOUT_MS` | number | `10000` | Timeout for outbound webhook HTTP calls | | `RATE_LIMIT_WINDOW_MS` | number | `60000` | Rate limit window in milliseconds | | `RATE_LIMIT_MAX_REQUESTS` | number | `100` | Maximum requests per window per API key | ### External Services Required | Service | Requirement | |---------|-------------| | **PostgreSQL 15+** | Primary data store. Must support `jsonb` columns for audit log diffs. | | **Redis 7+** | Job queue backend. Persistence not required; jobs are re-enqueued from database state on cold start. | | **Stripe Account** | Test-mode account for development; live-mode for production. Webhook endpoint must be registered in the Stripe dashboard. | --- ## 5. Directory Structure ``` billing-engine/ ā”œā”€ā”€ .env.example ā”œā”€ā”€ .env.test ā”œā”€ā”€ .eslintrc.json ā”œā”€ā”€ .gitignore ā”œā”€ā”€ docker-compose.yml ā”œā”€ā”€ Dockerfile ā”œā”€ā”€ jest.config.ts ā”œā”€ā”€ package.json ā”œā”€ā”€ tsconfig.json ā”œā”€ā”€ README.md ā”œā”€ā”€ prisma/ │ ā”œā”€ā”€ schema.prisma │ └── migrations/ │ └── .gitkeep ā”œā”€ā”€ src/ │ ā”œā”€ā”€ index.ts │ ā”œā”€ā”€ server.ts │ ā”œā”€ā”€ config/ │ │ ā”œā”€ā”€ index.ts │ │ ā”œā”€ā”€ database.ts │ │ ā”œā”€ā”€ redis.ts │ │ ā”œā”€ā”€ stripe.ts │ │ └── logger.ts │ ā”œā”€ā”€ middleware/ │ │ ā”œā”€ā”€ auth.ts │ │ ā”œā”€ā”€ errorHandler.ts │ │ ā”œā”€ā”€ rateLimiter.ts │ │ ā”œā”€ā”€ requestLogger.ts │ │ ā”œā”€ā”€ validateRequest.ts │ │ └── stripeWebhookParser.ts │ ā”œā”€ā”€ routes/ │ │ ā”œā”€ā”€ index.ts │ │ ā”œā”€ā”€ plans.ts │ │ ā”œā”€ā”€ customers.ts │ │ ā”œā”€ā”€ subscriptions.ts │ │ ā”œā”€ā”€ invoices.ts │ │ ā”œā”€ā”€ usage.ts │ │ ā”œā”€ā”€ webhookSubscriptions.ts │ │ └── stripeWebhooks.ts │ ā”œā”€ā”€ controllers/ │ │ ā”œā”€ā”€ planController.ts │ │ ā”œā”€ā”€ customerController.ts │ │ ā”œā”€ā”€ subscriptionController.ts │ │ ā”œā”€ā”€ invoiceController.ts │ │ ā”œā”€ā”€ usageController.ts │ │ ā”œā”€ā”€ webhookSubscriptionController.ts │ │ └── stripeWebhookController.ts │ ā”œā”€ā”€ services/ │ │ ā”œā”€ā”€ planService.ts │ │ ā”œā”€ā”€ customerService.ts │ │ ā”œā”€ā”€ subscriptionService.ts │ │ ā”œā”€ā”€ invoiceService.ts │ │ ā”œā”€ā”€ usageService.ts │ │ ā”œā”€ā”€ paymentService.ts │ │ ā”œā”€ā”€ dunningService.ts │ │ ā”œā”€ā”€ webhookService.ts │ │ └── auditService.ts │ ā”œā”€ā”€ repositories/ │ │ ā”œā”€ā”€ planRepository.ts │ │ ā”œā”€ā”€ customerRepository.ts │ │ ā”œā”€ā”€ subscriptionRepository.ts │ │ ā”œā”€ā”€ invoiceRepository.ts │ │ ā”œā”€ā”€ usageRepository.ts │ │ ā”œā”€ā”€ paymentRepository.ts │ │ ā”œā”€ā”€ dunningRepository.ts │ │ ā”œā”€ā”€ webhookSubscriptionRepository.ts │ │ ā”œā”€ā”€ webhookEventRepository.ts │ │ ā”œā”€ā”€ auditLogRepository.ts │ │ └── apiKeyRepository.ts │ ā”œā”€ā”€ gateways/ │ │ └── stripeGateway.ts │ ā”œā”€ā”€ queues/ │ │ ā”œā”€ā”€ index.ts │ │ ā”œā”€ā”€ invoiceQueue.ts │ │ ā”œā”€ā”€ paymentQueue.ts │ │ ā”œā”€ā”€ webhookQueue.ts │ │ └── usageQueue.ts │ ā”œā”€ā”€ workers/ │ │ ā”œā”€ā”€ index.ts │ │ ā”œā”€ā”€ invoiceWorker.ts │ │ ā”œā”€ā”€ paymentWorker.ts │ │ ā”œā”€ā”€ webhookWorker.ts │ │ └── usageWorker.ts │ ā”œā”€ā”€ schemas/ │ │ ā”œā”€ā”€ plan.ts │ │ ā”œā”€ā”€ customer.ts │ │ ā”œā”€ā”€ subscription.ts │ │ ā”œā”€ā”€ invoice.ts │ │ ā”œā”€ā”€ usage.ts │ │ ā”œā”€ā”€ webhookSubscription.ts │ │ └── common.ts │ ā”œā”€ā”€ errors/ │ │ ā”œā”€ā”€ index.ts │ │ ā”œā”€ā”€ AppError.ts │ │ ā”œā”€ā”€ ValidationError.ts │ │ ā”œā”€ā”€ NotFoundError.ts │ │ ā”œā”€ā”€ ConflictError.ts │ │ ā”œā”€ā”€ StripeError.ts │ │ └── AuthenticationError.ts │ ā”œā”€ā”€ types/ │ │ ā”œā”€ā”€ index.ts │ │ ā”œā”€ā”€ plan.ts │ │ ā”œā”€ā”€ customer.ts │ │ ā”œā”€ā”€ subscription.ts │ │ ā”œā”€ā”€ invoice.ts │ │ ā”œā”€ā”€ usage.ts │ │ ā”œā”€ā”€ payment.ts │ │ ā”œā”€ā”€ webhook.ts │ │ ā”œā”€ā”€ dunning.ts │ │ └── audit.ts │ └── utils/ │ ā”œā”€ā”€ money.ts │ ā”œā”€ā”€ dateUtils.ts │ ā”œā”€ā”€ idGenerator.ts │ ā”œā”€ā”€ hmac.ts │ └── pagination.ts ā”œā”€ā”€ cli/ │ ā”œā”€ā”€ index.ts │ ā”œā”€ā”€ commands/ │ │ ā”œā”€ā”€ plans.ts │ │ ā”œā”€ā”€ subscriptions.ts │ │ ā”œā”€ā”€ invoices.ts │ │ ā”œā”€ā”€ dunning.ts │ │ ā”œā”€ā”€ webhooks.ts │ │ ā”œā”€ā”€ apiKeys.ts │ │ └── audit.ts │ └── utils/ │ ā”œā”€ā”€ output.ts │ └── prompts.ts └── tests/ ā”œā”€ā”€ setup.ts ā”œā”€ā”€ helpers/ │ ā”œā”€ā”€ factories.ts │ ā”œā”€ā”€ testDb.ts │ ā”œā”€ā”€ testRedis.ts │ └── mockStripeGateway.ts ā”œā”€ā”€ unit/ │ ā”œā”€ā”€ services/ │ │ ā”œā”€ā”€ planService.test.ts │ │ ā”œā”€ā”€ subscriptionService.test.ts │ │ ā”œā”€ā”€ invoiceService.test.ts │ │ ā”œā”€ā”€ usageService.test.ts │ │ ā”œā”€ā”€ paymentService.test.ts │ │ ā”œā”€ā”€ dunningService.test.ts │ │ └── webhookService.test.ts │ ā”œā”€ā”€ utils/ │ │ ā”œā”€ā”€ money.test.ts │ │ └── dateUtils.test.ts │ └── middleware/ │ ā”œā”€ā”€ auth.test.ts │ └── validateRequest.test.ts ā”œā”€ā”€ integration/ │ ā”œā”€ā”€ repositories/ │ │ ā”œā”€ā”€ planRepository.test.ts │ │ ā”œā”€ā”€ subscriptionRepository.test.ts │ │ ā”œā”€ā”€ invoiceRepository.test.ts │ │ └── auditLogRepository.test.ts │ ā”œā”€ā”€ workers/ │ │ ā”œā”€ā”€ invoiceWorker.test.ts │ │ ā”œā”€ā”€ paymentWorker.test.ts │ │ └── webhookWorker.test.ts │ └── stripeGateway.test.ts ā”œā”€ā”€ e2e/ │ ā”œā”€ā”€ plans.test.ts │ ā”œā”€ā”€ subscriptions.test.ts │ ā”œā”€ā”€ invoices.test.ts │ ā”œā”€ā”€ usage.test.ts │ ā”œā”€ā”€ webhooks.test.ts │ └── dunning.test.ts └── smoke/ └── health.test.ts ``` --- ## 6. File Contracts ### Root Configuration Files **`.env.example`** - Purpose: Template of all required environment variables with placeholder values. - Exports: none (configuration file). - Depends on: nothing. **`.env.test`** - Purpose: Environment overrides for the test suite — test database URL, test Redis URL, Stripe test keys. - Exports: none (configuration file). - Depends on: nothing. **`.eslintrc.json`** - Purpose: ESLint configuration with TypeScript plugin rules. - Exports: none (configuration file). - Depends on: nothing. **`.gitignore`** - Purpose: Excludes `node_modules`, `dist`, `.env`, Prisma generated client, and coverage output. - Exports: none (configuration file). - Depends on: nothing. **`docker-compose.yml`** - Purpose: Defines PostgreSQL, Redis, and the billing-engine application containers for local development. - Exports: none (infrastructure file). - Depends on: `Dockerfile`. **`Dockerfile`** - Purpose: Multi-stage build — installs dependencies, compiles TypeScript, runs Prisma generate, and starts the server. - Exports: none (infrastructure file). - Depends on: `package.json`, `tsconfig.json`, `prisma/schema.prisma`. **`jest.config.ts`** - Purpose: Jest configuration — sets `ts-jest` as the transformer, maps path aliases, specifies `tests/setup.ts` as the global setup file, and defines test match patterns. - Exports: Jest configuration object. - Depends on: nothing. **`package.json`** - Purpose: Project metadata, dependency manifest, and npm scripts (`dev`, `build`, `start`, `test`, `test:unit`, `test:integration`, `test:e2e`, `test:smoke`, `lint`, `migrate`, `cli`). - Exports: none (configuration file). - Depends on: nothing. **`tsconfig.json`** - Purpose: TypeScript compiler options — target ES2022, module NodeNext, strict mode, path aliases (`@/` → `src/`), output to `dist/`. - Exports: none (configuration file). - Depends on: nothing. **`README.md`** - Purpose: Project overview, local setup instructions, API endpoint summary, CLI command reference, and deployment notes. - Exports: none (documentation). - Depends on: nothing. ### Prisma **`prisma/schema.prisma`** - Purpose: Defines all database models (Plan, Customer, Subscription, Invoice, InvoiceLine, Payment, UsageEvent, UsageAggregate, DunningState, WebhookSubscription, WebhookEvent, AuditLog, ApiKey), their relations, and indexes. - Exports: Prisma Client (generated). - Depends on: nothing. **`prisma/migrations/.gitkeep`** - Purpose: Placeholder to ensure the migrations directory exists in version control. - Exports: none. - Depends on: nothing. ### Source — Entry Points **`src/index.ts`** - Purpose: Application entry point. Loads environment variables from dotenv, initializes the database connection, starts BullMQ workers, and calls `server.listen()`. - Exports: none (entry point). - Depends on: `src/server.ts`, `src/config/index.ts`, `src/config/database.ts`, `src/workers/index.ts`. **`src/server.ts`** - Purpose: Creates and configures the Express application — registers middleware (helmet, cors, morgan, rate limiter, auth), mounts route modules, and attaches the error handler. - Exports: `app` (Express application instance). - Depends on: `src/middleware/*`, `src/routes/index.ts`, `src/config/logger.ts`. ### Source — Config **`src/config/index.ts`** - Purpose: Reads and validates all environment variables using zod. Exports a frozen configuration object. Fails fast on missing or invalid values. - Exports: `config` object with typed properties for every environment variable. - Depends on: nothing (reads `process.env`). **`src/config/database.ts`** - Purpose: Creates and exports the Prisma Client singleton. Configures connection logging level based on `config.logLevel`. - Exports: `prisma` (PrismaClient instance). - Depends on: `src/config/index.ts`. **`src/config/redis.ts`** - Purpose: Creates and exports the ioredis connection instance used by BullMQ. - Exports: `redisConnection` (IORedis instance). - Depends on: `src/config/index.ts`. **`src/config/stripe.ts`** - Purpose: Creates and exports the configured Stripe SDK instance. - Exports: `stripeClient` (Stripe instance). - Depends on: `src/config/index.ts`. **`src/config/logger.ts`** - Purpose: Creates and exports a Winston logger configured with JSON format for production and colorized console format for development. - Exports: `logger` (Winston Logger instance). - Depends on: `src/config/index.ts`. ### Source — Middleware **`src/middleware/auth.ts`** - Purpose: Extracts the API key from the `Authorization: Bearer <key>` header, hashes it, looks it up in the database, and attaches the authenticated principal to `req.context`. Returns 401 on missing or invalid keys. - Exports: `authenticate` middleware function. - Depends on: `src/repositories/apiKeyRepository.ts`, `src/config/index.ts`, `src/errors/AuthenticationError.ts`. **`src/middleware/errorHandler.ts`** - Purpose: Express error-handling middleware. Catches all errors, maps `AppError` subclasses to appropriate HTTP status codes, logs the error, and returns the standard error response format. - Exports: `errorHandler` middleware function. - Depends on: `src/errors/AppError.ts`, `src/config/logger.ts`. **`src/middleware/rateLimiter.ts`** - Purpose: Configures express-rate-limit with per-API-key windowing using the values from config. - Exports: `rateLimiter` middleware function. - Depends on: `src/config/index.ts`. **`src/middleware/requestLogger.ts`** - Purpose: Configures Morgan HTTP request logging piped through the Winston logger. - Exports: `requestLogger` middleware function. - Depends on: `src/config/logger.ts`. **`src/middleware/validateRequest.ts`** - Purpose: Higher-order middleware that accepts a zod schema and validates `req.body`, `req.params`, and `req.query` against it. Returns 400 with validation error details on failure. - Exports: `validateRequest` function. - Depends on: `src/errors/ValidationError.ts`. **`src/middleware/stripeWebhookParser.ts`** - Purpose: Raw body parser for the Stripe webhook route. Captures the raw request body as a Buffer for signature verification and skips JSON parsing. - Exports: `stripeWebhookParser` middleware function. - Depends on: nothing. ### Source — Routes **`src/routes/index.ts`** - Purpose: Aggregates all route modules and mounts them on the Express app under `/v1/` prefix. Mounts the health check at `/health`. Mounts Stripe webhooks at `/webhooks/stripe` (outside the `/v1/` prefix). - Exports: `registerRoutes` function. - Depends on: `src/routes/plans.ts`, `src/routes/customers.ts`, `src/routes/subscriptions.ts`, `src/routes/invoices.ts`, `src/routes/usage.ts`, `src/routes/webhookSubscriptions.ts`, `src/routes/stripeWebhooks.ts`. **`src/routes/plans.ts`** - Purpose: Defines routes for `POST /v1/plans`, `GET /v1/plans`, `GET /v1/plans/:id`, `PATCH /v1/plans/:id`, `POST /v1/plans/:id/archive`. - Exports: Express Router. - Depends on: `src/controllers/planController.ts`, `src/middleware/validateRequest.ts`, `src/schemas/plan.ts`. **`src/routes/customers.ts`** - Purpose: Defines routes for `POST /v1/customers`, `GET /v1/customers`, `GET /v1/customers/:id`, `PATCH /v1/customers/:id`. - Exports: Express Router. - Depends on: `src/controllers/customerController.ts`, `src/middleware/validateRequest.ts`, `src/schemas/customer.ts`. **`src/routes/subscriptions.ts`** - Purpose: Defines routes for `POST /v1/subscriptions`, `GET /v1/subscriptions`, `GET /v1/subscriptions/:id`, `POST /v1/subscriptions/:id/cancel`, `POST /v1/subscriptions/:id/reactivate`, `POST /v1/subscriptions/:id/change-plan`. - Exports: Express Router. - Depends on: `src/controllers/subscriptionController.ts`, `src/middleware/validateRequest.ts`, `src/schemas/subscription.ts`. **`src/routes/invoices.ts`** - Purpose: Defines routes for `GET /v1/invoices`, `GET /v1/invoices/:id`, `POST /v1/invoices/:id/finalize`, `POST /v1/invoices/:id/void`, `POST /v1/invoices/:id/pay`. - Exports: Express Router. - Depends on: `src/controllers/invoiceController.ts`, `src/middleware/validateRequest.ts`, `src/schemas/invoice.ts`. **`src/routes/usage.ts`** - Purpose: Defines routes for `POST /v1/usage` (ingest event), `GET /v1/usage/subscriptions/:subscriptionId/summary`. - Exports: Express Router. - Depends on: `src/controllers/usageController.ts`, `src/middleware/validateRequest.ts`, `src/schemas/usage.ts`. **`src/routes/webhookSubscriptions.ts`** - Purpose: Defines routes for `POST /v1/webhook-subscriptions`, `GET /v1/webhook-subscriptions`, `DELETE /v1/webhook-subscriptions/:id`. - Exports: Express Router. - Depends on: `src/controllers/webhookSubscriptionController.ts`, `src/middleware/validateRequest.ts`, `src/schemas/webhookSubscription.ts`. **`src/routes/stripeWebhooks.ts`** - Purpose: Defines `POST /webhooks/stripe` with the raw body parser and Stripe signature verification. - Exports: Express Router. - Depends on: `src/controllers/stripeWebhookController.ts`, `src/middleware/stripeWebhookParser.ts`. ### Source — Controllers **`src/controllers/planController.ts`** - Purpose: Handles plan CRUD requests. Calls `PlanService` methods and returns formatted JSON responses. - Exports: `createPlan`, `listPlans`, `getPlan`, `updatePlan`, `archivePlan` handler functions. - Depends on: `src/services/planService.ts`, `src/types/plan.ts`. **`src/controllers/customerController.ts`** - Purpose: Handles customer CRUD requests. Calls `CustomerService` and returns JSON. - Exports: `createCustomer`, `listCustomers`, `getCustomer`, `updateCustomer` handler functions. - Depends on: `src/services/customerService.ts`, `src/types/customer.ts`. **`src/controllers/subscriptionController.ts`** - Purpose: Handles subscription lifecycle requests. Calls `SubscriptionService` and enqueues jobs where needed. - Exports: `createSubscription`, `listSubscriptions`, `getSubscription`, `cancelSubscription`, `reactivateSubscription`, `changePlan` handler functions. - Depends on: `src/services/subscriptionService.ts`, `src/queues/invoiceQueue.ts`, `src/queues/webhookQueue.ts`, `src/types/subscription.ts`. **`src/controllers/invoiceController.ts`** - Purpose: Handles invoice retrieval, finalization, voiding, and manual payment trigger. - Exports: `listInvoices`, `getInvoice`, `finalizeInvoice`, `voidInvoice`, `payInvoice` handler functions. - Depends on: `src/services/invoiceService.ts`, `src/queues/paymentQueue.ts`, `src/queues/webhookQueue.ts`, `src/types/invoice.ts`. **`src/controllers/usageController.ts`** - Purpose: Handles usage event ingestion and usage summary retrieval. - Exports: `ingestUsage`, `getUsageSummary` handler functions. - Depends on: `src/services/usageService.ts`, `src/types/usage.ts`. **`src/controllers/webhookSubscriptionController.ts`** - Purpose: Handles CRUD for outbound webhook subscription registrations. - Exports: `createWebhookSubscription`, `listWebhookSubscriptions`, `deleteWebhookSubscription` handler functions. - Depends on: `src/services/webhookService.ts`, `src/types/webhook.ts`. **`src/controllers/stripeWebhookController.ts`** - Purpose: Receives inbound Stripe webhooks, verifies signatures via the Stripe SDK, and dispatches to the appropriate service method based on event type. - Exports: `handleStripeWebhook` handler function. - Depends on: `src/gateways/stripeGateway.ts`, `src/services/paymentService.ts`, `src/services/subscriptionService.ts`, `src/config/logger.ts`. ### Source — Services **`src/services/planService.ts`** - Purpose: Business logic for plan management — validates plan creation inputs, enforces pricing rules (amounts in cents, valid billing intervals), prevents editing archived plans. - Exports: `PlanService` class with methods `create`, `list`, `getById`, `update`, `archive`. - Depends on: `src/repositories/planRepository.ts`, `src/services/auditService.ts`, `src/types/plan.ts`, `src/errors/index.ts`. **`src/services/customerService.ts`** - Purpose: Business logic for customer management — creates a Stripe customer on creation, syncs email/name changes to Stripe. - Exports: `CustomerService` class with methods `create`, `list`, `getById`, `update`. - Depends on: `src/repositories/customerRepository.ts`, `src/gateways/stripeGateway.ts`, `src/services/auditService.ts`, `src/types/customer.ts`, `src/errors/index.ts`. **`src/services/subscriptionService.ts`** - Purpose: Subscription state machine — enforces valid transitions (active → past_due → canceled, active → canceled, canceled → active for reactivation within grace period). Computes proration on plan changes. Validates no conflicting active subscriptions for a customer. - Exports: `SubscriptionService` class with methods `create`, `list`, `getById`, `cancel`, `reactivate`, `changePlan`, `markPastDue`, `markCanceled`. - Depends on: `src/repositories/subscriptionRepository.ts`, `src/services/planService.ts`, `src/services/auditService.ts`, `src/utils/money.ts`, `src/utils/dateUtils.ts`, `src/types/subscription.ts`, `src/errors/index.ts`. **`src/services/invoiceService.ts`** - Purpose: Generates invoices from subscription and usage data. Computes line items for flat-rate charges, per-unit usage charges, and proration credits/debits. All arithmetic in integer cents. Manages invoice status transitions: `draft → open → paid | void`. - Exports: `InvoiceService` class with methods `generate`, `list`, `getById`, `finalize`, `void`, `markPaid`. - Depends on: `src/repositories/invoiceRepository.ts`, `src/repositories/subscriptionRepository.ts`, `src/services/usageService.ts`, `src/services/auditService.ts`, `src/utils/money.ts`, `src/utils/dateUtils.ts`, `src/types/invoice.ts`, `src/errors/index.ts`. **`src/services/usageService.ts`** - Purpose: Ingests usage events with idempotency key deduplication. Aggregates usage per subscription per billing period. Returns usage summaries. - Exports: `UsageService` class with methods `ingest`, `aggregate`, `getSummary`. - Depends on: `src/repositories/usageRepository.ts`, `src/services/auditService.ts`, `src/types/usage.ts`, `src/errors/index.ts`. **`src/services/paymentService.ts`** - Purpose: Orchestrates payment processing — creates Stripe PaymentIntents for invoices, records payment attempts, handles Stripe webhook confirmations (success, failure, dispute), updates invoice status on payment completion. - Exports: `PaymentService` class with methods `processInvoice`, `handlePaymentSuccess`, `handlePaymentFailure`, `handleDispute`. - Depends on: `src/repositories/paymentRepository.ts`, `src/services/invoiceService.ts`, `src/services/dunningService.ts`, `src/gateways/stripeGateway.ts`, `src/services/auditService.ts`, `src/queues/webhookQueue.ts`, `src/types/payment.ts`, `src/errors/index.ts`. **`src/services/dunningService.ts`** - Purpose: Manages the dunning state machine — tracks retry count and next retry time for each failed invoice, determines the appropriate escalation action (retry, notify, pause subscription, cancel subscription). - Exports: `DunningService` class with methods `initiate`, `scheduleRetry`, `escalate`, `resolve`, `getState`. - Depends on: `src/repositories/dunningRepository.ts`, `src/services/subscriptionService.ts`, `src/services/auditService.ts`, `src/queues/paymentQueue.ts`, `src/queues/webhookQueue.ts`, `src/config/index.ts`, `src/types/dunning.ts`, `src/errors/index.ts`. **`src/services/webhookService.ts`** - Purpose: Manages outbound webhook subscriptions and event delivery. Constructs event payloads, signs them, and enqueues delivery jobs. Tracks delivery attempts and outcomes. - Exports: `WebhookService` class with methods `createSubscription`, `listSubscriptions`, `deleteSubscription`, `emit`, `recordDeliveryAttempt`. - Depends on: `src/repositories/webhookSubscriptionRepository.ts`, `src/repositories/webhookEventRepository.ts`, `src/queues/webhookQueue.ts`, `src/utils/hmac.ts`, `src/services/auditService.ts`, `src/types/webhook.ts`, `src/errors/index.ts`. **`src/services/auditService.ts`** - Purpose: Writes audit log entries. Computes JSON diff between before and after states. Enforces append-only semantics. - Exports: `AuditService` class with methods `log`, `query`. - Depends on: `src/repositories/auditLogRepository.ts`, `src/types/audit.ts`. ### Source — Repositories **`src/repositories/planRepository.ts`** - Purpose: CRUD operations for Plan records. Supports filtering by status (active, archived) and pagination. - Exports: `PlanRepository` class with methods `create`, `findById`, `findMany`, `update`. - Depends on: `src/config/database.ts`, `src/types/plan.ts`. **`src/repositories/customerRepository.ts`** - Purpose: CRUD operations for Customer records. Supports lookup by Stripe customer ID. - Exports: `CustomerRepository` class with methods `create`, `findById`, `findByStripeId`, `findMany`, `update`. - Depends on: `src/config/database.ts`, `src/types/customer.ts`. **`src/repositories/subscriptionRepository.ts`** - Purpose: CRUD operations for Subscription records. Supports querying active subscriptions by customer, subscriptions due for billing, and subscriptions in dunning. - Exports: `SubscriptionRepository` class with methods `create`, `findById`, `findByCustomerId`, `findDueForBilling`, `update`. - Depends on: `src/config/database.ts`, `src/types/subscription.ts`. **`src/repositories/invoiceRepository.ts`** - Purpose: CRUD operations for Invoice and InvoiceLine records. Supports filtering by subscription, status, and date range. - Exports: `InvoiceRepository` class with methods `create`, `findById`, `findBySubscriptionId`, `findMany`, `update`. - Depends on: `src/config/database.ts`, `src/types/invoice.ts`. **`src/repositories/usageRepository.ts`** - Purpose: CRUD operations for UsageEvent and UsageAggregate records. Supports idempotency key lookup and time-range aggregation queries. - Exports: `UsageRepository` class with methods `createEvent`, `findByIdempotencyKey`, `createAggregate`, `findAggregates`, `sumUsage`. - Depends on: `src/config/database.ts`, `src/types/usage.ts`. **`src/repositories/paymentRepository.ts`** - Purpose: CRUD operations for Payment records. Supports lookup by Stripe PaymentIntent ID. - Exports: `PaymentRepository` class with methods `create`, `findById`, `findByStripePaymentIntentId`, `findByInvoiceId`, `update`. - Depends on: `src/config/database.ts`, `src/types/payment.ts`. **`src/repositories/dunningRepository.ts`** - Purpose: CRUD operations for DunningState records. Supports querying states due for retry. - Exports: `DunningRepository` class with methods `create`, `findByInvoiceId`, `findDueForRetry`, `update`. - Depends on: `src/config/database.ts`, `src/types/dunning.ts`. **`src/repositories/webhookSubscriptionRepository.ts`** - Purpose: CRUD operations for WebhookSubscription records (external system registrations for receiving events). - Exports: `WebhookSubscriptionRepository` class with methods `create`, `findById`, `findAll`, `findByEventType`, `delete`. - Depends on: `src/config/database.ts`, `src/types/webhook.ts`. **`src/repositories/webhookEventRepository.ts`** - Purpose: CRUD operations for WebhookEvent records (outbound event delivery log). - Exports: `WebhookEventRepository` class with methods `create`, `findById`, `findBySubscriptionId`, `update`. - Depends on: `src/config/database.ts`, `src/types/webhook.ts`. **`src/repositories/auditLogRepository.ts`** - Purpose: Append-only insert and query operations for AuditLog records. No update or delete methods. - Exports: `AuditLogRepository` class with methods `create`, `query`. - Depends on: `src/config/database.ts`, `src/types/audit.ts`. **`src/repositories/apiKeyRepository.ts`** - Purpose: CRUD operations for ApiKey records. Stores hashed keys only. Supports lookup by key hash and revocation. - Exports: `ApiKeyRepository` class with methods `create`, `findByHash`, `findById`, `revoke`. - Depends on: `src/config/database.ts`. ### Source — Gateways **`src/gateways/stripeGateway.ts`** - Purpose: Adapter wrapping the Stripe SDK. Translates internal models to Stripe API calls and Stripe responses back to internal types. Handles Stripe-specific error mapping. - Exports: `StripeGateway` class with methods `createCustomer`, `updateCustomer`, `createPaymentIntent`, `confirmPaymentIntent`, `createRefund`, `constructWebhookEvent`. - Depends on: `src/config/stripe.ts`, `src/types/payment.ts`, `src/types/customer.ts`, `src/errors/StripeError.ts`. ### Source — Queues **`src/queues/index.ts`** - Purpose: Creates and exports all BullMQ `Queue` instances. Configures default job options (attempts, backoff, removal on completion). - Exports: `invoiceQueue`, `paymentQueue`, `webhookQueue`, `usageQueue`. - Depends on: `src/config/redis.ts`. **`src/queues/invoiceQueue.ts`** - Purpose: Defines job types and helper functions for enqueueing invoice generation jobs (single subscription or bulk billing cycle). - Exports: `enqueueInvoiceGeneration`, `enqueueBillingCycle` functions. - Depends on: `src/queues/index.ts`. **`src/queues/paymentQueue.ts`** - Purpose: Defines job types and helper functions for enqueueing payment processing and retry jobs. - Exports: `enqueuePayment`, `enqueuePaymentRetry` functions. - Depends on: `src/queues/index.ts`. **`src/queues/webhookQueue.ts`** - Purpose: Defines job types and helper functions for enqueueing outbound webhook delivery jobs. - Exports: `enqueueWebhookDelivery` function. - Depends on: `src/queues/index.ts`. **`src/queues/usageQueue.ts`** - Purpose: Defines job types and helper functions for enqueueing usage aggregation jobs. - Exports: `enqueueUsageAggregation` function. - Depends on: `src/queues/index.ts`. ### Source — Workers **`src/workers/index.ts`** - Purpose: Initializes and starts all BullMQ `Worker` instances. Attaches error logging and graceful shutdown handlers. - Exports: `startWorkers`, `stopWorkers` functions. - Depends on: `src/workers/invoiceWorker.ts`, `src/workers/paymentWorker.ts`, `src/workers/webhookWorker.ts`, `src/workers/usageWorker.ts`, `src/config/logger.ts`. **`src/workers/invoiceWorker.ts`** - Purpose: Processes invoice generation jobs. Calls `InvoiceService.generate()` for each due subscription, then enqueues payment jobs for finalized invoices. - Exports: `createInvoiceWorker` function. - Depends on: `src/services/invoiceService.ts`, `src/queues/paymentQueue.ts`, `src/queues/webhookQueue.ts`, `src/config/redis.ts`, `src/config/logger.ts`. **`src/workers/paymentWorker.ts`** - Purpose: Processes payment jobs. Calls `PaymentService.processInvoice()`. On failure, consults `DunningService` for retry scheduling. - Exports: `createPaymentWorker` function. - Depends on: `src/services/paymentService.ts`, `src/services/dunningService.ts`, `src/config/redis.ts`, `src/config/logger.ts`. **`src/workers/webhookWorker.ts`** - Purpose: Processes outbound webhook delivery jobs. Makes HTTP POST requests to registered endpoints with signed payloads. Records delivery outcomes. - Exports: `createWebhookWorker` function. - Depends on: `src/services/webhookService.ts`, `src/utils/hmac.ts`, `src/config/redis.ts`, `src/config/logger.ts`. **`src/workers/usageWorker.ts`** - Purpose: Processes usage aggregation jobs. Sums raw usage events into UsageAggregate records per subscription per billing period. - Exports: `createUsageWorker` function. - Depends on: `src/services/usageService.ts`, `src/config/redis.ts`, `src/config/logger.ts`. ### Source — Schemas **`src/schemas/plan.ts`** - Purpose: Zod schemas for plan API request validation — create, update, and query parameter schemas. - Exports: `createPlanSchema`, `updatePlanSchema`, `planQuerySchema`. - Depends on: `src/schemas/common.ts`. **`src/schemas/customer.ts`** - Purpose: Zod schemas for customer API request validation. - Exports: `createCustomerSchema`, `updateCustomerSchema`, `customerQuerySchema`. - Depends on: `src/schemas/common.ts`. **`src/schemas/subscription.ts`** - Purpose: Zod schemas for subscription API request validation — create, cancel, reactivate, change-plan. - Exports: `createSubscriptionSchema`, `cancelSubscriptionSchema`, `changePlanSchema`, `subscriptionQuerySchema`. - Depends on: `src/schemas/common.ts`. **`src/schemas/invoice.ts`** - Purpose: Zod schemas for invoice API request validation — query filters. - Exports: `invoiceQuerySchema`. - Depends on: `src/schemas/common.ts`. **`src/schemas/usage.ts`** - Purpose: Zod schemas for usage event ingestion and summary query validation. - Exports: `ingestUsageSchema`, `usageSummaryQuerySchema`. - Depends on: `src/schemas/common.ts`. **`src/schemas/webhookSubscription.ts`** - Purpose: Zod schemas for webhook subscription creation and query validation. - Exports: `createWebhookSubscriptionSchema`, `webhookSubscriptionQuerySchema`. - Depends on: `src/schemas/common.ts`. **`src/schemas/common.ts`** - Purpose: Shared zod schemas — pagination parameters, UUID format, ISO date string, positive integer cents. - Exports: `paginationSchema`, `uuidParam`, `isoDateString`, `centsAmount`. - Depends on: nothing. ### Source — Errors **`src/errors/index.ts`** - Purpose: Re-exports all error classes for convenient importing. - Exports: `AppError`, `ValidationError`, `NotFoundError`, `ConflictError`, `StripeError`, `AuthenticationError`. - Depends on: all error files. **`src/errors/AppError.ts`** - Purpose: Base error class with `statusCode`, `code` (machine-readable string), and `isOperational` flag. - Exports: `AppError` class. - Depends on: nothing. **`src/errors/ValidationError.ts`** - Purpose: 400-level error for request validation failures. Carries a `details` array of field-level errors. - Exports: `ValidationError` class (extends `AppError`). - Depends on: `src/errors/AppError.ts`. **`src/errors/NotFoundError.ts`** - Purpose: 404 error for resource-not-found cases. - Exports: `NotFoundError` class (extends `AppError`). - Depends on: `src/errors/AppError.ts`. **`src/errors/ConflictError.ts`** - Purpose: 409 error for state conflicts — duplicate subscriptions, invalid status transitions. - Exports: `ConflictError` class (extends `AppError`). - Depends on: `src/errors/AppError.ts`. **`src/errors/StripeError.ts`** - Purpose: Wraps Stripe SDK errors with internal error codes and appropriate HTTP status mapping. - Exports: `StripeError` class (extends `AppError`). - Depends on: `src/errors/AppError.ts`. **`src/errors/AuthenticationError.ts`** - Purpose: 401 error for missing or invalid API keys. - Exports: `AuthenticationError` class (extends `AppError`). - Depends on: `src/errors/AppError.ts`. ### Source — Types **`src/types/index.ts`** - Purpose: Re-exports all type definitions. - Exports: all types from sibling files. - Depends on: all type files. **`src/types/plan.ts`** - Purpose: TypeScript interfaces for Plan domain — `Plan`, `CreatePlanInput`, `UpdatePlanInput`, `PlanStatus`, `BillingInterval`, `PricingModel`. - Exports: the listed interfaces and enums. - Depends on: nothing. **`src/types/customer.ts`** - Purpose: TypeScript interfaces for Customer domain — `Customer`, `CreateCustomerInput`, `UpdateCustomerInput`. - Exports: the listed interfaces. - Depends on: nothing. **`src/types/subscription.ts`** - Purpose: TypeScript interfaces for Subscription domain — `Subscription`, `CreateSubscriptionInput`, `SubscriptionStatus`, `ChangePlanInput`, `ProrationResult`. - Exports: the listed interfaces and enums. - Depends on: nothing. **`src/types/invoice.ts`** - Purpose: TypeScript interfaces for Invoice domain — `Invoice`, `InvoiceLine`, `InvoiceStatus`, `InvoiceSummary`. - Exports: the listed interfaces and enums. - Depends on: nothing. **`src/types/usage.ts`** - Purpose: TypeScript interfaces for Usage domain — `UsageEvent`, `UsageAggregate`, `IngestUsageInput`, `UsageSummary`. - Exports: the listed interfaces. - Depends on: nothing. **`src/types/payment.ts`** - Purpose: TypeScript interfaces for Payment domain — `Payment`, `PaymentStatus`, `PaymentAttempt`. - Exports: the listed interfaces and enums. - Depends on: nothing. **`src/types/webhook.ts`** - Purpose: TypeScript interfaces for Webhook domain — `WebhookSubscription`, `WebhookEvent`, `WebhookEventType`, `WebhookDeliveryStatus`, `CreateWebhookSubscriptionInput`. - Exports: the listed interfaces and enums. - Depends on: nothing. **`src/types/dunning.ts`** - Purpose: TypeScript interfaces for Dunning domain — `DunningState`, `DunningStep`, `DunningAction`. - Exports: the listed interfaces and enums. - Depends on: nothing. **`src/types/audit.ts`** - Purpose: TypeScript interfaces for Audit domain — `AuditLogEntry`, `AuditAction`, `AuditQuery`. - Exports: the listed interfaces and enums. - Depends on: nothing. ### Source — Utils **`src/utils/money.ts`** - Purpose: Integer-cent arithmetic helpers — `addCents`, `subtractCents`, `multiplyCents` (takes quantity as integer, unit price in cents), `prorateCents` (computes proportional amount for partial billing periods using integer division with banker's rounding), `formatCentsToDecimal` (display only, returns string). - Exports: the listed functions. - Depends on: nothing. - Notes: All functions accept and return integers. Any function receiving a non-integer throws immediately. **`src/utils/dateUtils.ts`** - Purpose: Billing date helpers — `computeBillingPeriodEnd` (given anchor date and interval), `daysBetween`, `isWithinGracePeriod`, `nextBillingDate`, `toISODate`. - Exports: the listed functions. - Depends on: nothing. **`src/utils/idGenerator.ts`** - Purpose: Generates prefixed UUIDs for each entity type — `plan_xxx`, `cust_xxx`, `sub_xxx`, `inv_xxx`, `pay_xxx`, `evt_xxx`, `wh_xxx`. - Exports: `generateId` function. - Depends on: `uuid`. **`src/utils/hmac.ts`** - Purpose: HMAC-SHA256 signing and verification for outbound webhook payloads. - Exports: `signPayload`, `verifySignature` functions. - Depends on: `src/config/index.ts`. **`src/utils/pagination.ts`** - Purpose: Converts pagination query parameters (`page`, `pageSize`) into Prisma `skip`/`take` values. Constructs pagination metadata for API responses. - Exports: `paginate`, `buildPaginationMeta` functions. - Depends on: nothing. ### CLI **`cli/index.ts`** - Purpose: CLI entry point. Configures Commander.js, registers all command modules, and parses `process.argv`. - Exports: none (entry point). - Depends on: `cli/commands/*`, `src/config/index.ts`, `src/config/database.ts`. **`cli/commands/plans.ts`** - Purpose: CLI commands for plan management — `plan list`, `plan create`, `plan archive`. - Exports: `registerPlanCommands` function. - Depends on: `src/services/planService.ts`, `cli/utils/output.ts`. **`cli/commands/subscriptions.ts`** - Purpose: CLI commands for subscription management — `subscription list`, `subscription cancel`, `subscription reactivate`. - Exports: `registerSubscriptionCommands` function. - Depends on: `src/services/subscriptionService.ts`, `cli/utils/output.ts`. **`cli/commands/invoices.ts`** - Purpose: CLI commands for invoice operations — `invoice list`, `invoice regenerate`, `invoice void`. - Exports: `registerInvoiceCommands` function. - Depends on: `src/services/invoiceService.ts`, `cli/utils/output.ts`. **`cli/commands/dunning.ts`** - Purpose: CLI commands for dunning overrides — `dunning status`, `dunning resolve`, `dunning retry-now`. - Exports: `registerDunningCommands` function. - Depends on: `src/services/dunningService.ts`, `cli/utils/output.ts`. **`cli/commands/webhooks.ts`** - Purpose: CLI commands for webhook management — `webhook list`, `webhook test` (sends a test event to a URL). - Exports: `registerWebhookCommands` function. - Depends on: `src/services/webhookService.ts`, `cli/utils/output.ts`. **`cli/commands/apiKeys.ts`** - Purpose: CLI commands for API key management — `apikey create`, `apikey list`, `apikey revoke`. - Exports: `registerApiKeyCommands` function. - Depends on: `src/repositories/apiKeyRepository.ts`, `cli/utils/output.ts`. **`cli/commands/audit.ts`** - Purpose: CLI commands for audit log queries — `audit query` with filters for resource type, actor, and date range. - Exports: `registerAuditCommands` function. - Depends on: `src/services/auditService.ts`, `cli/utils/output.ts`. **`cli/utils/output.ts`** - Purpose: Formats CLI output as tables or JSON depending on a `--format` flag. - Exports: `printTable`, `printJson`, `printSuccess`, `printError` functions. - Depends on: nothing. **`cli/utils/prompts.ts`** - Purpose: Interactive confirmation prompts for destructive CLI operations (cancel subscription, void invoice, revoke API key). - Exports: `confirmAction` function. - Depends on: nothing (uses Node.js `readline`). ### Tests **`tests/setup.ts`** - Purpose: Global test setup — loads `.env.test`, resets the test database, clears Redis, and configures global test timeouts. - Exports: Jest `globalSetup` and `globalTeardown` functions. - Depends on: `src/config/database.ts`, `src/config/redis.ts`. **`tests/helpers/factories.ts`** - Purpose: Factory functions for creating test fixtures — `buildPlan`, `buildCustomer`, `buildSubscription`, `buildInvoice`, `buildUsageEvent`. Returns plain objects matching the input types with sensible defaults. - Exports: the listed factory functions. - Depends on: `src/types/index.ts`, `src/utils/idGenerator.ts`. **`tests/helpers/testDb.ts`** - Purpose: Helpers for test database management — `resetDatabase` (truncates all tables), `seedDatabase` (inserts baseline test data). - Exports: `resetDatabase`, `seedDatabase` functions. - Depends on: `src/config/database.ts`. **`tests/helpers/testRedis.ts`** - Purpose: Helpers for test Redis management — `flushTestRedis`, `getTestRedis`. - Exports: `flushTestRedis`, `getTestRedis` functions. - Depends on: `src/config/redis.ts`. **`tests/helpers/mockStripeGateway.ts`** - Purpose: Mock implementation of `StripeGateway` that returns predictable responses without making real Stripe API calls. - Exports: `MockStripeGateway` class. - Depends on: `src/gateways/stripeGateway.ts`, `src/types/payment.ts`, `src/types/customer.ts`. **`tests/unit/services/planService.test.ts`** - Purpose: Unit tests for `PlanService` — creation validation, archive rules, pricing constraint enforcement. - Depends on: `src/services/planService.ts`, `tests/helpers/factories.ts`. **`tests/unit/services/subscriptionService.test.ts`** - Purpose: Unit tests for `SubscriptionService` — state machine transitions, proration calculations, conflict detection. - Depends on: `src/services/subscriptionService.ts`, `tests/helpers/factories.ts`. **`tests/unit/services/invoiceService.test.ts`** - Purpose: Unit tests for `InvoiceService` — line item computation, proration amounts, status transitions, cent-precision arithmetic. - Depends on: `src/services/invoiceService.ts`, `tests/helpers/factories.ts`. **`tests/unit/services/usageService.test.ts`** - Purpose: Unit tests for `UsageService` — idempotency deduplication, aggregation correctness, time boundary handling. - Depends on: `src/services/usageService.ts`, `tests/helpers/factories.ts`. **`tests/unit/services/paymentService.test.ts`** - Purpose: Unit tests for `PaymentService` — payment intent creation, success/failure handling, invoice status updates. - Depends on: `src/services/paymentService.ts`, `tests/helpers/factories.ts`, `tests/helpers/mockStripeGateway.ts`. **`tests/unit/services/dunningService.test.ts`** - Purpose: Unit tests for `DunningService` — retry scheduling, escalation ladder, resolution on successful payment. - Depends on: `src/services/dunningService.ts`, `tests/helpers/factories.ts`. **`tests/unit/services/webhookService.test.ts`** - Purpose: Unit tests for `WebhookService` — event construction, HMAC signing, delivery attempt recording. - Depends on: `src/services/webhookService.ts`, `tests/helpers/factories.ts`. **`tests/unit/utils/money.test.ts`** - Purpose: Unit tests for cent arithmetic — overflow protection, non-integer rejection, proration rounding behavior. - Depends on: `src/utils/money.ts`. **`tests/unit/utils/dateUtils.test.ts`** - Purpose: Unit tests for billing date calculations — period boundaries, leap year handling, month-end anchoring. - Depends on: `src/utils/dateUtils.ts`. **`tests/unit/middleware/auth.test.ts`** - Purpose: Unit tests for auth middleware — valid key, invalid key, missing header, revoked key. - Depends on: `src/middleware/auth.ts`, `tests/helpers/factories.ts`. **`tests/unit/middleware/validateRequest.test.ts`** - Purpose: Unit tests for request validation — valid input passes, invalid input returns structured errors. - Depends on: `src/middleware/validateRequest.ts`, `src/schemas/plan.ts`. **`tests/integration/repositories/planRepository.test.ts`** - Purpose: Integration tests for `PlanRepository` — CRUD against a real test database, pagination, filtering. - Depends on: `src/repositories/planRepository.ts`, `tests/helpers/testDb.ts`. **`tests/integration/repositories/subscriptionRepository.test.ts`** - Purpose: Integration tests for `SubscriptionRepository` — foreign key constraints, due-for-billing queries. - Depends on: `src/repositories/subscriptionRepository.ts`, `tests/helpers/testDb.ts`. **`tests/integration/repositories/invoiceRepository.test.ts`** - Purpose: Integration tests for `InvoiceRepository` — line item creation, status filtering, date range queries. - Depends on: `src/repositories/invoiceRepository.ts`, `tests/helpers/testDb.ts`. **`tests/integration/repositories/auditLogRepository.test.ts`** - Purpose: Integration tests for `AuditLogRepository` — append-only enforcement, query filtering by resource type and date range. - Depends on: `src/repositories/auditLogRepository.ts`, `tests/helpers/testDb.ts`. **`tests/integration/workers/invoiceWorker.test.ts`** - Purpose: Integration tests for `invoiceWorker` — processes queued jobs, generates invoices, enqueues payment jobs. - Depends on: `src/workers/invoiceWorker.ts`, `tests/helpers/testDb.ts`, `tests/helpers/testRedis.ts`. **`tests/integration/workers/paymentWorker.test.ts`** - Purpose: Integration tests for `paymentWorker` — processes payment jobs, handles mock Stripe responses, triggers dunning on failure. - Depends on: `src/workers/paymentWorker.ts`, `tests/helpers/testDb.ts`, `tests/helpers/testRedis.ts`, `tests/helpers/mockStripeGateway.ts`. **`tests/integration/workers/webhookWorker.test.ts`** - Purpose: Integration tests for `webhookWorker` — delivers payloads to a local HTTP server, records outcomes, retries on failure. - Depends on: `src/workers/webhookWorker.ts`, `tests/helpers/testDb.ts`, `tests/helpers/testRedis.ts`. **`tests/integration/stripeGateway.test.ts`** - Purpose: Integration tests for `StripeGateway` using Stripe test-mode keys — creates test customers and payment intents. - Depends on: `src/gateways/stripeGateway.ts`, `src/config/stripe.ts`. - Notes: Requires `STRIPE_SECRET_KEY` to be a valid test-mode key. Skipped in CI if not set. **`tests/e2e/plans.test.ts`** - Purpose: End-to-end tests for plan CRUD via the REST API using supertest. - Depends on: `src/server.ts`, `tests/helpers/testDb.ts`, `tests/helpers/factories.ts`. **`tests/e2e/subscriptions.test.ts`** - Purpose: End-to-end tests for the subscription lifecycle — create, change plan, cancel, reactivate. - Depends on: `src/server.ts`, `tests/helpers/testDb.ts`, `tests/helpers/factories.ts`. **`tests/e2e/invoices.test.ts`** - Purpose: End-to-end tests for invoice operations — generation, finalization, voiding, manual payment. - Depends on: `src/server.ts`, `tests/helpers/testDb.ts`, `tests/helpers/factories.ts`. **`tests/e2e/usage.test.ts`** - Purpose: End-to-end tests for usage ingestion — idempotency, aggregation, and summary retrieval. - Depends on: `src/server.ts`, `tests/helpers/testDb.ts`, `tests/helpers/factories.ts`. **`tests/e2e/webhooks.test.ts`** - Purpose: End-to-end tests for webhook subscription registration and event delivery flow. - Depends on: `src/server.ts`, `tests/helpers/testDb.ts`, `tests/helpers/factories.ts`. **`tests/e2e/dunning.test.ts`** - Purpose: End-to-end tests for the dunning workflow — failed payment triggers dunning, retries escalate, resolution on success. - Depends on: `src/server.ts`, `tests/helpers/testDb.ts`, `tests/helpers/factories.ts`, `tests/helpers/mockStripeGateway.ts`. **`tests/smoke/health.test.ts`** - Purpose: Smoke test that hits `GET /health` and verifies a 200 response with expected body structure. - Depends on: `src/server.ts`. --- ## 7. Data Models and Contracts ### Database Models (Prisma Schema) #### Plan | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID (`plan_xxx`) | Unique plan identifier | | `name` | String | Required, max 100 chars | Display name | | `description` | String | Optional, max 500 chars | Plan description | | `pricingModel` | Enum: `flat_rate`, `per_unit` | Required | How charges are calculated | | `amountCents` | Int | Required, >= 0 | Price in cents (for flat_rate: total; for per_unit: per unit price) | | `currency` | String | Required, 3 chars, default `usd` | ISO 4217 currency code | | `billingInterval` | Enum: `month`, `year` | Required | Billing cycle length | | `trialPeriodDays` | Int | Optional, >= 0, default 0 | Free trial period | | `status` | Enum: `active`, `archived` | Required, default `active` | Plan availability | | `metadata` | Json | Optional, default `{}` | Arbitrary key-value pairs | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | #### Customer | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID (`cust_xxx`) | Unique customer identifier | | `email` | String | Required, valid email, max 255 chars | Customer email | | `name` | String | Required, max 200 chars | Customer display name | | `stripeCustomerId` | String | Unique, nullable | Linked Stripe customer ID | | `metadata` | Json | Optional, default `{}` | Arbitrary key-value pairs | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | #### Subscription | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID (`sub_xxx`) | Unique subscription identifier | | `customerId` | String | FK → Customer.id, required | Owning customer | | `planId` | String | FK → Plan.id, required | Subscribed plan | | `status` | Enum: `active`, `past_due`, `canceled`, `trialing`, `paused` | Required, default `active` | Current status | | `currentPeriodStart` | DateTime | Required | Start of current billing period | | `currentPeriodEnd` | DateTime | Required | End of current billing period | | `billingAnchorDay` | Int | Required, 1-28 | Day of month for billing | | `cancelAtPeriodEnd` | Boolean | Default `false` | Cancel at end of current period | | `canceledAt` | DateTime | Nullable | When cancellation was requested | | `trialEnd` | DateTime | Nullable | When trial expires | | `metadata` | Json | Optional, default `{}` | Arbitrary key-value pairs | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | Index: `(customerId, status)` for active-subscription-per-customer lookups. #### Invoice | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID (`inv_xxx`) | Unique invoice identifier | | `subscriptionId` | String | FK → Subscription.id, required | Associated subscription | | `customerId` | String | FK → Customer.id, required | Billing customer | | `status` | Enum: `draft`, `open`, `paid`, `void`, `uncollectible` | Required, default `draft` | Invoice status | | `subtotalCents` | Int | Required, >= 0 | Sum of line items before tax | | `taxAmountCents` | Int | Default 0 | Tax amount (placeholder for tax integration) | | `totalCents` | Int | Required, >= 0 | subtotalCents + taxAmountCents | | `currency` | String | Required, 3 chars | ISO 4217 currency code | | `periodStart` | DateTime | Required | Billing period start | | `periodEnd` | DateTime | Required | Billing period end | | `dueDate` | DateTime | Required | Payment due date | | `paidAt` | DateTime | Nullable | When payment was received | | `voidedAt` | DateTime | Nullable | When invoice was voided | | `invoiceNumber` | String | Unique, auto-generated | Sequential human-readable number | | `metadata` | Json | Optional, default `{}` | Arbitrary key-value pairs | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | #### InvoiceLine | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID | Unique line identifier | | `invoiceId` | String | FK → Invoice.id, required | Parent invoice | | `description` | String | Required, max 500 chars | Line item description | | `type` | Enum: `plan_charge`, `usage_charge`, `proration_credit`, `proration_debit` | Required | Charge type | | `quantity` | Int | Required, >= 0 | Units (1 for flat rate) | | `unitAmountCents` | Int | Required | Per-unit price in cents | | `amountCents` | Int | Required | quantity Ɨ unitAmountCents | | `periodStart` | DateTime | Required | Period this line covers | | `periodEnd` | DateTime | Required | Period this line covers | | `metadata` | Json | Optional, default `{}` | Arbitrary key-value pairs | #### Payment | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID (`pay_xxx`) | Unique payment identifier | | `invoiceId` | String | FK → Invoice.id, required | Invoice being paid | | `amountCents` | Int | Required, > 0 | Payment amount in cents | | `currency` | String | Required, 3 chars | ISO 4217 currency code | | `status` | Enum: `pending`, `succeeded`, `failed`, `refunded`, `disputed` | Required, default `pending` | Payment status | | `stripePaymentIntentId` | String | Unique, nullable | Stripe PaymentIntent ID | | `failureCode` | String | Nullable, max 100 chars | Stripe failure code | | `failureMessage` | String | Nullable, max 500 chars | Stripe failure message | | `attemptCount` | Int | Default 1 | Number of charge attempts | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | #### UsageEvent | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID | Unique event identifier | | `subscriptionId` | String | FK → Subscription.id, required | Owning subscription | | `idempotencyKey` | String | Unique, required, max 255 chars | Client-provided deduplication key | | `quantity` | Int | Required, > 0 | Usage units consumed | | `timestamp` | DateTime | Required | When usage occurred | | `properties` | Json | Optional, default `{}` | Additional event metadata | | `createdAt` | DateTime | Auto-set | Ingestion timestamp | Index: `(subscriptionId, timestamp)` for range queries. Unique index on `idempotencyKey`. #### UsageAggregate | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID | Unique aggregate identifier | | `subscriptionId` | String | FK → Subscription.id, required | Owning subscription | | `periodStart` | DateTime | Required | Aggregation period start | | `periodEnd` | DateTime | Required | Aggregation period end | | `totalQuantity` | Int | Required, >= 0 | Sum of usage events in period | | `computedAt` | DateTime | Required | When aggregation was last run | Unique index: `(subscriptionId, periodStart, periodEnd)`. #### DunningState | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID | Unique dunning state identifier | | `invoiceId` | String | FK → Invoice.id, unique, required | Invoice under dunning | | `subscriptionId` | String | FK → Subscription.id, required | Associated subscription | | `retryCount` | Int | Default 0 | Number of retries attempted | | `maxRetries` | Int | Required | Maximum retries allowed | | `nextRetryAt` | DateTime | Nullable | Scheduled next retry time | | `currentAction` | Enum: `retry`, `notify`, `pause`, `cancel` | Required, default `retry` | Current escalation action | | `resolvedAt` | DateTime | Nullable | When dunning was resolved | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | #### WebhookSubscription | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID (`wh_xxx`) | Unique webhook subscription identifier | | `url` | String | Required, valid HTTPS URL, max 2048 chars | Delivery target URL | | `eventTypes` | String[] | Required, non-empty | List of event types to deliver | | `secret` | String | Required | Shared secret for HMAC verification on the consumer side | | `isActive` | Boolean | Default `true` | Whether deliveries are enabled | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | #### WebhookEvent | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID (`evt_xxx`) | Unique event identifier | | `type` | String | Required, max 100 chars | Event type string | | `payload` | Json | Required | Event data | | `webhookSubscriptionId` | String | FK → WebhookSubscription.id, required | Target subscription | | `status` | Enum: `pending`, `delivered`, `failed` | Required, default `pending` | Delivery status | | `attemptCount` | Int | Default 0 | Number of delivery attempts | | `lastAttemptAt` | DateTime | Nullable | Timestamp of last attempt | | `nextAttemptAt` | DateTime | Nullable | Scheduled next retry | | `lastResponseStatus` | Int | Nullable | HTTP status from last attempt | | `createdAt` | DateTime | Auto-set | Creation timestamp | | `updatedAt` | DateTime | Auto-updated | Last modification timestamp | #### AuditLog | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID | Unique log entry identifier | | `actor` | String | Required, max 255 chars | Who performed the action (API key ID or `cli:operator`) | | `action` | String | Required, max 100 chars | Action performed (e.g., `plan.created`, `subscription.canceled`) | | `resourceType` | String | Required, max 50 chars | Resource type (e.g., `Plan`, `Subscription`) | | `resourceId` | String | Required | Resource identifier | | `before` | Json | Nullable | State before the change | | `after` | Json | Nullable | State after the change | | `metadata` | Json | Optional, default `{}` | Additional context | | `createdAt` | DateTime | Auto-set | Timestamp | Index: `(resourceType, resourceId)`, `(actor)`, `(createdAt)`. #### ApiKey | Field | Type | Constraints | Description | |-------|------|-------------|-------------| | `id` | String | PK, prefixed UUID | Unique key identifier | | `name` | String | Required, max 100 chars | Human-readable label | | `keyHash` | String | Unique, required | SHA-256 hash of the raw key with salt | | `keyPrefix` | String | Required, 8 chars | First 8 characters of the key for identification | | `scopes` | String[] | Required, non-empty | Permitted scopes (e.g., `billing:read`, `billing:write`) | | `isRevoked` | Boolean | Default `false` | Whether the key has been revoked | | `lastUsedAt` | DateTime | Nullable | Last authentication timestamp | | `expiresAt` | DateTime | Nullable | Optional expiration | | `createdAt` | DateTime | Auto-set | Creation timestamp | ### API Request/Response Schemas #### Standard Success Response Envelope ``` { "data": <resource or array>, "meta": { "page": number, "pageSize": number, "totalCount": number, "totalPages": number } } ``` `meta` is included only on list endpoints. Single-resource endpoints return `{ "data": <resource> }`. #### Standard Error Response ``` { "error": { "code": string, // Machine-readable: "VALIDATION_ERROR", "NOT_FOUND", "CONFLICT", "STRIPE_ERROR", "AUTHENTICATION_ERROR", "RATE_LIMIT_EXCEEDED", "INTERNAL_ERROR" "message": string, // Human-readable description "details": [ // Optional, present for validation errors { "field": string, "message": string, "code": string } ], "requestId": string // UUID for tracing } } ``` #### Create Plan — `POST /v1/plans` Request body: | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `name` | string | yes | 1-100 chars | | `description` | string | no | max 500 chars | | `pricingModel` | string | yes | `flat_rate` or `per_unit` | | `amountCents` | integer | yes | >= 0 | | `billingInterval` | string | yes | `month` or `year` | | `trialPeriodDays` | integer | no | >= 0, default 0 | | `metadata` | object | no | arbitrary key-value | Response: `201 Created` with the full Plan object in `data`. #### Create Customer — `POST /v1/customers` Request body: | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `email` | string | yes | valid email, max 255 chars | | `name` | string | yes | 1-200 chars | | `metadata` | object | no | arbitrary key-value | Response: `201 Created` with the full Customer object. #### Create Subscription — `POST /v1/subscriptions` Request body: | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `customerId` | string | yes | existing customer ID | | `planId` | string | yes | existing active plan ID | | `billingAnchorDay` | integer | no | 1-28, defaults to current day of month | | `metadata` | object | no | arbitrary key-value | Response: `201 Created` with the full Subscription object. #### Ingest Usage — `POST /v1/usage` Request body: | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `subscriptionId` | string | yes | existing subscription ID | | `idempotencyKey` | string | yes | 1-255 chars, unique | | `quantity` | integer | yes | > 0 | | `timestamp` | string | yes | ISO 8601 datetime | | `properties` | object | no | arbitrary key-value | Response: `201 Created` with the UsageEvent object. `200 OK` if the idempotency key already exists (returns existing event). #### Create Webhook Subscription — `POST /v1/webhook-subscriptions` Request body: | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `url` | string | yes | valid HTTPS URL, max 2048 chars | | `eventTypes` | string[] | yes | non-empty, values from `WebhookEventType` enum | Response: `201 Created` with the WebhookSubscription object including the generated `secret`. ### Webhook Event Types | Event Type | Trigger | |------------|---------| | `subscription.created` | New subscription created | | `subscription.updated` | Subscription status or plan changed | | `subscription.canceled` | Subscription canceled | | `subscription.reactivated` | Canceled subscription reactivated | | `invoice.created` | Invoice generated | | `invoice.finalized` | Invoice moved to open | | `invoice.paid` | Invoice fully paid | | `invoice.voided` | Invoice voided | | `payment.succeeded` | Payment processed successfully | | `payment.failed` | Payment attempt failed | | `dunning.started` | Dunning workflow initiated | | `dunning.retry_scheduled` | Payment retry scheduled | | `dunning.escalated` | Dunning escalated to next action | | `dunning.resolved` | Dunning resolved (payment succeeded) | ### Outbound Webhook Payload Format ``` { "id": "evt_xxx", "type": "subscription.created", "createdAt": "2025-01-15T10:30:00.000Z", "data": { /* full resource object */ } } ``` Delivered with headers: - `Content-Type: application/json` - `X-Webhook-Signature: sha256=<HMAC hex digest of body>` - `X-Webhook-Id: evt_xxx` - `X-Webhook-Timestamp: 1705312200` ### Pagination Query Parameters All list endpoints accept: | Param | Type | Default | Constraints | |-------|------|---------|-------------| | `page` | integer | 1 | >= 1 | | `pageSize` | integer | 20 | 1-100 | ### Validation Rules Summary - All IDs: prefixed UUID format, validated with regex `^(plan|cust|sub|inv|pay|evt|wh)_[a-f0-9-]{36}$`. - All monetary amounts: non-negative integers. Service layer throws `ValidationError` on negative values or non-integers. - Email: validated against RFC 5322 basic pattern via zod `.email()`. - URLs: must start with `https://` in production. `http://` allowed only when `NODE_ENV` is `development` or `test`. - Billing anchor day: 1-28 (avoids month-end ambiguity for February). - Billing interval: strictly `month` or `year`. - Subscription status transitions: enforced by the state machine in `SubscriptionService`. Invalid transitions return `ConflictError`. - Invoice status transitions: `draft → open`, `open → paid`, `open → void`, `open → uncollectible`. Any other transition returns `ConflictError`. --- ## 8. Risk and Failure Map ### Input Errors | Risk | Severity | System Response | Fallback | |------|----------|-----------------|----------| | Invalid request body (wrong types, missing fields) | Low | Return 400 with `ValidationError` and field-level details. | Client corrects and retries. | | Duplicate idempotency key on usage ingestion | Low | Return 200 with the existing event. No state change. | By design — idempotent. | | Invalid subscription state transition (e.g., cancel an already-canceled subscription) | Medium | Return 409 `ConflictError` with current status and allowed transitions. | Client reads current state and adjusts. | | Non-integer monetary amount in API input | Medium | Return 400 `ValidationError`. Zod schema rejects non-integers. | Client corrects input. | | API key missing or invalid | Medium | Return 401 `AuthenticationError`. No operation performed. | Client re-authenticates. | | Rate limit exceeded | Low | Return 429 with `Retry-After` header. | Client implements backoff. | ### Runtime Failures | Risk | Severity | System Response | Fallback | |------|----------|-----------------|----------| | Database connection lost | Critical | Health check returns 503. API requests fail with 500. | Application retries Prisma connections automatically. Alert on sustained failure. Workers pause and resume when connection restores. | | Redis connection lost | High | BullMQ workers stop processing. New jobs cannot be enqueued. API requests that only read/write to the database continue to work. | Workers reconnect automatically via ioredis retry strategy. Billing cycle jobs are idempotent and will process on reconnect. | | Worker process crash | High | BullMQ marks the job as stalled after 30 seconds and reassigns it to another worker (or the same worker on restart). | Jobs have `attempts: 3` by default. Dead-letter queue captures permanently failed jobs for manual inspection. | | Invoice generation produces incorrect totals due to integer overflow | Medium | `money.ts` validates that intermediate values stay within safe integer range (`Number.MAX_SAFE_INTEGER`). Throws `AppError` if exceeded. | Alert fires. Manual correction via CLI. | | Concurrent subscription modification | Medium | Prisma's optimistic concurrency check (`updatedAt` version field) rejects stale writes with a `ConflictError`. | Client retries with fresh data. | ### External Dependency Failures | Risk | Severity | System Response | Fallback | |------|----------|-----------------|----------| | Stripe API unavailable | Critical | `StripeGateway` catches Stripe SDK errors, wraps them in `StripeError`, and propagates to the payment worker. Worker retries with exponential backoff. | Invoice stays in `open` status. Dunning service schedules a retry. Webhook emits `payment.failed` with failure reason. | | Stripe webhook signature verification fails | Medium | Return 400 to Stripe. Log the event with the raw body hash for investigation. | Stripe retries webhook delivery per its own schedule (up to 72 hours). | | Outbound webhook delivery fails (consumer endpoint down) | Medium | Worker records the failure, increments attempt count, and schedules next retry per the exponential backoff schedule (1m, 5m, 30m, 2h, 12h). | After 5 failed attempts, the event status is set to `failed`. An internal log entry records the permanently failed delivery. | | Stripe returns unexpected response format | High | `StripeGateway` validates response shape. On unexpected format, throws `StripeError` and logs the raw response. | Payment job fails and enters dunning retry flow. Alert fires for unexpected Stripe API changes. | ### Security | Risk | Severity | System Response | Fallback | |------|----------|-----------------|----------| | API key leaked | Critical | Revoke the key via CLI immediately. All requests with the revoked key return 401. Audit log retains all actions taken with the compromised key. | Issue new key. Review audit log for unauthorized actions. | | SQL injection via crafted input | High | Prisma uses parameterized queries exclusively. Zod validation rejects unexpected types before reaching the ORM. | Defense in depth — even if validation is bypassed, Prisma parameterization prevents injection. | | Stripe webhook replay attack | Medium | Stripe signature verification includes a timestamp. Events older than 5 minutes are rejected. | Attacker cannot replay captured webhook payloads. | | Outbound webhook secret exposure | Medium | Secrets are generated per webhook subscription and returned only once at creation time. Stored hashed in the database. | Rotate the subscription — delete and recreate with a new URL. | | Audit log tampering | High | The database user for the application has only INSERT and SELECT on the `AuditLog` table — no UPDATE or DELETE. | Database-level enforcement prevents application-layer bugs from corrupting the audit trail. | | Sensitive data in logs | Medium | Winston logger is configured with a redaction filter that masks `stripeSecretKey`, `apiKey`, and `keyHash` fields in JSON log output. | SOC2 log review confirms no sensitive values in production logs. | --- ## 9. Testing Strategy ### Test Types and Coverage #### Smoke Tests Verify the server starts and responds to health checks. - `tests/smoke/health.test.ts`: `GET /health` returns 200 with `{ status: "ok", database: "connected", redis: "connected" }`. Minimum scenarios: 1 (health check returns 200). #### Unit Tests Test domain logic in isolation. Repositories are mocked. Stripe gateway is mocked. - **planService**: valid creation, reject archived plan edits, reject negative amounts, reject non-integer cents. - **subscriptionService**: valid creation, reject duplicate active subscriptions, enforce state transitions (6 valid, 4 invalid), proration calculation for mid-cycle plan change, proration calculation with usage. - **invoiceService**: flat-rate line item generation, per-unit line item generation, proration credit line, proration debit line, total equals sum of lines, status transitions (3 valid, 3 invalid). - **usageService**: ingest new event, deduplicate by idempotency key, aggregate across period boundary, handle zero-usage period. - **paymentService**: successful payment updates invoice to paid, failed payment triggers dunning, dispute handling. - **dunningService**: initiate on first failure, schedule retry with correct delay, escalate after max retries, resolve on successful payment. - **webhookService**: construct payload with correct structure, HMAC signature verification round-trip, track delivery attempts. - **money.ts**: addition, subtraction, multiplication, proration rounding, reject non-integer input, reject negative values where specified, overflow protection. - **dateUtils.ts**: monthly period boundaries, yearly period boundaries, leap year handling, month-end anchoring (anchor day 31 → capped at 28). - **auth middleware**: valid key passes, missing header returns 401, invalid key returns 401, revoked key returns 401. - **validateRequest middleware**: valid body passes, missing required field returns 400 with field detail, wrong type returns 400. Minimum scenarios per service: 4. Total minimum unit test scenarios: 55. #### Integration Tests Test a component with real database and/or real Redis. Stripe gateway integration tests use Stripe test mode. - **Repository tests**: CRUD round-trips, pagination, filter queries, foreign key enforcement, unique constraint enforcement. - **Worker tests**: Job processing end-to-end with real queues — enqueue, process, verify database state changes and downstream job enqueuing. - **Stripe gateway**: Create test customer, create test payment intent, handle test-mode error responses. Minimum scenarios per repository: 4. Minimum scenarios per worker: 3. Total minimum integration scenarios: 28. #### End-to-End Tests Full HTTP request through the stack using supertest against the Express app with a test database. - **plans.test.ts**: create plan → list plans → get plan → update plan → archive plan. - **subscriptions.test.ts**: create customer → create plan → create subscription → change plan (verify proration) → cancel → reactivate. - **invoices.test.ts**: create subscription → trigger invoice generation → finalize → pay → verify paid status. - **usage.test.ts**: ingest events → verify idempotency → get summary → verify aggregation. - **webhooks.test.ts**: register webhook subscription → trigger event → verify delivery (against local HTTP server in test). - **dunning.test.ts**: create subscription → generate invoice → mock payment failure → verify dunning initiation → mock retry success → verify resolution. Minimum scenarios per e2e file: 1 full workflow. Total minimum e2e scenarios: 6. ### Pass/Fail Criteria - All tests pass with exit code 0. - Unit test code coverage (line): minimum 85% across `src/services/` and `src/utils/`. - Integration tests must not leave orphaned data (each test cleans up via `resetDatabase`). - E2E tests must complete within 60 seconds total. ### Commands ```bash # Full suite npm test # By category npm run test:smoke npm run test:unit npm run test:integration npm run test:e2e # With coverage npm run test:unit -- --coverage ``` --- ## 10. Setup and Run ### Local Setup **Prerequisites:** Node.js 20+, Docker and Docker Compose (for PostgreSQL and Redis), a Stripe test-mode account. ```bash # 1. Clone and install git clone <repo-url> billing-engine cd billing-engine npm install # 2. Start infrastructure docker compose up -d postgres redis # 3. Configure environment cp .env.example .env # Edit .env: set STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET from your Stripe dashboard # 4. Initialize database npx prisma migrate dev npx prisma generate # 5. Create an initial API key npm run cli -- apikey create --name "dev-key" # Save the printed key — it is shown only once # 6. Start the development server (with auto-reload) npm run dev # 7. In a separate terminal, start the workers npm run dev:workers ``` ### Container Setup ```bash # Build and start everything (app, workers, postgres, redis) docker compose up --build # Run migrations inside the app container docker compose exec app npx prisma migrate deploy # Create an API key docker compose exec app npm run cli -- apikey create --name "dev-key" ``` ### Verification Steps ```bash # 1. Health check — should return {"status":"ok","database":"connected","redis":"connected"} curl http://localhost:3000/health # 2. Smoke test npm run test:smoke # 3. Create a plan (replace <API_KEY> with the key from step 5 above) curl -X POST http://localhost:3000/v1/plans \ -H "Authorization: Bearer <API_KEY>" \ -H "Content-Type: application/json" \ -d '{"name":"Starter","pricingModel":"flat_rate","amountCents":2999,"billingInterval":"month"}' # Should return 201 with the plan object # 4. List plans curl http://localhost:3000/v1/plans \ -H "Authorization: Bearer <API_KEY>" # Should return the plan created above # 5. Verify Stripe connectivity — create a customer curl -X POST http://localhost:3000/v1/customers \ -H "Authorization: Bearer <API_KEY>" \ -H "Content-Type: application/json" \ -d '{"name":"Test User","email":"test@example.com"}' # Should return 201 with a stripeCustomerId populated # 6. Run the full test suite npm test ``` ### npm Scripts Reference | Script | Command | Description | |--------|---------|-------------| | `dev` | `nodemon --exec tsx src/index.ts` | Start API server with auto-reload | | `dev:workers` | `tsx src/workers/index.ts` | Start background workers in dev mode | | `build` | `tsc` | Compile TypeScript to `dist/` | | `start` | `node dist/src/index.js` | Start production server | | `start:workers` | `node dist/src/workers/index.js` | Start production workers | | `test` | `jest` | Run all tests | | `test:smoke` | `jest --testPathPattern=smoke` | Run smoke tests | | `test:unit` | `jest --testPathPattern=unit` | Run unit tests | | `test:integration` | `jest --testPathPattern=integration` | Run integration tests | | `test:e2e` | `jest --testPathPattern=e2e` | Run end-to-end tests | | `lint` | `eslint src/ cli/ tests/` | Run linter | | `migrate` | `prisma migrate dev` | Run database migrations | | `cli` | `tsx cli/index.ts` | Run admin CLI |
šŸŒ€ Claude

Technical Blueprint For Developers

$9.99-50%
$5.00
SPECIAL PROMOTION — Offer ends 22 April
Add to Cart
Instant accessInstant access
Usage rightsCommercial use
Money-back guaranteeMoney‑back
By purchasing this prompt, you agree to our terms of service
CLAUDE-4-6-OPUS
Tested icon
Guide icon
4 examples icon
Free credits icon
šŸ› ļø Generate a Complete Technical Project Spec in One Step - Describe your project idea and get a structured, buildable blueprint: ā–Ŗļø Scope contract with clear in/out boundaries ā–Ŗļø Full directory tree with file-by-file contracts ā–Ŗļø Dependency map with versions and environment config ā–Ŗļø Risk analysis with severity levels and fallbacks ā–Ŗļø Testing plan and step-by-step setup commands Built for developers and AI coding tools like Cursor or Claude Code. šŸ“ Instructions and tips included.
...more
Added 6 hours ago
Report
Browse Marketplace