feat(phase-4): WS6 — Billing & Usage Metering (Stripe, free tier enforcement)
- DB migration 023: tenant_subscriptions and usage_events tables - UsageMeteringMiddleware: in-memory counters, 60s flush to DB via UPSERT - FreeTierEnforcementMiddleware: 10 agents / 1,000 calls/day limits, Redis cache - UsageService: getDailyUsage and getActiveAgentCount - BillingService: Stripe checkout sessions, webhook verification, subscription status - POST /billing/checkout, POST /billing/webhook, GET /billing/usage endpoints - BILLING_ENABLED=false disables enforcement without breaking metering - Dashboard: Usage tab with Free Tier/Pro badges and metric cards - 19 unit tests passing across billing services and middleware Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
33
src/app.ts
33
src/app.ts
@@ -8,6 +8,7 @@ import express, { Application } from 'express';
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
import morgan from 'morgan';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { getPool } from './db/pool.js';
|
||||
import { getRedisClient } from './cache/redis.js';
|
||||
@@ -21,6 +22,8 @@ import { OrgRepository } from './repositories/OrgRepository.js';
|
||||
import { AuditService } from './services/AuditService.js';
|
||||
import { AgentService } from './services/AgentService.js';
|
||||
import { MarketplaceService } from './services/MarketplaceService.js';
|
||||
import { BillingService } from './services/BillingService.js';
|
||||
import { UsageService } from './services/UsageService.js';
|
||||
import { CredentialService } from './services/CredentialService.js';
|
||||
import { OAuth2Service } from './services/OAuth2Service.js';
|
||||
import { OrgService } from './services/OrgService.js';
|
||||
@@ -35,6 +38,7 @@ import { createKafkaProducer } from './adapters/KafkaAdapter.js';
|
||||
|
||||
import { AgentController } from './controllers/AgentController.js';
|
||||
import { MarketplaceController } from './controllers/MarketplaceController.js';
|
||||
import { BillingController } from './controllers/BillingController.js';
|
||||
import { OIDCTrustPolicyController } from './controllers/OIDCTrustPolicyController.js';
|
||||
import { OIDCTokenExchangeController } from './controllers/OIDCTokenExchangeController.js';
|
||||
import { TokenController } from './controllers/TokenController.js';
|
||||
@@ -49,6 +53,7 @@ import { ComplianceController } from './controllers/ComplianceController.js';
|
||||
|
||||
import { createAgentsRouter } from './routes/agents.js';
|
||||
import { createMarketplaceRouter } from './routes/marketplace.js';
|
||||
import { createBillingRouter } from './routes/billing.js';
|
||||
import { createOIDCTrustPoliciesRouter } from './routes/oidcTrustPolicies.js';
|
||||
import { createOIDCTokenExchangeRouter } from './routes/oidcTokenExchange.js';
|
||||
import { OIDCTrustPolicyService } from './services/OIDCTrustPolicyService.js';
|
||||
@@ -69,6 +74,8 @@ import { createOpaMiddleware } from './middleware/opa.js';
|
||||
import { metricsMiddleware } from './middleware/metrics.js';
|
||||
import { createOrgContextMiddleware } from './middleware/orgContext.js';
|
||||
import { authMiddleware } from './middleware/auth.js';
|
||||
import { createUsageMeteringMiddleware, startUsageMeteringFlush } from './middleware/usageMeteringMiddleware.js';
|
||||
import { createFreeTierEnforcementMiddleware } from './middleware/freeTierEnforcementMiddleware.js';
|
||||
import { tlsEnforcementMiddleware } from './middleware/TLSEnforcementMiddleware.js';
|
||||
import { createVaultClientFromEnv } from './vault/VaultClient.js';
|
||||
import { getEncryptionService } from './services/EncryptionService.js';
|
||||
@@ -232,6 +239,17 @@ export async function createApp(): Promise<Application> {
|
||||
const webhookController = new WebhookController(webhookService);
|
||||
const marketplaceController = new MarketplaceController(marketplaceService);
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Billing & Usage Metering (WS6)
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
const stripe = new Stripe(process.env['STRIPE_SECRET_KEY'] ?? '', { apiVersion: '2026-03-25.dahlia' });
|
||||
const billingService = new BillingService(pool, stripe);
|
||||
const usageService = new UsageService(pool);
|
||||
const billingController = new BillingController(billingService, usageService);
|
||||
|
||||
// Start periodic flush of in-memory usage counters to DB (every 60s)
|
||||
startUsageMeteringFlush(pool);
|
||||
|
||||
// OIDC trust policy management + GitHub Actions token exchange
|
||||
const oidcTrustPolicyService = new OIDCTrustPolicyService(pool);
|
||||
const oidcTrustPolicyController = new OIDCTrustPolicyController(oidcTrustPolicyService);
|
||||
@@ -254,6 +272,18 @@ export async function createApp(): Promise<Application> {
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
app.use(createOrgContextMiddleware(pool));
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Usage metering — records per-tenant API call counts in-memory
|
||||
// Applied after auth middleware so req.user is populated.
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
app.use(createUsageMeteringMiddleware(pool));
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Free tier enforcement — rejects requests exceeding free plan limits
|
||||
// Applied after usage metering and before routes.
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
app.use(createFreeTierEnforcementMiddleware(pool, redis as RedisClientType));
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Routes
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
@@ -287,6 +317,9 @@ export async function createApp(): Promise<Application> {
|
||||
app.use(`${API_BASE}`, createComplianceRouter(complianceController));
|
||||
app.use(`${API_BASE}/marketplace`, createMarketplaceRouter(marketplaceController));
|
||||
|
||||
// Billing & Usage Metering — checkout, webhook, usage summary
|
||||
app.use(`${API_BASE}/billing`, createBillingRouter(billingController, authMiddleware));
|
||||
|
||||
// OIDC trust-policy management (authenticated) and token exchange (unauthenticated)
|
||||
// Both routers mount under ${API_BASE}/oidc — trust-policy routes use /trust-policies prefix,
|
||||
// token exchange uses /token, so there are no path conflicts.
|
||||
|
||||
Reference in New Issue
Block a user