AkurAI Build
Menu

Public repository / 42cbcead3f1b5dfa8e3d274c3b18adb25b043b88

AkurAI-Platform / payment/service.ts

Latest change 40f5f122d654d8a118dade55a8a401f5889e8c83 - fix: validate Platform OIDC user via userinfo (AKURAI-PLATFORM-39) by Ólafur Búi Ólafsson

import { Database } from "bun:sqlite";
import { mkdirSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, randomUUID, timingSafeEqual, verify as verifySignature } from "node:crypto";
import { MyPOSClient, type PaymentFormFields, type WebhookValidationResult } from "mypos-online-checkout";
import { catalog as embeddedCatalog } from "../backend/catalog";
import {
  maskEmail,
  money,
  nextBillingDate,
  quoteCheckout,
  validateCatalog,
  type BillingInterval,
  type Catalog,
  type CheckoutQuote,
  type Product,
} from "./core";
import { GROWTH_EVENT_SCHEMA_VERSION, growthStage, publicGrowthEvent, type GrowthEventName, type GrowthEventRow, type GrowthEvent, type PublicGrowthEvent, type RevenueDetail } from "./events";

type OrderStatus = "pending" | "paid" | "failed" | "canceled" | "rolled_back";
type ProvisioningStatus = "pending" | "entitled" | "installing" | "ready" | "failed";
type SubscriptionStatus = "active" | "past_due" | "unpaid" | "canceled" | "requires_payment_method";
type MembershipRole = "admin" | "member";
type LifecycleEntity = "organization" | "workspace" | "membership" | "entitlement" | "installation" | "domain";

interface OrganizationRow {
  id: string;
  name: string;
  crm_company_id: string | null;
  idp_tenant_id: string | null;
  status: "active" | "suspended" | "canceled";
  created_at: string;
  updated_at: string;
}

interface MembershipRow {
  id: string;
  organization_id: string;
  user_id: string;
  role: MembershipRole;
  status: "active" | "revoked";
  created_at: string;
  updated_at: string;
}

interface WorkspaceRow {
  id: string;
  organization_id: string;
  name: string;
  kind: "managed";
  status: "provisioning" | "active" | "suspended" | "deprovisioned";
  idp_workspace_id: string | null;
  created_at: string;
  updated_at: string;
}

interface EntitlementRow {
  id: string;
  organization_id: string;
  workspace_id: string;
  app_id: string;
  status: "granted" | "revoked";
  granted_at: string | null;
  revoked_at: string | null;
  created_at: string;
  updated_at: string;
}

interface InstallationRow {
  id: string;
  organization_id: string;
  workspace_id: string;
  app_id: string;
  status: "pending" | "installing" | "installed" | "suspended" | "uninstalled" | "failed";
  created_at: string;
  updated_at: string;
}

interface DomainRow {
  id: string;
  organization_id: string;
  workspace_id: string | null;
  hostname: string;
  status: "pending" | "verified" | "active" | "disabled";
  created_at: string;
  updated_at: string;
}

interface LifecycleRevisionRow {
  id: string;
  organization_id: string;
  workspace_id: string | null;
  entity_type: LifecycleEntity;
  entity_id: string;
  revision: number;
  from_state: string | null;
  to_state: string;
  reason: string | null;
  actor_user_id: string | null;
  metadata_json: string;
  created_at: string;
}
interface ProvisioningRequestRow {
  id: string;
  order_id: string;
  organization_id: string;
  workspace_id: string;
  status: ProvisioningStatus;
  error_code: string | null;
  created_at: string;
  updated_at: string;
}


interface OrderRow {
  id: string;
  kind: "initial" | "renewal";
  subscription_id: string | null;
  customer_name: string;
  customer_email: string;
  customer_user_id: string | null;
  company: string | null;
  interval: BillingInterval;
  products_json: string;
  unit_cents: number;
  amount_cents: number;
  currency: "EUR";
  status: OrderStatus;
  provider: string;
  idempotency_key: string | null;
  provider_ref: string | null;
  provider_payload: string | null;
  manage_token_hash: string;
  period_start: string | null;
  error_code: string | null;
  created_at: string;
  updated_at: string;
  paid_at: string | null;
}

interface SubscriptionRow {
  id: string;
  initial_order_id: string;
  customer_email: string;
  interval: BillingInterval;
  products_json: string;
  unit_cents: number;
  amount_cents: number;
  currency: "EUR";
  status: SubscriptionStatus;
  card_token_enc: string | null;
  manage_token_hash: string;
  next_charge_at: string;
  billing_anchor_day: number;
  retry_at: string | null;
  renew_attempts: number;
  created_at: string;
  updated_at: string;
  canceled_at: string | null;
  retention_deadline: string | null;
}

interface BeginResult {
  redirectUrl: string;
  payload?: PaymentFormFields;
}

interface ChargeResult {
  success: boolean;
  transactionRef?: string;
  errorCode?: string;
}

type RecoveryResult = ChargeResult | "pending" | null;

export interface PaymentProvider {
  readonly name: "disabled" | "mock" | "mypos";
  begin(order: OrderRow, products: Product[]): Promise<BeginResult>;
  renew(order: OrderRow, subscription: SubscriptionRow, cardToken: string, products: Product[]): Promise<ChargeResult>;
  recover(order: OrderRow): Promise<RecoveryResult>;
}

interface WebhookProvider extends PaymentProvider {
  readonly checkoutUrl: string;
  validateWebhook(formData: FormData): WebhookValidationResult;
}

interface IdentityConfig {
  readonly trustedProxySecret: string | null;
  readonly credentialSecret: string | null;
  readonly idpPublicKey: string | null;
  readonly issuer: string | null;
  readonly audience: string | null;
}

interface OidcConfig {
  readonly issuer: string;
  readonly authorizationEndpoint: string;
  readonly tokenEndpoint: string;
  readonly clientId: string;
  readonly clientSecret: string;
  readonly redirectUri: string;
}

interface BrowserSession {
  readonly userId: string;
  readonly expiresAt: number;
}
interface TokenKeyring {
  readonly currentId: string;
  readonly keys: ReadonlyMap<string, Buffer>;
}
export interface RecoveryDelivery {
  send(input: { to: string; managementUrl: string }): Promise<void>;
}


interface ServiceOptions {
  env?: Record<string, string | undefined>;
  dbPath?: string;
  catalog?: Catalog;
  catalogPath?: string;
  publicUrl?: string;
  now?: () => Date;
  paymentProvider?: PaymentProvider;
  recoveryDelivery?: RecoveryDelivery;
}

export interface PaymentService {
  fetch(request: Request): Promise<Response>;
  runRenewals(): Promise<void>;
  close(): void;
  provider: "disabled" | "mock" | "mypos";
}

class Store {
  private readonly db: Database;
  private readonly now: () => Date;
  private closed = false;

  constructor(path: string, now: () => Date) {
    mkdirSync(dirname(path), { recursive: true });
    this.db = new Database(path, { create: true, strict: true });
    this.now = now;
    this.db.exec(`
      PRAGMA journal_mode = WAL;
      PRAGMA foreign_keys = ON;
      CREATE TABLE IF NOT EXISTS schema_migrations (
        version INTEGER PRIMARY KEY,
        applied_at TEXT NOT NULL
      );
    `);
    const current = this.db.query("SELECT COALESCE(MAX(version), 0) AS version FROM schema_migrations").get() as { version: number };
    if (current.version < 1) {
      const migrate = this.db.transaction(() => {
        this.db.exec(`
          CREATE TABLE IF NOT EXISTS payment_orders (
            customer_user_id TEXT,
            id TEXT PRIMARY KEY,
            kind TEXT NOT NULL CHECK (kind IN ('initial', 'renewal')),
            subscription_id TEXT,
            customer_name TEXT NOT NULL,
            customer_email TEXT NOT NULL,
            company TEXT,
            interval TEXT NOT NULL CHECK (interval IN ('monthly', 'annual')),
            products_json TEXT NOT NULL,
            unit_cents INTEGER NOT NULL CHECK (unit_cents > 0),
            amount_cents INTEGER NOT NULL CHECK (amount_cents > 0),
            currency TEXT NOT NULL CHECK (currency = 'EUR'),
            status TEXT NOT NULL CHECK (status IN ('pending', 'paid', 'failed', 'canceled', 'rolled_back')),
            provider TEXT NOT NULL,
            provider_ref TEXT,
            provider_payload TEXT,
            manage_token_hash TEXT NOT NULL,
            period_start TEXT,
            error_code TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            paid_at TEXT
          );
          CREATE TABLE IF NOT EXISTS subscriptions (
            id TEXT PRIMARY KEY,
            initial_order_id TEXT NOT NULL UNIQUE REFERENCES payment_orders(id),
            customer_email TEXT NOT NULL,
            interval TEXT NOT NULL CHECK (interval IN ('monthly', 'annual')),
            products_json TEXT NOT NULL,
            unit_cents INTEGER NOT NULL CHECK (unit_cents > 0),
            amount_cents INTEGER NOT NULL CHECK (amount_cents > 0),
            currency TEXT NOT NULL CHECK (currency = 'EUR'),
            status TEXT NOT NULL CHECK (status IN ('active', 'past_due', 'unpaid', 'canceled', 'requires_payment_method')),
            card_token_enc TEXT,
            manage_token_hash TEXT NOT NULL UNIQUE,
            next_charge_at TEXT NOT NULL,
            retry_at TEXT,
            renew_attempts INTEGER NOT NULL DEFAULT 0,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            canceled_at TEXT
          );
          CREATE INDEX IF NOT EXISTS payment_orders_subscription_idx ON payment_orders(subscription_id, status);
          CREATE INDEX IF NOT EXISTS subscriptions_due_idx ON subscriptions(status, next_charge_at, retry_at);

          CREATE TABLE IF NOT EXISTS organizations (
            id TEXT PRIMARY KEY,
            name TEXT NOT NULL,
            crm_company_id TEXT UNIQUE,
            idp_tenant_id TEXT UNIQUE,
            status TEXT NOT NULL CHECK (status IN ('active', 'suspended', 'canceled')),
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL
          );
          CREATE TABLE IF NOT EXISTS organization_memberships (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            user_id TEXT NOT NULL,
            role TEXT NOT NULL CHECK (role IN ('admin', 'member')),
            status TEXT NOT NULL CHECK (status IN ('active', 'revoked')),
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            UNIQUE (organization_id, user_id)
          );
          CREATE INDEX IF NOT EXISTS memberships_user_idx ON organization_memberships(user_id, status);
          CREATE TABLE IF NOT EXISTS workspaces (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            name TEXT NOT NULL,
            kind TEXT NOT NULL DEFAULT 'managed' CHECK (kind = 'managed'),
            status TEXT NOT NULL CHECK (status IN ('provisioning', 'active', 'suspended', 'deprovisioned')),
            idp_workspace_id TEXT UNIQUE,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL
          );
          CREATE INDEX IF NOT EXISTS workspaces_org_idx ON workspaces(organization_id, status);
          CREATE TABLE IF NOT EXISTS app_entitlements (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            workspace_id TEXT NOT NULL REFERENCES workspaces(id),
            app_id TEXT NOT NULL,
            status TEXT NOT NULL CHECK (status IN ('granted', 'revoked')),
            granted_at TEXT,
            revoked_at TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            UNIQUE (organization_id, workspace_id, app_id)
          );
          CREATE INDEX IF NOT EXISTS entitlements_org_idx ON app_entitlements(organization_id, workspace_id, status);
          CREATE TABLE IF NOT EXISTS installations (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            workspace_id TEXT NOT NULL REFERENCES workspaces(id),
            app_id TEXT NOT NULL,
            status TEXT NOT NULL CHECK (status IN ('pending', 'installing', 'installed', 'suspended', 'uninstalled', 'failed')),
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            UNIQUE (workspace_id, app_id)
          );
          CREATE INDEX IF NOT EXISTS installations_org_idx ON installations(organization_id, workspace_id, status);
          CREATE TABLE IF NOT EXISTS domains (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            workspace_id TEXT REFERENCES workspaces(id),
            hostname TEXT NOT NULL UNIQUE,
            status TEXT NOT NULL CHECK (status IN ('pending', 'verified', 'active', 'disabled')),
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL
          );
          CREATE INDEX IF NOT EXISTS domains_org_idx ON domains(organization_id, workspace_id, status);
          CREATE TABLE IF NOT EXISTS lifecycle_revisions (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            workspace_id TEXT,
            entity_type TEXT NOT NULL CHECK (entity_type IN ('organization', 'workspace', 'membership', 'entitlement', 'installation', 'domain')),
            entity_id TEXT NOT NULL,
            revision INTEGER NOT NULL CHECK (revision > 0),
            from_state TEXT,
            to_state TEXT NOT NULL,
            reason TEXT,
            actor_user_id TEXT,
            metadata_json TEXT NOT NULL DEFAULT '{}',
            created_at TEXT NOT NULL,
            UNIQUE (entity_type, entity_id, revision)
          );
          CREATE INDEX IF NOT EXISTS lifecycle_org_idx ON lifecycle_revisions(organization_id, created_at, entity_type);
          CREATE TRIGGER IF NOT EXISTS lifecycle_revisions_no_update
          BEFORE UPDATE ON lifecycle_revisions
          BEGIN
            SELECT RAISE(ABORT, 'lifecycle revisions are append-only');
          END;
          CREATE TRIGGER IF NOT EXISTS lifecycle_revisions_no_delete
          BEFORE DELETE ON lifecycle_revisions
          BEGIN
            SELECT RAISE(ABORT, 'lifecycle revisions are append-only');
          END;
        `);
        this.db.query("INSERT INTO schema_migrations(version, applied_at) VALUES (1, ?)").run(this.now().toISOString());
      });
      migrate();
    }

    if (current.version < 2) {
      const migrateV2 = this.db.transaction(() => {
        this.db.exec(`
          CREATE TABLE IF NOT EXISTS provisioning_requests (
            id TEXT PRIMARY KEY,
            order_id TEXT NOT NULL UNIQUE REFERENCES payment_orders(id),
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            workspace_id TEXT NOT NULL REFERENCES workspaces(id),
            status TEXT NOT NULL CHECK (status IN ('pending', 'entitled', 'installing', 'ready', 'failed')),
            error_code TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL
          );
          CREATE INDEX IF NOT EXISTS provisioning_requests_order_idx ON provisioning_requests(order_id, status);
        `);
        this.db.query("INSERT INTO schema_migrations(version, applied_at) VALUES (2, ?)").run(this.now().toISOString());
      });
      migrateV2();
    }

    if (current.version < 3) {
      const migrateV3 = this.db.transaction(() => {
        this.db.exec(`
          CREATE TABLE IF NOT EXISTS growth_events (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            workspace_id TEXT,
            name TEXT NOT NULL,
            stage TEXT NOT NULL,
            occurred_at TEXT NOT NULL,
            schema_version INTEGER NOT NULL DEFAULT 1,
            subscription_id TEXT,
            customer_email TEXT,
            actor_user_id TEXT,
            attribution_json TEXT,
            consent_json TEXT,
            revenue_json TEXT,
            product_ids_json TEXT,
            interval TEXT,
            metadata_json TEXT NOT NULL DEFAULT '{}',
            created_at TEXT NOT NULL,
            UNIQUE (organization_id, name, occurred_at)
          );
          CREATE INDEX IF NOT EXISTS growth_events_org_idx ON growth_events(organization_id, occurred_at, name);
          CREATE INDEX IF NOT EXISTS growth_events_stage_idx ON growth_events(stage, occurred_at);
        `);
        this.db.query("INSERT INTO schema_migrations(version, applied_at) VALUES (3, ?)").run(this.now().toISOString());
      });
      migrateV3();
    }

    if (current.version < 4) {
      const migrateV4 = this.db.transaction(() => {
        this.db.exec(`
          CREATE TABLE IF NOT EXISTS provisioning_release_snapshots (
            id TEXT PRIMARY KEY,
            provisioning_request_id TEXT NOT NULL REFERENCES provisioning_requests(id),
            app_id TEXT NOT NULL,
            version TEXT NOT NULL,
            source_endpoint TEXT NOT NULL,
            metadata_json TEXT NOT NULL DEFAULT '{}',
            created_at TEXT NOT NULL
          );
          CREATE INDEX IF NOT EXISTS release_snapshots_req_idx ON provisioning_release_snapshots(provisioning_request_id, app_id);
          CREATE TABLE IF NOT EXISTS provisioning_backup_records (
            id TEXT PRIMARY KEY,
            provisioning_request_id TEXT NOT NULL REFERENCES provisioning_requests(id),
            target_app_id TEXT,
            status TEXT NOT NULL CHECK (status IN ('initiated', 'verified', 'failed')),
            verified_at TEXT,
            error_code TEXT,
            created_at TEXT NOT NULL
          );
          CREATE INDEX IF NOT EXISTS backup_records_req_idx ON provisioning_backup_records(provisioning_request_id, status);
        `);
        this.db.query("INSERT INTO schema_migrations(version, applied_at) VALUES (4, ?)").run(this.now().toISOString());
      });
      migrateV4();
    }
    if (current.version < 5) {
      const migrateV5 = this.db.transaction(() => {
        this.db.exec(`
          CREATE TABLE IF NOT EXISTS contact_submissions (
            id TEXT PRIMARY KEY,
            name TEXT NOT NULL,
            email TEXT NOT NULL,
            company TEXT,
            outcome TEXT NOT NULL CHECK (outcome IN ('demo', 'waitlist', 'contact')),
            intent TEXT,
            locale TEXT NOT NULL,
            product_ids TEXT,
            attribution_json TEXT,
            consent_json TEXT NOT NULL,
            crm_sync_status TEXT NOT NULL DEFAULT 'pending' CHECK (crm_sync_status IN ('pending', 'synced', 'failed')),
            crm_error TEXT,
            crm_person_id TEXT,
            crm_company_id INTEGER,
            crm_opportunity_id INTEGER,
            growth_event_id TEXT,
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL
          );
          CREATE INDEX IF NOT EXISTS contact_submissions_email_idx ON contact_submissions(email, created_at);
          CREATE INDEX IF NOT EXISTS contact_submissions_sync_idx ON contact_submissions(crm_sync_status, created_at);
        `);
        this.db.query("INSERT INTO schema_migrations(version, applied_at) VALUES (5, ?)").run(this.now().toISOString());
      });
      migrateV5();
    }
    if (current.version < 6) {
      const migrateV6 = this.db.transaction(() => {
        try {
          this.db.exec("ALTER TABLE subscriptions ADD COLUMN retention_deadline TEXT");
        } catch {
          // Column may already exist on a migrated DB
        }
      });
      migrateV6();
      this.db.query("INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (6, ?)").run(this.now().toISOString());
    }
    if (current.version < 7) {
      const migrateV7 = this.db.transaction(() => {
        try {
          this.db.exec("ALTER TABLE payment_orders ADD COLUMN idempotency_key TEXT");
        } catch {
          // Column may already exist on a migrated DB.
        }
        this.db.exec("CREATE UNIQUE INDEX IF NOT EXISTS payment_orders_idempotency_key_idx ON payment_orders(idempotency_key) WHERE idempotency_key IS NOT NULL");
      });
      migrateV7();
      this.db.query("INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (7, ?)").run(this.now().toISOString());
    }
    if (current.version < 8) {
      const migrateV8 = this.db.transaction(() => {
        this.db.exec(`
          CREATE TABLE IF NOT EXISTS payment_events (
            id TEXT PRIMARY KEY,
            order_id TEXT REFERENCES payment_orders(id),
            subscription_id TEXT REFERENCES subscriptions(id),
            type TEXT NOT NULL,
            payload_json TEXT NOT NULL,
            created_at TEXT NOT NULL
          );
          CREATE INDEX IF NOT EXISTS payment_events_order_idx ON payment_events(order_id, created_at);
          CREATE TRIGGER IF NOT EXISTS payment_events_no_update BEFORE UPDATE ON payment_events BEGIN SELECT RAISE(ABORT, 'payment events are append-only'); END;
          CREATE TRIGGER IF NOT EXISTS payment_events_no_delete BEFORE DELETE ON payment_events BEGIN SELECT RAISE(ABORT, 'payment events are append-only'); END;
          CREATE TABLE IF NOT EXISTS organization_exports (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            requested_by TEXT NOT NULL,
            data_json TEXT NOT NULL,
            status TEXT NOT NULL CHECK (status IN ('ready', 'restored')),
            created_at TEXT NOT NULL,
            restored_at TEXT
          );
          CREATE TABLE IF NOT EXISTS organization_deletion_requests (
            id TEXT PRIMARY KEY,
            organization_id TEXT NOT NULL REFERENCES organizations(id),
            requested_by TEXT NOT NULL,
            token_hash TEXT NOT NULL UNIQUE,
            expires_at TEXT NOT NULL,
            created_at TEXT NOT NULL
          );
          CREATE TABLE IF NOT EXISTS organization_deletion_receipts (
            id TEXT PRIMARY KEY,
            organization_id_hash TEXT NOT NULL,
            deleted_at TEXT NOT NULL,
            requested_by TEXT NOT NULL
          );
        `);
      });
      migrateV8();
      this.db.query("INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (8, ?)").run(this.now().toISOString());
    }
    if (current.version < 9) {
      try {
        this.db.exec("ALTER TABLE subscriptions ADD COLUMN billing_anchor_day INTEGER NOT NULL DEFAULT 1");
        this.db.exec("UPDATE subscriptions SET billing_anchor_day = CAST(strftime('%d', created_at) AS INTEGER) WHERE billing_anchor_day = 1");
      } catch {
        // Existing databases may already include the anchor column.
      }
      this.db.query("INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (9, ?)").run(this.now().toISOString());
    }
    if (current.version < 10) {
      this.db.exec(`
        CREATE TABLE IF NOT EXISTS recovery_delivery_attempts (
          id TEXT PRIMARY KEY,
          subscription_id TEXT NOT NULL REFERENCES subscriptions(id),
          requested_by TEXT,
          status TEXT NOT NULL CHECK (status IN ('sent', 'failed')),
          error_code TEXT,
          created_at TEXT NOT NULL
        );
        CREATE INDEX IF NOT EXISTS recovery_delivery_subscription_idx ON recovery_delivery_attempts(subscription_id, created_at);
      `);
      this.db.query("INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (10, ?)").run(this.now().toISOString());
    }
    if (current.version < 11) {
      try {
        this.db.exec("ALTER TABLE payment_orders ADD COLUMN customer_user_id TEXT");
      } catch {
        // Existing databases may already include the customer identity binding.
      }
      this.db.query("INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (11, ?)").run(this.now().toISOString());
    }
    if (current.version < 12) {
      try {
        this.db.exec("ALTER TABLE organization_exports ADD COLUMN integrity_hash TEXT");
      } catch {
        // Existing databases may already include the export integrity digest.
      }
      this.db.query("INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (12, ?)").run(this.now().toISOString());
    }
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS renewal_fences (
        subscription_id TEXT PRIMARY KEY REFERENCES subscriptions(id),
        lease_token TEXT NOT NULL,
        acquired_at INTEGER NOT NULL,
        expires_at INTEGER NOT NULL
      );
    `);
  }
  private recordPaymentEvent(orderId: string | null, subscriptionId: string | null, type: string, payload: Record<string, unknown> = {}): void {
    this.db.query("INSERT INTO payment_events(id, order_id, subscription_id, type, payload_json, created_at) VALUES (?, ?, ?, ?, ?, ?)")
      .run(`pev_${randomUUID()}`, orderId, subscriptionId, type, JSON.stringify(payload), this.now().toISOString());
  }
  paymentEvents(organizationId: string): Record<string, unknown>[] {
    return this.db.query(`
      SELECT pe.id, pe.order_id AS orderId, pe.subscription_id AS subscriptionId, pe.type, pe.payload_json AS payloadJson, pe.created_at AS createdAt
      FROM payment_events pe
      LEFT JOIN subscriptions s ON s.id = pe.subscription_id
      LEFT JOIN provisioning_requests pr ON pr.order_id = COALESCE(s.initial_order_id, pe.order_id)
      WHERE pr.organization_id = ?
      ORDER BY pe.created_at DESC
      LIMIT 500
    `).all(organizationId) as Record<string, unknown>[];
  }
  createInitial(quote: CheckoutQuote, provider: string, manageTokenHash: string, idempotencyKey: string | null, customerUserId: string | null): OrderRow {
    const id = `ord_${randomUUID()}`;
    const timestamp = this.now().toISOString();
    this.db.query(`
      INSERT INTO payment_orders (
        id, kind, customer_name, customer_email, customer_user_id, company, interval, products_json,
        unit_cents, amount_cents, currency, status, provider, manage_token_hash, idempotency_key,
        created_at, updated_at
      ) VALUES (?, 'initial', ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?)
    `).run(
      id,
      quote.customer.name,
      quote.customer.email,
      customerUserId,
      quote.customer.company ?? null,
      quote.interval,
      JSON.stringify(quote.productIds),
      quote.unitCents,
      quote.amountCents,
      quote.currency,
      provider,
      manageTokenHash,
      idempotencyKey,
      timestamp,
      timestamp,
    );
    this.recordPaymentEvent(id, null, "checkout_created", { provider, amountCents: quote.amountCents, idempotencyKey: Boolean(idempotencyKey) });
    return this.order(id)!;
  }

  createRenewal(subscription: SubscriptionRow): OrderRow {
    const attempt = subscription.renew_attempts + 1;
    const periodKey = subscription.next_charge_at.slice(0, 10).replaceAll("-", "");
    const id = `ren_${subscription.id.slice(4, 16)}_${periodKey}_${attempt}`;
    const timestamp = this.now().toISOString();
    this.db.query(`
      INSERT OR IGNORE INTO payment_orders (
        id, kind, subscription_id, customer_name, customer_email, interval, products_json,
        unit_cents, amount_cents, currency, status, provider, manage_token_hash,
        period_start, created_at, updated_at
      ) VALUES (?, 'renewal', ?, '', ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?)
    `).run(
      id,
      subscription.id,
      subscription.customer_email,
      subscription.interval,
      subscription.products_json,
      subscription.unit_cents,
      subscription.amount_cents,
      subscription.currency,
      providerNameFromInitial(this.db, subscription.initial_order_id),
      subscription.manage_token_hash,
      subscription.next_charge_at,
      timestamp,
      timestamp,
    );
    this.recordPaymentEvent(id, subscription.id, "renewal_created", { periodStart: subscription.next_charge_at, attempt });
    return this.order(id)!;
  }
  orderByIdempotencyKey(key: string): OrderRow | null {
    return (this.db.query("SELECT * FROM payment_orders WHERE idempotency_key = ?").get(key) as OrderRow | null) ?? null;
  }
  staleOrderCleanup(): number {
    const cutoff = new Date(this.now().getTime() - 24 * 60 * 60 * 1000).toISOString();
    const count = this.db.query(`
      UPDATE payment_orders SET status = 'canceled', error_code = 'timeout', updated_at = ?
      WHERE kind = 'initial' AND status = 'pending' AND created_at < ?
    `).run(this.now().toISOString(), cutoff) as unknown as { changes: number };
    this.recordPaymentEvent(null, null, "stale_orders_expired", { count: count.changes });
    return count.changes;
  }
  order(id: string): OrderRow | null {
    return (this.db.query("SELECT * FROM payment_orders WHERE id = ?").get(id) as OrderRow | null) ?? null;
  }
  setPayload(id: string, payload: PaymentFormFields): void {
    this.db.query("UPDATE payment_orders SET provider_payload = ?, updated_at = ? WHERE id = ? AND status = 'pending'")
      .run(JSON.stringify(payload), this.now().toISOString(), id);
  }

  markOrder(id: string, status: Exclude<OrderStatus, "paid">, errorCode?: string): void {
    const timestamp = this.now().toISOString();
    this.db.query("UPDATE payment_orders SET status = ?, error_code = ?, updated_at = ? WHERE id = ? AND status = 'pending'")
      .run(status, errorCode ?? null, timestamp, id);
    this.recordPaymentEvent(id, null, `order_${status}`, { errorCode: errorCode ?? null });
  }
  rollbackOrder(id: string, transactionRef?: string): void {
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query("UPDATE payment_orders SET status = 'rolled_back', provider_ref = ?, updated_at = ? WHERE id = ?")
        .run(transactionRef ?? null, timestamp, id);
      this.db.query("UPDATE subscriptions SET status = 'canceled', canceled_at = ?, updated_at = ? WHERE initial_order_id = ?")
        .run(timestamp, timestamp, id);
    });
    transaction();
    this.recordPaymentEvent(id, null, "order_rolled_back", { transactionRef: transactionRef ?? null });
  }
  completeInitial(order: OrderRow, encryptedCardToken: string | null, transactionRef?: string): { subscription: SubscriptionRow; provisioning: ProvisioningRequestRow } {
    const timestamp = this.now().toISOString();
    const subscriptionId = `sub_${order.id.slice(4)}`;
    const billingAnchorDay = this.now().getUTCDate();
    const nextChargeAt = nextBillingDate(this.now(), order.interval, billingAnchorDay).toISOString();
    const status: SubscriptionStatus = encryptedCardToken ? "active" : "requires_payment_method";
    let completed = false;
    const paymentTransaction = this.db.transaction(() => {
      const current = this.order(order.id);
      if (!current) throw new Error("Order not found");
      if (current.status === "paid") return;
      if (current.status !== "pending") throw new Error("Order is not payable");
      completed = true;
      this.db.query(`
        INSERT INTO subscriptions (
          id, initial_order_id, customer_email, interval, products_json, unit_cents,
          amount_cents, currency, status, card_token_enc, manage_token_hash,
          next_charge_at, billing_anchor_day, created_at, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run(
        subscriptionId,
        order.id,
        order.customer_email,
        order.interval,
        order.products_json,
        order.unit_cents,
        order.amount_cents,
        order.currency,
        status,
        encryptedCardToken,
        order.manage_token_hash,
        nextChargeAt,
        billingAnchorDay,
        timestamp,
        timestamp,
      );
      this.db.query("UPDATE payment_orders SET status = 'paid', provider_ref = ?, paid_at = ?, updated_at = ? WHERE id = ?")
        .run(transactionRef ?? null, timestamp, timestamp, order.id);
    });
    paymentTransaction();
    const provisioning = completed ? this.createProvisioning(order) : this.provisioningRequest(order.id)!;
    const subscription = this.subscriptionByOrder(order.id)!;
    if (!completed) return { subscription, provisioning };
    this.recordPaymentEvent(order.id, subscription.id, "initial_payment_confirmed", { transactionRef: transactionRef ?? null, status });
    const revenue: RevenueDetail | undefined = order.amount_cents > 0 ? {
      dataUnavailable: false,
      amountCents: order.amount_cents,
      currency: order.currency,
      provider: order.provider,
      providerRef: transactionRef,
      entitlementCount: JSON.parse(order.products_json).length,
      installationCount: null,
      readyCount: null,
    } : undefined;
    this.recordGrowthEvent({
      id: `ge_${createHash("sha256").update(`paid:${order.id}`).digest("hex").slice(0, 14)}`,
      organization_id: provisioning.organization_id,
      workspace_id: provisioning.workspace_id,
      name: "paid/v1",
      stage: "retention",
      occurred_at: timestamp,
      schema_version: GROWTH_EVENT_SCHEMA_VERSION,
      subscription_id: subscription.id,
      customer_email: order.customer_email,
      actor_user_id: null,
      attribution_json: null,
      consent_json: null,
      revenue_json: revenue ? JSON.stringify(revenue) : null,
      product_ids_json: order.products_json,
      interval: order.interval,
      metadata_json: JSON.stringify({ orderId: order.id }),
      created_at: timestamp,
    });
    return { subscription, provisioning };
  }

  completeRenewal(order: OrderRow, transactionRef?: string): void {
    if (!order.subscription_id || !order.period_start) throw new Error("Invalid renewal order");
    const subscription = this.subscription(order.subscription_id);
    if (!subscription) throw new Error("Subscription not found");
    const timestamp = this.now().toISOString();
    const nextChargeAt = nextBillingDate(new Date(order.period_start), order.interval, subscription.billing_anchor_day).toISOString();
    const transaction = this.db.transaction(() => {
      const current = this.order(order.id);
      if (!current) throw new Error("Renewal order not found");
      if (current.status === "paid") return;
      if (current.status !== "pending") throw new Error("Renewal order is not payable");
      this.db.query("UPDATE payment_orders SET status = 'paid', provider_ref = ?, paid_at = ?, updated_at = ? WHERE id = ?")
        .run(transactionRef ?? null, timestamp, timestamp, order.id);
      this.db.query(`
        UPDATE subscriptions
        SET status = 'active', next_charge_at = ?, retry_at = NULL, renew_attempts = 0, updated_at = ?
        WHERE id = ? AND status != 'canceled'
      `).run(nextChargeAt, timestamp, order.subscription_id);
    });
    transaction();
    this.recordPaymentEvent(order.id, order.subscription_id, "renewal_payment_confirmed", { transactionRef: transactionRef ?? null });
    const orgId = this.organizationIdForSubscription(order.subscription_id!);
    if (orgId) {
      this.recordGrowthEvent({
        id: `ge_${createHash("sha256").update(`retained:${order.id}`).digest("hex").slice(0, 14)}`,
        organization_id: orgId,
        workspace_id: null,
        name: "retained/v1",
        stage: "retention",
        occurred_at: timestamp,
        schema_version: GROWTH_EVENT_SCHEMA_VERSION,
        subscription_id: order.subscription_id,
        customer_email: order.customer_email,
        actor_user_id: null,
        attribution_json: null,
        consent_json: null,
        revenue_json: JSON.stringify({
          amountCents: order.amount_cents,
          currency: order.currency,
          provider: order.provider,
          providerRef: transactionRef,
          dataUnavailable: false,
          entitlementCount: JSON.parse(order.products_json).length,
          installationCount: null,
          readyCount: null,
        }),
        product_ids_json: order.products_json,
        interval: order.interval,
        metadata_json: JSON.stringify({ orderId: order.id }),
        created_at: timestamp,
      });
    }
  }
  failRenewal(order: OrderRow, errorCode: string): void {
    if (!order.subscription_id) throw new Error("Invalid renewal order");
    const subscription = this.subscription(order.subscription_id);
    if (!subscription || subscription.status === "canceled") return;
    const attempts = subscription.renew_attempts + 1;
    const terminal = attempts >= 3;
    const retryDays = attempts === 1 ? 1 : 3;
    const retryAt = terminal ? null : new Date(this.now().getTime() + retryDays * 86_400_000).toISOString();
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query("UPDATE payment_orders SET status = 'failed', error_code = ?, updated_at = ? WHERE id = ? AND status = 'pending'")
        .run(errorCode.slice(0, 80), timestamp, order.id);
      this.db.query(`
        UPDATE subscriptions
        SET status = ?, renew_attempts = ?, retry_at = ?, updated_at = ?
        WHERE id = ? AND status != 'canceled'
      `).run(terminal ? "unpaid" : "past_due", attempts, retryAt, timestamp, order.subscription_id);
    });
    transaction();
    this.recordPaymentEvent(order.id, order.subscription_id, terminal ? "renewal_unpaid" : "renewal_past_due", { attempts, errorCode, retryAt });
    if (terminal) {
      const orgId = this.organizationIdForSubscription(order.subscription_id!);
      if (orgId) {
        this.recordGrowthEvent({
          id: `ge_${createHash("sha256").update(`churned:${order.subscription_id}:${timestamp.slice(0, 10)}`).digest("hex").slice(0, 14)}`,
          organization_id: orgId,
          workspace_id: null,
          name: "churned/v1",
          stage: "churn",
          occurred_at: timestamp,
          schema_version: GROWTH_EVENT_SCHEMA_VERSION,
          subscription_id: order.subscription_id,
          customer_email: order.customer_email,
          actor_user_id: null,
          attribution_json: null,
          consent_json: null,
          revenue_json: null,
          product_ids_json: order.products_json,
          interval: order.interval,
          metadata_json: JSON.stringify({ orderId: order.id, errorCode }),
          created_at: timestamp,
        });
      }
    }
  }

  provisioningRequest(orderId: string): ProvisioningRequestRow | null {
    return (this.db.query("SELECT * FROM provisioning_requests WHERE order_id = ?").get(orderId) as ProvisioningRequestRow | null) ?? null;
  }

  createProvisioning(order: OrderRow): ProvisioningRequestRow {
    const existing = this.provisioningRequest(order.id);
    if (existing) return existing;

    const timestamp = this.now().toISOString();
    const products = JSON.parse(order.products_json) as string[];
    const emailKey = order.customer_email.toLowerCase().trim();
    const orgId = `org_${createHash("sha256").update(`customer:${emailKey}`).digest("hex").slice(0, 14)}`;
    const wsId = `ws_${createHash("sha256").update(`order:${order.id}`).digest("hex").slice(0, 14)}`;
    const reqId = `pr_${createHash("sha256").update(order.id).digest("hex").slice(0, 14)}`;

    const transaction = this.db.transaction(() => {
      const existingOrg = this.db.query("SELECT id FROM organizations WHERE id = ?").get(orgId) as OrganizationRow | null;
      if (!existingOrg) {
        this.db.query("INSERT INTO organizations(id, name, status, created_at, updated_at) VALUES (?, ?, 'active', ?, ?)")
          .run(orgId, emailKey, timestamp, timestamp);
        this.revision(orgId, null, "organization", orgId, null, "active", null, "provisioned_from_order");
      }
      if (order.customer_user_id) {
        const membership = this.membershipForUser(orgId, order.customer_user_id);
        if (!membership) {
          const membershipId = `mbr_${randomUUID()}`;
          this.db.query("INSERT INTO organization_memberships(id, organization_id, user_id, role, status, created_at, updated_at) VALUES (?, ?, ?, 'admin', 'active', ?, ?)")
            .run(membershipId, orgId, order.customer_user_id, timestamp, timestamp);
          this.revision(orgId, null, "membership", membershipId, null, "active", order.customer_user_id, "provisioned_owner");
        }
      }

      const existingWs = this.db.query("SELECT id FROM workspaces WHERE id = ?").get(wsId) as WorkspaceRow | null;
      if (!existingWs) {
        this.db.query("INSERT INTO workspaces(id, organization_id, name, kind, status, created_at, updated_at) VALUES (?, ?, ?, 'managed', 'provisioning', ?, ?)")
          .run(wsId, orgId, `Managed workspace`, timestamp, timestamp);
        this.revision(orgId, wsId, "workspace", wsId, null, "provisioning", null, "provisioned_from_order");
      }

      for (const appId of products) {
        const existingEnt = this.db.query("SELECT * FROM app_entitlements WHERE organization_id = ? AND workspace_id = ? AND app_id = ?").get(orgId, wsId, appId) as EntitlementRow | null;
        const entId = existingEnt?.id ?? `ent_${createHash("sha256").update(`${wsId}:${appId}`).digest("hex").slice(0, 14)}`;
        if (!existingEnt || existingEnt.status !== "granted") {
          if (existingEnt) {
            this.db.query("UPDATE app_entitlements SET status = 'granted', granted_at = ?, revoked_at = NULL, updated_at = ? WHERE id = ?")
              .run(timestamp, timestamp, entId);
          } else {
            this.db.query("INSERT INTO app_entitlements(id, organization_id, workspace_id, app_id, status, granted_at, created_at, updated_at) VALUES (?, ?, ?, ?, 'granted', ?, ?, ?)")
              .run(entId, orgId, wsId, appId, timestamp, timestamp, timestamp);
          }
          this.revision(orgId, wsId, "entitlement", entId, existingEnt?.status ?? null, "granted", null, "provisioned_from_order");
        }

        const existingInst = this.db.query("SELECT * FROM installations WHERE organization_id = ? AND workspace_id = ? AND app_id = ?").get(orgId, wsId, appId) as InstallationRow | null;
        if (!existingInst) {
          const instId = `inst_${createHash("sha256").update(`${wsId}:${appId}`).digest("hex").slice(0, 14)}`;
          this.db.query("INSERT INTO installations(id, organization_id, workspace_id, app_id, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'pending', ?, ?)")
            .run(instId, orgId, wsId, appId, timestamp, timestamp);
          this.revision(orgId, wsId, "installation", instId, null, "pending", null, "provisioned_from_order");
        }
      }

      this.db.query("INSERT INTO provisioning_requests(id, order_id, organization_id, workspace_id, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'entitled', ?, ?)")
        .run(reqId, order.id, orgId, wsId, timestamp, timestamp);
    });
    transaction();
    const pr = this.provisioningRequest(order.id)!;
    this.recordGrowthEvent({
      id: `ge_${createHash("sha256").update(`trial:${pr.organization_id}:${pr.workspace_id}`).digest("hex").slice(0, 14)}`,
      organization_id: pr.organization_id,
      workspace_id: pr.workspace_id,
      name: "trial_requested/v1",
      stage: "activation",
      occurred_at: timestamp,
      schema_version: GROWTH_EVENT_SCHEMA_VERSION,
      subscription_id: null,
      customer_email: order.customer_email,
      actor_user_id: null,
      attribution_json: null,
      consent_json: null,
      revenue_json: null,
      product_ids_json: JSON.stringify(products),
      interval: null,
      metadata_json: JSON.stringify({ orderId: order.id }),
      created_at: timestamp,
    });
    return pr;
  }


  subscription(id: string): SubscriptionRow | null {
    return (this.db.query("SELECT * FROM subscriptions WHERE id = ?").get(id) as SubscriptionRow | null) ?? null;
  }

  subscriptionByOrder(orderId: string): SubscriptionRow | null {
    return (this.db.query("SELECT * FROM subscriptions WHERE initial_order_id = ?").get(orderId) as SubscriptionRow | null) ?? null;
  }

  organizationIdForSubscription(subscriptionId: string): string | null {
    const row = this.db.query(`
      SELECT pr.organization_id FROM provisioning_requests pr
      JOIN subscriptions s ON s.initial_order_id = pr.order_id
      WHERE s.id = ?
    `).get(subscriptionId) as { organization_id: string } | null;
    return row?.organization_id ?? null;
  }
  subscriptionByManageHash(hash: string): SubscriptionRow | null {
    return (this.db.query("SELECT * FROM subscriptions WHERE manage_token_hash = ?").get(hash) as SubscriptionRow | null) ?? null;
  }
  setSubscriptionCardToken(subscriptionId: string, encryptedCardToken: string): void {
    this.db.query("UPDATE subscriptions SET card_token_enc = ?, updated_at = ? WHERE id = ?")
      .run(encryptedCardToken, this.now().toISOString(), subscriptionId);
  }

  dueSubscriptions(): SubscriptionRow[] {
    const timestamp = this.now().toISOString();
    return this.db.query(`
      SELECT * FROM subscriptions
      WHERE (status = 'active' AND next_charge_at <= ?)
         OR (status = 'past_due' AND retry_at IS NOT NULL AND retry_at <= ?)
      ORDER BY COALESCE(retry_at, next_charge_at)
      LIMIT 50
    `).all(timestamp, timestamp) as SubscriptionRow[];
  }
  acquireRenewalFence(subscriptionId: string): string | null {
    const token = randomUUID();
    const acquiredAt = this.now().getTime();
    const expiresAt = acquiredAt + 2 * 60 * 60 * 1000;
    const transaction = this.db.transaction(() => {
      const result = this.db.query(`
        INSERT INTO renewal_fences(subscription_id, lease_token, acquired_at, expires_at)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(subscription_id) DO UPDATE SET
          lease_token = excluded.lease_token,
          acquired_at = excluded.acquired_at,
          expires_at = excluded.expires_at
        WHERE renewal_fences.expires_at <= excluded.acquired_at
      `).run(subscriptionId, token, acquiredAt, expiresAt) as { changes?: number };
      return result.changes === 1;
    });
    return transaction() ? token : null;
  }

  releaseRenewalFence(subscriptionId: string, leaseToken: string): void {
    this.db.query("DELETE FROM renewal_fences WHERE subscription_id = ? AND lease_token = ?").run(subscriptionId, leaseToken);
  }

  pendingRenewal(subscriptionId: string): OrderRow | null {
    return (this.db.query(`
      SELECT * FROM payment_orders
      WHERE subscription_id = ? AND kind = 'renewal' AND status = 'pending'
      ORDER BY created_at DESC LIMIT 1
    `).get(subscriptionId) as OrderRow | null) ?? null;
  }

  cancelSubscription(manageTokenHash: string): SubscriptionRow | null {
    const subscription = this.subscriptionByManageHash(manageTokenHash);
    if (!subscription || subscription.status === "canceled") return subscription ?? null;
    const timestamp = this.now().toISOString();
    const canceledAt = new Date(timestamp);
    const periodEnd = new Date(subscription.next_charge_at);
    // Retention: 30 days after the paid period ends
    const retentionDeadline = new Date(Math.max(periodEnd.getTime(), canceledAt.getTime()) + 30 * 86_400_000).toISOString();
    this.db.query(`
      UPDATE subscriptions
      SET status = 'canceled', canceled_at = ?, retention_deadline = ?, retry_at = NULL, updated_at = ?
      WHERE manage_token_hash = ? AND status != 'canceled'
    `).run(timestamp, retentionDeadline, timestamp, manageTokenHash);
    const updated = this.subscriptionByManageHash(manageTokenHash);
    if (updated) this.recordPaymentEvent(null, subscription.id, "subscription_canceled", { retentionDeadline });
    const orgId = this.organizationIdForSubscription(subscription.id);
    if (orgId) {
      this.recordGrowthEvent({
        id: `ge_${createHash("sha256").update(`canceled:${subscription.id}`).digest("hex").slice(0, 14)}`,
        organization_id: orgId,
        workspace_id: null,
        name: "canceled/v1",
        stage: "churn",
        occurred_at: timestamp,
        schema_version: GROWTH_EVENT_SCHEMA_VERSION,
        subscription_id: subscription.id,
        customer_email: subscription.customer_email,
        actor_user_id: null,
        attribution_json: null,
        consent_json: null,
        revenue_json: null,
        product_ids_json: subscription.products_json,
        interval: subscription.interval,
        metadata_json: JSON.stringify({ manageTokenHash, retentionDeadline }),
        created_at: timestamp,
      });
    }
    return updated;
  }
  replaceManageToken(manageTokenHash: string, newTokenHash: string): SubscriptionRow | null {
    const subscription = this.subscriptionByManageHash(manageTokenHash);
    if (!subscription) return null;
    const result = this.db.query(`
      UPDATE subscriptions SET manage_token_hash = ?, updated_at = ?
      WHERE id = ? AND manage_token_hash = ?
    `).run(newTokenHash, this.now().toISOString(), subscription.id, manageTokenHash) as { changes?: number };
    return result.changes === 1 ? this.subscriptionByManageHash(newTokenHash) : null;
  }
  recordManagementTokenRotation(subscriptionId: string, requestedBy: string | null = null): void {
    this.recordPaymentEvent(null, subscriptionId, "management_token_rotated", { requestedBy });
  }
  rotateManageToken(manageTokenHash: string, newTokenHash: string): SubscriptionRow | null {
    const subscription = this.replaceManageToken(manageTokenHash, newTokenHash);
    if (subscription) this.recordManagementTokenRotation(subscription.id);
    return subscription;
  }
  recordRecoveryDelivery(subscriptionId: string, requestedBy: string | null, sent: boolean, errorCode: string | null): void {
    const timestamp = this.now().toISOString();
    this.db.query("INSERT INTO recovery_delivery_attempts(id, subscription_id, requested_by, status, error_code, created_at) VALUES (?, ?, ?, ?, ?, ?)")
      .run(`rda_${randomUUID()}`, subscriptionId, requestedBy, sent ? "sent" : "failed", errorCode, timestamp);
    this.recordPaymentEvent(null, subscriptionId, sent ? "management_recovery_sent" : "management_recovery_failed", { requestedBy, errorCode });
  }
  recoveryDeliveries(organizationId: string): Record<string, unknown>[] {
    return this.db.query(`
      SELECT r.id, r.subscription_id AS subscriptionId, r.requested_by AS requestedBy, r.status, r.error_code AS errorCode, r.created_at AS createdAt
      FROM recovery_delivery_attempts r
      JOIN subscriptions s ON s.id = r.subscription_id
      JOIN provisioning_requests pr ON pr.order_id = s.initial_order_id
      WHERE pr.organization_id = ? ORDER BY r.created_at DESC LIMIT 100
    `).all(organizationId) as Record<string, unknown>[];
  }
  subscriptionForEmail(email: string): SubscriptionRow | null {
    return (this.db.query("SELECT * FROM subscriptions WHERE lower(customer_email) = ? ORDER BY created_at DESC LIMIT 1").get(email.toLowerCase()) as SubscriptionRow | null) ?? null;
  }
  subscriptionForOrganization(organizationId: string): SubscriptionRow | null {
    return (this.db.query(`
      SELECT s.* FROM subscriptions s JOIN provisioning_requests pr ON pr.order_id = s.initial_order_id
      WHERE pr.organization_id = ? ORDER BY s.created_at DESC LIMIT 1
    `).get(organizationId) as SubscriptionRow | null) ?? null;
  }

  membershipForUser(organizationId: string, userId: string): MembershipRow | null {
    return (this.db.query("SELECT * FROM organization_memberships WHERE organization_id = ? AND user_id = ? AND status = 'active'").get(organizationId, userId) as MembershipRow | null) ?? null;
  }

  organizationForUser(organizationId: string, userId: string): OrganizationRow | null {
    return (this.db.query(`
      SELECT o.* FROM organizations o
      JOIN organization_memberships m ON m.organization_id = o.id
      WHERE o.id = ? AND m.user_id = ? AND m.status = 'active'
    `).get(organizationId, userId) as OrganizationRow | null) ?? null;
  }

  organizationsForUser(userId: string): OrganizationRow[] {
    return this.db.query(`
      SELECT o.* FROM organizations o
      JOIN organization_memberships m ON m.organization_id = o.id
      WHERE m.user_id = ? AND m.status = 'active'
      ORDER BY o.created_at
    `).all(userId) as OrganizationRow[];
  }

  createOrganization(name: string, userId: string, crmCompanyId: string | null, idpTenantId: string | null, workspaceName: string): {
    organization: OrganizationRow;
    workspace: WorkspaceRow;
    membership: MembershipRow;
  } {
    const organizationId = `org_${randomUUID()}`;
    this.assertExternalLinkConsistency(organizationId, crmCompanyId, idpTenantId);
    if (crmCompanyId || idpTenantId) throw new Error("External CRM/IDP links require verified binding");
    const workspaceId = `ws_${randomUUID()}`;
    const membershipId = `mbr_${randomUUID()}`;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query(`
        INSERT INTO organizations(id, name, crm_company_id, idp_tenant_id, status, created_at, updated_at)
        VALUES (?, ?, ?, ?, 'active', ?, ?)
      `).run(organizationId, name, crmCompanyId, idpTenantId, timestamp, timestamp);
      this.db.query(`
        INSERT INTO organization_memberships(id, organization_id, user_id, role, status, created_at, updated_at)
        VALUES (?, ?, ?, 'admin', 'active', ?, ?)
      `).run(membershipId, organizationId, userId, timestamp, timestamp);
      this.db.query(`
        INSERT INTO workspaces(id, organization_id, name, kind, status, created_at, updated_at)
        VALUES (?, ?, ?, 'managed', 'provisioning', ?, ?)
      `).run(workspaceId, organizationId, workspaceName, timestamp, timestamp);
      this.revision(organizationId, null, "organization", organizationId, null, "active", userId, "created");
      this.revision(organizationId, workspaceId, "workspace", workspaceId, null, "provisioning", userId, "created");
      this.revision(organizationId, null, "membership", membershipId, null, "active", userId, "created");
    });
    transaction();
    this.recordGrowthEvent({
      id: `ge_${createHash("sha256").update(`contact:${organizationId}`).digest("hex").slice(0, 14)}`,
      organization_id: organizationId,
      workspace_id: workspaceId,
      name: "contact/v1",
      stage: "acquisition",
      occurred_at: timestamp,
      schema_version: GROWTH_EVENT_SCHEMA_VERSION,
      subscription_id: null,
      customer_email: name,
      actor_user_id: userId,
      attribution_json: null,
      consent_json: null,
      revenue_json: null,
      product_ids_json: null,
      interval: null,
      metadata_json: JSON.stringify({ workspaceName }),
      created_at: timestamp,
    });
    return {
      organization: this.organization(organizationId)!,
      workspace: this.workspace(workspaceId)!,
      membership: this.db.query("SELECT * FROM organization_memberships WHERE id = ?").get(membershipId) as MembershipRow,
    };
  }
  private assertExternalLinkConsistency(organizationId: string, crmCompanyId: string | null, idpTenantId: string | null): void {
    const links = [
      ["crm_company_id", crmCompanyId, "CRM company"],
      ["idp_tenant_id", idpTenantId, "IDP tenant"],
    ] as const;
    for (const [column, externalId, label] of links) {
      if (!externalId) continue;
      const existing = this.db.query(`SELECT id FROM organizations WHERE ${column} = ?`).get(externalId) as { id: string } | null;
      if (existing && existing.id !== organizationId) throw new Error(`${label} already belongs to another organization`);
    }
  }

  organization(id: string): OrganizationRow | null {
    return (this.db.query("SELECT * FROM organizations WHERE id = ?").get(id) as OrganizationRow | null) ?? null;
  }

  membershipForOrganization(id: string): MembershipRow[] {
    return this.db.query("SELECT * FROM organization_memberships WHERE organization_id = ? ORDER BY created_at").all(id) as MembershipRow[];
  }

  addMembership(organizationId: string, userId: string, role: MembershipRole, actorUserId: string): MembershipRow {
    const timestamp = this.now().toISOString();
    const existing = this.db.query("SELECT * FROM organization_memberships WHERE organization_id = ? AND user_id = ?").get(organizationId, userId) as MembershipRow | null;
    if (existing) {
      if (existing.status === "active" && existing.role === role) return existing;
      const transaction = this.db.transaction(() => {
        this.db.query("UPDATE organization_memberships SET role = ?, status = 'active', updated_at = ? WHERE id = ?").run(role, timestamp, existing.id);
        this.revision(organizationId, null, "membership", existing.id, existing.status, "active", actorUserId, "membership_restored");
      });
      transaction();
      return this.db.query("SELECT * FROM organization_memberships WHERE id = ?").get(existing.id) as MembershipRow;
    }
    const id = `mbr_${randomUUID()}`;
    const transaction = this.db.transaction(() => {
      this.db.query(`
        INSERT INTO organization_memberships(id, organization_id, user_id, role, status, created_at, updated_at)
        VALUES (?, ?, ?, ?, 'active', ?, ?)
      `).run(id, organizationId, userId, role, timestamp, timestamp);
      this.revision(organizationId, null, "membership", id, null, "active", actorUserId, "membership_created");
    });
    transaction();
    return this.db.query("SELECT * FROM organization_memberships WHERE id = ?").get(id) as MembershipRow;
  }

  revokeMembership(organizationId: string, memberId: string, actorUserId: string): MembershipRow | null {
    const existing = this.db.query("SELECT * FROM organization_memberships WHERE id = ? AND organization_id = ?").get(memberId, organizationId) as MembershipRow | null;
    if (!existing) return null;
    if (existing.status === "revoked") return existing;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query("UPDATE organization_memberships SET status = 'revoked', updated_at = ? WHERE id = ?").run(timestamp, memberId);
      this.revision(organizationId, null, "membership", memberId, "active", "revoked", actorUserId, "membership_revoked");
    });
    transaction();
    return this.db.query("SELECT * FROM organization_memberships WHERE id = ?").get(memberId) as MembershipRow;
  }

  workspacesForOrganization(organizationId: string): WorkspaceRow[] {
    return this.db.query("SELECT * FROM workspaces WHERE organization_id = ? ORDER BY created_at").all(organizationId) as WorkspaceRow[];
  }

  workspace(id: string): WorkspaceRow | null {
    return (this.db.query("SELECT * FROM workspaces WHERE id = ?").get(id) as WorkspaceRow | null) ?? null;
  }

  createWorkspace(organizationId: string, name: string, actorUserId: string): WorkspaceRow {
    const id = `ws_${randomUUID()}`;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query(`
        INSERT INTO workspaces(id, organization_id, name, kind, status, created_at, updated_at)
        VALUES (?, ?, ?, 'managed', 'provisioning', ?, ?)
      `).run(id, organizationId, name, timestamp, timestamp);
      this.revision(organizationId, id, "workspace", id, null, "provisioning", actorUserId, "created");
    });
    transaction();
    return this.workspace(id)!;
  }

  setWorkspaceState(organizationId: string, workspaceId: string, state: WorkspaceRow["status"], actorUserId: string): WorkspaceRow | null {
    const current = this.db.query("SELECT * FROM workspaces WHERE id = ? AND organization_id = ?").get(workspaceId, organizationId) as WorkspaceRow | null;
    if (!current || current.status === state) return current;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query("UPDATE workspaces SET status = ?, updated_at = ? WHERE id = ? AND organization_id = ?").run(state, timestamp, workspaceId, organizationId);
      this.revision(organizationId, workspaceId, "workspace", workspaceId, current.status, state, actorUserId, "state_changed");
    });
    transaction();
    return this.workspace(workspaceId);
  }

  entitlements(organizationId: string, workspaceId?: string): EntitlementRow[] {
    if (workspaceId) this.assertWorkspaceOrganization(organizationId, workspaceId);
    return (workspaceId
      ? this.db.query("SELECT * FROM app_entitlements WHERE organization_id = ? AND workspace_id = ? ORDER BY app_id").all(organizationId, workspaceId)
      : this.db.query("SELECT * FROM app_entitlements WHERE organization_id = ? ORDER BY workspace_id, app_id").all(organizationId)) as EntitlementRow[];
  }

  entitlement(organizationId: string, workspaceId: string, appId: string): EntitlementRow | null {
    this.assertWorkspaceOrganization(organizationId, workspaceId);
    return (this.db.query("SELECT * FROM app_entitlements WHERE organization_id = ? AND workspace_id = ? AND app_id = ?").get(organizationId, workspaceId, appId) as EntitlementRow | null) ?? null;
  }

  setEntitlement(organizationId: string, workspaceId: string, appId: string, granted: boolean, actorUserId: string): EntitlementRow {
    this.assertWorkspaceOrganization(organizationId, workspaceId);
    const existing = this.entitlement(organizationId, workspaceId, appId);
    const timestamp = this.now().toISOString();
    const target = granted ? "granted" : "revoked";
    if (existing?.status === target) return existing;
    const id = existing?.id ?? `ent_${randomUUID()}`;
    const transaction = this.db.transaction(() => {
      if (existing) {
        this.db.query(`
          UPDATE app_entitlements
          SET status = ?, granted_at = ?, revoked_at = ?, updated_at = ?
          WHERE id = ? AND organization_id = ? AND workspace_id = ?
        `).run(target, granted ? timestamp : existing.granted_at, granted ? null : timestamp, timestamp, id, organizationId, workspaceId);
      } else {
        this.db.query(`
          INSERT INTO app_entitlements(id, organization_id, workspace_id, app_id, status, granted_at, revoked_at, created_at, updated_at)
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        `).run(id, organizationId, workspaceId, appId, target, granted ? timestamp : null, granted ? null : timestamp, timestamp, timestamp);
      }
      this.revision(organizationId, workspaceId, "entitlement", id, existing?.status ?? null, target, actorUserId, granted ? "granted" : "revoked");
    });
    transaction();
    if (granted) {
      this.recordGrowthEvent({
        id: `ge_${createHash("sha256").update(`expanded:${organizationId}:${appId}`).digest("hex").slice(0, 14)}`,
        organization_id: organizationId,
        workspace_id: workspaceId,
        name: "expanded/v1",
        stage: "expansion",
        occurred_at: timestamp,
        schema_version: GROWTH_EVENT_SCHEMA_VERSION,
        subscription_id: null,
        customer_email: null,
        actor_user_id: actorUserId,
        attribution_json: null,
        consent_json: null,
        revenue_json: null,
        product_ids_json: JSON.stringify([appId]),
        interval: null,
        metadata_json: JSON.stringify({}),
        created_at: timestamp,
      });
    }
    return this.entitlement(organizationId, workspaceId, appId)!;
  }

  installations(organizationId: string, workspaceId?: string): InstallationRow[] {
    if (workspaceId) this.assertWorkspaceOrganization(organizationId, workspaceId);
    return (workspaceId
      ? this.db.query("SELECT * FROM installations WHERE organization_id = ? AND workspace_id = ? ORDER BY app_id").all(organizationId, workspaceId)
      : this.db.query("SELECT * FROM installations WHERE organization_id = ? ORDER BY workspace_id, app_id").all(organizationId)) as InstallationRow[];
  }

  installation(organizationId: string, workspaceId: string, appId: string): InstallationRow | null {
    this.assertWorkspaceOrganization(organizationId, workspaceId);
    return (this.db.query("SELECT * FROM installations WHERE organization_id = ? AND workspace_id = ? AND app_id = ?").get(organizationId, workspaceId, appId) as InstallationRow | null) ?? null;
  }

  createInstallation(organizationId: string, workspaceId: string, appId: string, actorUserId: string): InstallationRow {
    this.assertWorkspaceOrganization(organizationId, workspaceId);
    const existing = this.installation(organizationId, workspaceId, appId);
    if (existing) return existing;
    const id = `inst_${randomUUID()}`;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query(`
        INSERT INTO installations(id, organization_id, workspace_id, app_id, status, created_at, updated_at)
        VALUES (?, ?, ?, ?, 'pending', ?, ?)
      `).run(id, organizationId, workspaceId, appId, timestamp, timestamp);
      this.revision(organizationId, workspaceId, "installation", id, null, "pending", actorUserId, "created");
    });
    transaction();
    return this.installation(organizationId, workspaceId, appId)!;
  }

  setInstallationState(organizationId: string, installationId: string, state: InstallationRow["status"], actorUserId: string): InstallationRow | null {
    const current = this.db.query("SELECT * FROM installations WHERE id = ? AND organization_id = ?").get(installationId, organizationId) as InstallationRow | null;
    if (!current || current.status === state) return current;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query("UPDATE installations SET status = ?, updated_at = ? WHERE id = ? AND organization_id = ?").run(state, timestamp, installationId, organizationId);
      this.revision(organizationId, current.workspace_id, "installation", installationId, current.status, state, actorUserId, "state_changed");
    });
    transaction();
    return this.db.query("SELECT * FROM installations WHERE id = ? AND organization_id = ?").get(installationId, organizationId) as InstallationRow;
  }

  domains(organizationId: string, workspaceId?: string): DomainRow[] {
    if (workspaceId) this.assertWorkspaceOrganization(organizationId, workspaceId);
    return (workspaceId
      ? this.db.query("SELECT * FROM domains WHERE organization_id = ? AND workspace_id = ? ORDER BY hostname").all(organizationId, workspaceId)
      : this.db.query("SELECT * FROM domains WHERE organization_id = ? ORDER BY hostname").all(organizationId)) as DomainRow[];
  }

  domain(organizationId: string, domainId: string): DomainRow | null {
    return (this.db.query("SELECT * FROM domains WHERE organization_id = ? AND id = ?").get(organizationId, domainId) as DomainRow | null) ?? null;
  }

  createDomain(organizationId: string, workspaceId: string | null, hostname: string, actorUserId: string): DomainRow {
    if (workspaceId) this.assertWorkspaceOrganization(organizationId, workspaceId);
    const existing = this.db.query("SELECT * FROM domains WHERE hostname = ?").get(hostname) as DomainRow | null;
    if (existing) {
      if (existing.organization_id !== organizationId) throw new Error("Domain already belongs to another organization");
      if (existing.workspace_id !== workspaceId) throw new Error("Domain workspace does not match organization");
      return existing;
    }
    const id = `dom_${randomUUID()}`;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query(`
        INSERT INTO domains(id, organization_id, workspace_id, hostname, status, created_at, updated_at)
        VALUES (?, ?, ?, ?, 'pending', ?, ?)
      `).run(id, organizationId, workspaceId, hostname, timestamp, timestamp);
      this.revision(organizationId, workspaceId, "domain", id, null, "pending", actorUserId, "created");
    });
    transaction();
    return this.domain(organizationId, id)!;
  }

  setDomainState(organizationId: string, domainId: string, state: DomainRow["status"], actorUserId: string): DomainRow | null {
    const current = this.domain(organizationId, domainId);
    if (!current || current.status === state) return current;
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query("UPDATE domains SET status = ?, updated_at = ? WHERE id = ? AND organization_id = ?").run(state, timestamp, domainId, organizationId);
      this.revision(organizationId, current.workspace_id, "domain", domainId, current.status, state, actorUserId, "state_changed");
    });
    transaction();
    return this.domain(organizationId, domainId);
  }
  private assertWorkspaceOrganization(organizationId: string, workspaceId: string): WorkspaceRow {
    const workspace = this.workspace(workspaceId);
    if (!workspace || workspace.organization_id !== organizationId) throw new Error("Workspace does not belong to this organization");
    return workspace;
  }
  insertContactSubmission(row: {
    id: string;
    name: string;
    email: string;
    company: string | null;
    outcome: string;
    intent: string | null;
    locale: string;
    productIds: string | null;
    attributionJson: string | null;
    consentJson: string;
    growthEventId: string | null;
    timestamp: string;
  }): void {
    this.db.query(`
      INSERT OR IGNORE INTO contact_submissions(
        id, name, email, company, outcome, intent, locale, product_ids,
        attribution_json, consent_json, crm_sync_status, growth_event_id,
        created_at, updated_at
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)
    `).run(row.id, row.name, row.email, row.company, row.outcome, row.intent, row.locale,
      row.productIds, row.attributionJson, row.consentJson, row.growthEventId,
      row.timestamp, row.timestamp);
  }
  contactSubmission(id: string): Record<string, unknown> | null {
    return this.db.query("SELECT * FROM contact_submissions WHERE id = ?").get(id) as Record<string, unknown> | null ?? null;
  }
  setContactCrmSync(id: string, status: string, personId: string | null, companyId: number | null, opportunityId: number | null, error: string | null): void {
    const timestamp = this.now().toISOString();
    this.db.query(`
      UPDATE contact_submissions
      SET crm_sync_status = ?, crm_person_id = ?, crm_company_id = ?, crm_opportunity_id = ?, crm_error = ?, updated_at = ?
      WHERE id = ?
    `).run(status, personId, companyId, opportunityId, error, timestamp, id);
  }
  pendingContactSyncs(limit = 50): Record<string, unknown>[] {
    return this.db.query(`
      SELECT * FROM contact_submissions
      WHERE crm_sync_status IN ('pending', 'failed')
      ORDER BY created_at ASC
      LIMIT ?
    `).all(limit) as Record<string, unknown>[];
  }


  lifecycle(organizationId: string, entityType?: LifecycleEntity, entityId?: string): LifecycleRevisionRow[] {
    if (entityType && entityId) return this.db.query("SELECT * FROM lifecycle_revisions WHERE organization_id = ? AND entity_type = ? AND entity_id = ? ORDER BY revision").all(organizationId, entityType, entityId) as LifecycleRevisionRow[];
    if (entityType) return this.db.query("SELECT * FROM lifecycle_revisions WHERE organization_id = ? AND entity_type = ? ORDER BY created_at, revision").all(organizationId, entityType) as LifecycleRevisionRow[];
    return this.db.query("SELECT * FROM lifecycle_revisions WHERE organization_id = ? ORDER BY created_at, revision LIMIT 500").all(organizationId) as LifecycleRevisionRow[];
  }

  private revision(organizationId: string, workspaceId: string | null, entityType: LifecycleEntity, entityId: string, fromState: string | null, toState: string, actorUserId: string | null, reason: string | null): void {
    const row = this.db.query("SELECT COALESCE(MAX(revision), 0) AS revision FROM lifecycle_revisions WHERE entity_type = ? AND entity_id = ?").get(entityType, entityId) as { revision: number };
    this.db.query(`
      INSERT INTO lifecycle_revisions(id, organization_id, workspace_id, entity_type, entity_id, revision, from_state, to_state, reason, actor_user_id, metadata_json, created_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '{}', ?)
    `).run(`lrev_${randomUUID()}`, organizationId, workspaceId, entityType, entityId, row.revision + 1, fromState, toState, reason, actorUserId, this.now().toISOString());
  }
  recordGrowthEvent(event: GrowthEventRow): void {
    this.db.query(`
      INSERT OR IGNORE INTO growth_events(
        id, organization_id, workspace_id, name, stage, occurred_at, schema_version,
        subscription_id, customer_email, actor_user_id,
        attribution_json, consent_json, revenue_json,
        product_ids_json, interval, metadata_json, created_at
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `).run(
      event.id, event.organization_id, event.workspace_id,
      event.name, event.stage, event.occurred_at, event.schema_version,
      event.subscription_id, event.customer_email, event.actor_user_id,
      event.attribution_json, event.consent_json, event.revenue_json,
      event.product_ids_json, event.interval, event.metadata_json,
      this.now().toISOString(),
    );
  }
  growthEvents(organizationId: string, stage?: string, limit = 200): GrowthEventRow[] {
    if (stage) {
      return this.db.query(`
        SELECT * FROM growth_events
        WHERE organization_id = ? AND stage = ?
        ORDER BY occurred_at DESC, id
        LIMIT ?
      `).all(organizationId, stage, limit) as GrowthEventRow[];
    }
    return this.db.query(`
      SELECT * FROM growth_events
      WHERE organization_id = ?
      ORDER BY occurred_at DESC, id
      LIMIT ?
    `).all(organizationId, limit) as GrowthEventRow[];
  }
  deprovisionExpiredSubscriptions(): number {
    const timestamp = this.now().toISOString();
    const expired = this.db.query(`
      SELECT s.* FROM subscriptions s
      JOIN provisioning_requests pr ON pr.order_id = s.initial_order_id
      WHERE s.status = 'canceled'
        AND s.retention_deadline IS NOT NULL
        AND s.retention_deadline <= ?
        AND (
          EXISTS (SELECT 1 FROM app_entitlements e WHERE e.organization_id = pr.organization_id AND e.status = 'granted')
          OR EXISTS (SELECT 1 FROM workspaces w WHERE w.organization_id = pr.organization_id AND w.status != 'deprovisioned')
        )
    `).all(timestamp) as SubscriptionRow[];
    for (const sub of expired) {
      const orgId = this.organizationIdForSubscription(sub.id);
      if (!orgId) continue;
      const transaction = this.db.transaction(() => {
        const entitlements = this.db.query("SELECT * FROM app_entitlements WHERE organization_id = ? AND status = 'granted'").all(orgId) as EntitlementRow[];
        for (const ent of entitlements) {
          this.db.query("UPDATE app_entitlements SET status = 'revoked', revoked_at = ?, updated_at = ? WHERE id = ?")
            .run(timestamp, timestamp, ent.id);
          this.revision(orgId, ent.workspace_id, "entitlement", ent.id, "granted", "revoked", null, "retention_expired");
        }
        const workspaces = this.db.query("SELECT * FROM workspaces WHERE organization_id = ? AND status != 'deprovisioned'").all(orgId) as WorkspaceRow[];
        for (const ws of workspaces) {
          this.db.query("UPDATE workspaces SET status = 'deprovisioned', updated_at = ? WHERE id = ?").run(timestamp, ws.id);
          this.revision(orgId, ws.id, "workspace", ws.id, ws.status, "deprovisioned", null, "retention_expired");
        }
        this.recordGrowthEvent({
          id: `ge_${createHash("sha256").update(`churned_deprovisioned:${sub.id}:${timestamp.slice(0, 10)}`).digest("hex").slice(0, 14)}`,
          organization_id: orgId,
          workspace_id: null,
          name: "churned/v1",
          stage: "churn",
          occurred_at: timestamp,
          schema_version: GROWTH_EVENT_SCHEMA_VERSION,
          subscription_id: sub.id,
          customer_email: sub.customer_email,
          actor_user_id: null,
          attribution_json: null,
          consent_json: null,
          revenue_json: JSON.stringify({ dataUnavailable: false, amountCents: 0, currency: sub.currency, provider: "system", entitlementCount: entitlements.length, installationCount: null, readyCount: null }),
          product_ids_json: sub.products_json,
          interval: sub.interval,
          metadata_json: JSON.stringify({ reason: "retention_expired", retentionDeadline: sub.retention_deadline }),
          created_at: timestamp,
        });
      });
      transaction();
    }
    return expired.length;
  }
  backups(organizationId: string): Record<string, unknown>[] {
    return this.db.query(`
      SELECT b.id, b.target_app_id AS appId, b.status, b.verified_at AS verifiedAt, b.error_code AS errorCode, b.created_at AS createdAt
      FROM provisioning_backup_records b
      JOIN provisioning_requests pr ON pr.id = b.provisioning_request_id
      WHERE pr.organization_id = ? ORDER BY b.created_at DESC
    `).all(organizationId) as Record<string, unknown>[];
  }
  invoices(organizationId: string): Record<string, unknown>[] {
    return this.db.query(`
      SELECT o.id, o.kind, o.amount_cents AS amountCents, o.currency, o.status, o.paid_at AS paidAt, o.created_at AS createdAt
      FROM payment_orders o
      JOIN provisioning_requests pr ON pr.order_id = COALESCE(o.subscription_id, o.id)
      WHERE pr.organization_id = ? ORDER BY o.created_at DESC
    `).all(organizationId) as Record<string, unknown>[];
  }
  exportOrganization(organizationId: string, actorUserId: string): Record<string, unknown> {
    const timestamp = this.now().toISOString();
    const data = {
      version: 1,
      exportedAt: timestamp,
      organization: this.organization(organizationId),
      memberships: this.membershipForOrganization(organizationId),
      workspaces: this.workspacesForOrganization(organizationId),
      entitlements: this.entitlements(organizationId),
      installations: this.installations(organizationId),
      domains: this.domains(organizationId),
      backups: this.backups(organizationId),
      lifecycle: this.lifecycle(organizationId),
    };
    const id = `exp_${randomUUID()}`;
    const dataJson = JSON.stringify(data);
    const integrityHash = createHash("sha256").update(dataJson).digest("hex");
    this.db.query("INSERT INTO organization_exports(id, organization_id, requested_by, data_json, integrity_hash, status, created_at) VALUES (?, ?, ?, ?, ?, 'ready', ?)")
      .run(id, organizationId, actorUserId, dataJson, integrityHash, timestamp);
    this.revision(organizationId, null, "organization", organizationId, "active", "active", actorUserId, "export_created");
    return { id, status: "ready", createdAt: timestamp, integrityHash, data };
  }
  restoreOrganization(organizationId: string, exportId: string, actorUserId: string): Record<string, unknown> | null {
    const exportRow = this.db.query("SELECT * FROM organization_exports WHERE id = ? AND organization_id = ? AND status = 'ready'").get(exportId, organizationId) as { id: string; data_json: string; integrity_hash: string | null } | null;
    const subscription = this.db.query(`
      SELECT s.* FROM subscriptions s JOIN provisioning_requests pr ON pr.order_id = s.initial_order_id
      WHERE pr.organization_id = ? AND s.status = 'canceled' AND s.retention_deadline > ? ORDER BY s.retention_deadline DESC LIMIT 1
    `).get(organizationId, this.now().toISOString()) as SubscriptionRow | null;
    if (!exportRow || !subscription || !exportRow.integrity_hash) return null;
    const actualIntegrityHash = createHash("sha256").update(exportRow.data_json).digest("hex");
    if (exportRow.integrity_hash.length !== actualIntegrityHash.length
      || !timingSafeEqual(Buffer.from(exportRow.integrity_hash), Buffer.from(actualIntegrityHash))) return null;
    JSON.parse(exportRow.data_json);
    const timestamp = this.now().toISOString();
    const transaction = this.db.transaction(() => {
      this.db.query("UPDATE organization_exports SET status = 'restored', restored_at = ? WHERE id = ?").run(timestamp, exportId);
      this.db.query("UPDATE workspaces SET status = 'active', updated_at = ? WHERE organization_id = ? AND status = 'deprovisioned'").run(timestamp, organizationId);
      this.revision(organizationId, null, "organization", organizationId, "active", "active", actorUserId, "export_restored");
    });
    transaction();
    return { id: exportId, status: "restored", restoredAt: timestamp, retentionDeadline: subscription.retention_deadline };
  }
  requestDeletion(organizationId: string, actorUserId: string): { id: string; token: string; expiresAt: string } {
    const token = randomBytes(32).toString("base64url");
    const id = `del_${randomUUID()}`;
    const timestamp = this.now().toISOString();
    const expiresAt = new Date(this.now().getTime() + 10 * 60_000).toISOString();
    this.db.query("INSERT INTO organization_deletion_requests(id, organization_id, requested_by, token_hash, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?)")
      .run(id, organizationId, actorUserId, hashToken(token), expiresAt, timestamp);
    return { id, token, expiresAt };
  }
  confirmDeletion(organizationId: string, requestId: string, token: string, actorUserId: string): boolean {
    const timestamp = this.now().toISOString();
    const request = this.db.query("SELECT * FROM organization_deletion_requests WHERE id = ? AND organization_id = ? AND token_hash = ? AND expires_at > ?")
      .get(requestId, organizationId, hashToken(token), timestamp) as { id: string } | null;
    const active = this.db.query(`
      SELECT 1 FROM subscriptions s JOIN provisioning_requests pr ON pr.order_id = s.initial_order_id
      WHERE pr.organization_id = ? AND (s.status != 'canceled' OR s.retention_deadline IS NULL OR s.retention_deadline > ?) LIMIT 1
    `).get(organizationId, timestamp);
    if (!request || active) return false;
    const transaction = this.db.transaction(() => {
      this.db.query("INSERT INTO organization_deletion_receipts(id, organization_id_hash, deleted_at, requested_by) VALUES (?, ?, ?, ?)")
        .run(`dlr_${randomUUID()}`, createHash("sha256").update(organizationId).digest("hex"), timestamp, actorUserId);
      this.db.query("DELETE FROM provisioning_backup_records WHERE provisioning_request_id IN (SELECT id FROM provisioning_requests WHERE organization_id = ?)").run(organizationId);
      this.db.query("DELETE FROM provisioning_release_snapshots WHERE provisioning_request_id IN (SELECT id FROM provisioning_requests WHERE organization_id = ?)").run(organizationId);
      this.db.query("DELETE FROM organization_exports WHERE organization_id = ?").run(organizationId);
      this.db.query("DELETE FROM organization_deletion_requests WHERE organization_id = ?").run(organizationId);
      this.db.query("DELETE FROM growth_events WHERE organization_id = ?").run(organizationId);
      this.db.query("DELETE FROM app_entitlements WHERE organization_id = ?").run(organizationId);
      this.db.query("DELETE FROM installations WHERE organization_id = ?").run(organizationId);
      this.db.query("DELETE FROM domains WHERE organization_id = ?").run(organizationId);
      this.db.query("DELETE FROM organization_memberships WHERE organization_id = ?").run(organizationId);
      const subscriptions = this.db.query("SELECT s.id FROM subscriptions s JOIN provisioning_requests pr ON pr.order_id = s.initial_order_id WHERE pr.organization_id = ?").all(organizationId) as Array<{ id: string }>;
      for (const subscription of subscriptions) {
        this.db.query("UPDATE subscriptions SET customer_email = '', card_token_enc = NULL, manage_token_hash = ?, updated_at = ? WHERE id = ?")
          .run(hashToken(randomBytes(32).toString("base64url")), timestamp, subscription.id);
      }
      this.db.query(`
        UPDATE payment_orders SET customer_name = '', customer_email = '', company = NULL, provider_payload = NULL, manage_token_hash = ?
        WHERE subscription_id IN (SELECT s.id FROM subscriptions s JOIN provisioning_requests pr ON pr.order_id = s.initial_order_id WHERE pr.organization_id = ?)
           OR id IN (SELECT order_id FROM provisioning_requests WHERE organization_id = ?)
      `).run(hashToken(randomBytes(32).toString("base64url")), organizationId, organizationId);
      this.db.query("DELETE FROM provisioning_requests WHERE organization_id = ?").run(organizationId);
      this.db.query("DELETE FROM workspaces WHERE organization_id = ?").run(organizationId);
      this.db.query("UPDATE organizations SET name = 'Deleted organization', crm_company_id = NULL, idp_tenant_id = NULL, status = 'canceled', updated_at = ? WHERE id = ?").run(timestamp, organizationId);
    });
    transaction();
    return true;
  }
  get isClosed(): boolean {
    return this.closed;
  }
  close(): void {
    this.closed = true;
    this.db.close();
  }
}

class DisabledProvider implements PaymentProvider {
  readonly name = "disabled" as const;

  async begin(): Promise<BeginResult> {
    throw new Error("Payments are not configured");
  }

  async renew(): Promise<ChargeResult> {
    throw new Error("Payments are not configured");
  }

  async recover(): Promise<RecoveryResult> {
    return null;
  }
}

class MockProvider implements PaymentProvider {
  readonly name = "mock" as const;

  constructor(private readonly publicUrl: string) {}

  async begin(order: OrderRow): Promise<BeginResult> {
    return { redirectUrl: `${this.publicUrl}/api/payments/mock/checkout/${encodeURIComponent(order.id)}` };
  }

  async renew(order: OrderRow): Promise<ChargeResult> {
    return { success: true, transactionRef: `mock_${order.id}` };
  }

  async recover(): Promise<RecoveryResult> {
    return null;
  }
}

class MyPOSProvider implements WebhookProvider {
  readonly name = "mypos" as const;
  readonly checkoutUrl: string;
  private readonly client: MyPOSClient;

  constructor(private readonly publicUrl: string, env: Record<string, string | undefined>) {
    const testMode = env.MYPOS_TEST_MODE === "true";
    if (!publicUrl.startsWith("https://") || new URL(publicUrl).port) {
      throw new Error("myPOS mode requires an HTTPS PAYMENTS_PUBLIC_URL without a port");
    }
    if (env.MYPOS_CONFIG_PACK) {
      this.client = new MyPOSClient({ configPack: env.MYPOS_CONFIG_PACK, testMode, source: "AkurAI Platform" });
    } else {
      const storeId = required(env, "MYPOS_STORE_ID");
      const walletNumber = required(env, "MYPOS_WALLET_NUMBER");
      const privateKey = required(env, "MYPOS_PRIVATE_KEY").replaceAll("\\n", "\n");
      const publicCertificate = required(env, "MYPOS_PUBLIC_CERT").replaceAll("\\n", "\n");
      const keyIndex = Number(required(env, "MYPOS_KEY_INDEX"));
      if (!Number.isInteger(keyIndex) || keyIndex < 1) throw new Error("MYPOS_KEY_INDEX must be a positive integer");
      this.client = new MyPOSClient({ storeId, walletNumber, privateKey, publicCertificate, keyIndex, testMode, source: "AkurAI Platform" });
    }
    this.checkoutUrl = this.client.checkoutUrl;
  }

  async begin(order: OrderRow, products: Product[]): Promise<BeginResult> {
    const unitAmount = order.unit_cents / 100;
    const fields = this.client.generatePurchaseFields(
      { items: products.map((product) => ({ article: `${product.name} ${order.interval} subscription`, quantity: 1, price: unitAmount, amount: unitAmount })) },
      order.id,
      {
        urlOk: `${this.publicUrl}/checkout/success?order=${encodeURIComponent(order.id)}`,
        urlCancel: `${this.publicUrl}/checkout/cancel?order=${encodeURIComponent(order.id)}`,
        urlNotify: `${this.publicUrl}/api/payments/webhook`,
        currency: order.currency,
        language: "EN",
        cardTokenRequest: 2,
        paymentParametersRequired: 2,
        paymentMethod: 1,
        customerEmail: order.customer_email,
        note: `AkurAI ${order.interval} subscription`,
        expiresIn: 900,
      },
    );
    return {
      redirectUrl: `${this.publicUrl}/api/payments/mypos/redirect/${encodeURIComponent(order.id)}`,
      payload: fields,
    };
  }

  validateWebhook(formData: FormData): WebhookValidationResult {
    return this.client.validateWebhook(formData);
  }

  async renew(order: OrderRow, subscription: SubscriptionRow, cardToken: string, products: Product[]): Promise<ChargeResult> {
    const unitAmount = subscription.unit_cents / 100;
    const result = await this.client.iaPurchase({
      cardToken,
      orderId: order.id,
      cart: { items: products.map((product) => ({ article: `${product.name} ${subscription.interval} renewal`, quantity: 1, price: unitAmount, amount: unitAmount })) },
      currency: subscription.currency,
      language: "EN",
      note: `AkurAI ${subscription.interval} renewal`,
    });
    return result.success
      ? { success: true, transactionRef: result.ipcTrnref }
      : { success: false, errorCode: `mypos_${result.status}` };
  }

  async recover(order: OrderRow): Promise<RecoveryResult> {
    const result = await this.client.getPaymentStatus({ orderId: order.id });
    if (!result.success) return null;
    if (result.paymentStatus === 1) return { success: true, transactionRef: result.ipcTrnref };
    if (result.paymentStatus === 2) return "pending";
    if (result.paymentStatus === 3 || result.paymentStatus === 4) return { success: false, errorCode: `mypos_payment_${result.paymentStatus}` };
    return null;
  }
}

function configuredProvider(publicUrl: string, env: Record<string, string | undefined>): PaymentProvider {
  const name = env.PAYMENTS_PROVIDER ?? "disabled";
  if (name === "disabled") return new DisabledProvider();
  if (name === "mock") return new MockProvider(publicUrl);
  if (name === "mypos") return new MyPOSProvider(publicUrl, env);
  throw new Error("PAYMENTS_PROVIDER must be disabled, mock, or mypos");
}

function configuredRecoveryDelivery(env: Record<string, string | undefined>): RecoveryDelivery | null {
  const endpoint = env.PAYMENTS_RECOVERY_DELIVERY_URL;
  if (!endpoint) return null;
  const authorization = env.PAYMENTS_RECOVERY_DELIVERY_BEARER;
  return {
    async send(input): Promise<void> {
      const response = await fetch(endpoint, {
        method: "POST",
        headers: { "content-type": "application/json", ...(authorization ? { authorization: `Bearer ${authorization}` } : {}) },
        body: JSON.stringify({
          to: input.to,
          subject: "Your AkurAI subscription management link",
          text: `Use this secure link to manage your AkurAI subscription: ${input.managementUrl}`,
        }),
      });
      if (!response.ok) throw new Error(`delivery_${response.status}`);
    },
  };
}

export function createPaymentService(options: ServiceOptions = {}): PaymentService {
  const env = options.env ?? Bun.env;
  const now = options.now ?? (() => new Date());
  const publicUrl = trimSlash(options.publicUrl ?? env.PAYMENTS_PUBLIC_URL ?? `http://127.0.0.1:${env.PAYMENTS_PORT ?? "8198"}`);
  const catalogPath = options.catalogPath ?? env.PAYMENTS_CATALOG;
  const catalog = options.catalog ?? (catalogPath ? loadCatalog(catalogPath) : validateCatalog(embeddedCatalog));
  const provider = options.paymentProvider ?? configuredProvider(publicUrl, env);
  const tokenKeys = tokenKeyring(env, provider.name);
  const recoveryDelivery = options.recoveryDelivery ?? configuredRecoveryDelivery(env);
  const recoveryAttempts = new Map<string, number[]>();

  function allowRecovery(request: Request): boolean {
    const nowMs = now().getTime();
    const key = request.headers.get("cf-connecting-ip") ?? request.headers.get("x-forwarded-for")?.split(",", 1)[0]?.trim() ?? "unknown";
    const attempts = (recoveryAttempts.get(key) ?? []).filter((at) => nowMs - at < 60_000);
    if (attempts.length >= 3) return false;
    attempts.push(nowMs);
    recoveryAttempts.set(key, attempts);
    return true;
  }

  async function sendManagementRecovery(subscription: SubscriptionRow, requestedBy: string | null): Promise<boolean> {
    if (!recoveryDelivery) {
      store.recordRecoveryDelivery(subscription.id, requestedBy, false, "delivery_not_configured");
      return false;
    }
    const token = randomBytes(32).toString("base64url");
    const tokenHash = hashToken(token);
    const rotated = store.replaceManageToken(subscription.manage_token_hash, tokenHash);
    if (!rotated) {
      store.recordRecoveryDelivery(subscription.id, requestedBy, false, "token_rotation_conflict");
      return false;
    }
    try {
      await recoveryDelivery.send({ to: subscription.customer_email, managementUrl: `${publicUrl}/subscriptions/manage/${encodeURIComponent(token)}` });
      store.recordManagementTokenRotation(subscription.id, requestedBy);
      store.recordRecoveryDelivery(subscription.id, requestedBy, true, null);
      return true;
    } catch (error) {
      store.replaceManageToken(tokenHash, subscription.manage_token_hash);
      store.recordRecoveryDelivery(subscription.id, requestedBy, false, safeError(error));
      return false;
    }
  }
  const identity = identityConfig(env);
  const oidc = oidcConfig(env, publicUrl);
  const oidcStates = new Map<string, { verifier: string; expiresAt: number }>();
  const browserSessions = new Map<string, BrowserSession>();
  const authenticatedRequestUser = (request: Request): string | null => {
    const sessionId = requestCookie(request, "__Host-akurai-platform-session");
    if (sessionId) {
      const session = browserSessions.get(sessionId);
      if (session && session.expiresAt > now().getTime()) return session.userId;
      browserSessions.delete(sessionId);
    }
    return authenticatedUser(request, identity);
  };
  const store = new Store(resolve(options.dbPath ?? env.PAYMENTS_DB ?? "data/payments.db"), now);
  const productById = new Map(catalog.products.map((product) => [product.id, product]));
  let renewalsRunning = false;
  const checkoutAttempts = new Map<string, number[]>();

  function allowCheckout(request: Request): boolean {
    const nowMs = now().getTime();
    const key = request.headers.get("cf-connecting-ip") ?? request.headers.get("x-forwarded-for")?.split(",", 1)[0]?.trim() ?? "unknown";
    const attempts = (checkoutAttempts.get(key) ?? []).filter((at) => nowMs - at < 60_000);
    if (attempts.length >= 5) return false;
    attempts.push(nowMs);
    checkoutAttempts.set(key, attempts);
    return true;
  }

  async function completeInitial(order: OrderRow, cardToken: string | undefined, transactionRef?: string): Promise<void> {
    const encrypted = cardToken ? encrypt(cardToken, tokenKeys) : null;
    store.completeInitial(order, encrypted, transactionRef);
  }

  function oidcRedirect(location: string, cookies: string[] = []): Response {
    const headers = new Headers({ location, "cache-control": "no-store" });
    for (const cookie of cookies) headers.append("set-cookie", cookie);
    return new Response(null, { status: 302, headers });
  }

  async function handleOidc(request: Request, url: URL): Promise<Response | null> {
    if (request.method !== "GET" || !url.pathname.startsWith("/auth/")) return null;
    if (url.pathname === "/auth/logout") {
      const sessionId = requestCookie(request, "__Host-akurai-platform-session");
      if (sessionId) browserSessions.delete(sessionId);
      return oidcRedirect("/", ["__Host-akurai-platform-session=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0"]);
    }
    if (!oidc) return htmlPage("Sign-in unavailable", "AkurAI ID sign-in is not configured for this portal.", 503);
    if (url.pathname === "/auth/login") {
      const nowMs = now().getTime();
      for (const [key, value] of oidcStates) if (value.expiresAt <= nowMs) oidcStates.delete(key);
      const state = randomBytes(32).toString("base64url");
      const verifier = randomBytes(32).toString("base64url");
      oidcStates.set(state, { verifier, expiresAt: nowMs + 10 * 60_000 });
      const authorize = new URL(oidc.authorizationEndpoint);
      authorize.search = new URLSearchParams({
        response_type: "code",
        client_id: oidc.clientId,
        redirect_uri: oidc.redirectUri,
        scope: "openid profile email",
        state,
        code_challenge: createHash("sha256").update(verifier).digest("base64url"),
        code_challenge_method: "S256",
      }).toString();
      return oidcRedirect(authorize.toString(), [`__Host-akurai-platform-oidc=${state}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=600`]);
    }
    if (url.pathname !== "/auth/callback") return null;
    const state = url.searchParams.get("state") ?? "";
    const pending = oidcStates.get(state);
    oidcStates.delete(state);
    const clearState = "__Host-akurai-platform-oidc=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0";
    if (!pending || pending.expiresAt <= now().getTime() || requestCookie(request, "__Host-akurai-platform-oidc") !== state || url.searchParams.has("error")) {
      return oidcRedirect("/?auth=failed#customer-portal", [clearState]);
    }
    const code = url.searchParams.get("code");
    if (!code) return oidcRedirect("/?auth=failed#customer-portal", [clearState]);
    const tokenResponse = await fetch(oidc.tokenEndpoint, {
      method: "POST",
      headers: {
        accept: "application/json",
        authorization: `Basic ${Buffer.from(`${oidc.clientId}:${oidc.clientSecret}`).toString("base64")}`,
        "content-type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        grant_type: "authorization_code",
        code,
        redirect_uri: oidc.redirectUri,
        code_verifier: pending.verifier,
      }),
    });
    if (!tokenResponse.ok) return oidcRedirect("/?auth=failed#customer-portal", [clearState]);
    const tokens = await tokenResponse.json() as Record<string, unknown>;
    const accessToken = typeof tokens.access_token === "string" ? tokens.access_token : "";
    const expiresIn = typeof tokens.expires_in === "number" && Number.isSafeInteger(tokens.expires_in) && tokens.expires_in > 0 ? tokens.expires_in : 0;
    if (!accessToken || !expiresIn) return oidcRedirect("/?auth=failed#customer-portal", [clearState]);
    const userInfoResponse = await fetch(`${oidc.issuer}/userinfo`, {
      headers: { accept: "application/json", authorization: `Bearer ${accessToken}` },
    });
    if (!userInfoResponse.ok) return oidcRedirect("/?auth=failed#customer-portal", [clearState]);
    const userInfo = await userInfoResponse.json() as Record<string, unknown>;
    const userId = typeof userInfo.sub === "string" ? boundedIdentity(userInfo.sub) : null;
    if (!userId) return oidcRedirect("/?auth=failed#customer-portal", [clearState]);
    const sessionId = randomBytes(32).toString("base64url");
    const sessionExpiresAt = Math.min(now().getTime() + expiresIn * 1000, now().getTime() + 8 * 60 * 60_000);
    browserSessions.set(sessionId, { userId, expiresAt: sessionExpiresAt });
    const maxAge = Math.max(1, Math.floor((sessionExpiresAt - now().getTime()) / 1000));
    return oidcRedirect("/#customer-portal", [
      clearState,
      `__Host-akurai-platform-session=${sessionId}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`,
    ]);
  }

  async function fetchHandler(request: Request): Promise<Response> {
    const url = new URL(request.url);
    try {
      const oidcResponse = await handleOidc(request, url);
      if (oidcResponse) return oidcResponse;
      if (request.method === "GET" && url.pathname === "/api/payments/health") {
        return json({ ok: true, provider: provider.name, paymentsEnabled: provider.name !== "disabled", products: catalog.products.filter((product) => product.available !== false).length });
      }
      if (request.method === "GET" && url.pathname === "/api/payments/catalog") {
        return json({
          currency: catalog.currency,
          monthlyUnitCents: catalog.monthly_unit_cents,
          annualUnitCents: Math.round(catalog.monthly_unit_cents * 12 * (100 - catalog.annual_discount_percent) / 100),
          annualDiscountPercent: catalog.annual_discount_percent,
          products: catalog.products,
          provider: provider.name,
          paymentsEnabled: provider.name !== "disabled",
        });
      }
      if (request.method === "GET" && url.pathname === "/api/payments/growth-events") {
        const orgId = url.searchParams.get("organizationId");
        if (!orgId) return json({ error: "organizationId query parameter is required" }, 400);
        const userId = authenticatedRequestUser(request);
        if (!userId) return json({ error: "Authentication required" }, 401);
        if (!store.organizationForUser(orgId, userId)) return json({ error: "Not found" }, 404);
        const stage = url.searchParams.get("stage");
        const limit = Math.min(Math.max(Number(url.searchParams.get("limit") ?? "50"), 1), 500);
        const events = store.growthEvents(orgId, stage ?? undefined, limit);
        return json({ events: events.map(publicGrowthEvent), count: events.length });
      }
      const platformSegments = parsePlatformPath(url.pathname);
      if (platformSegments) {
        const userId = authenticatedRequestUser(request);
        if (!userId) return json({ error: "Authentication required" }, 401);
        let body: Record<string, unknown> = {};
        if (request.method !== "GET" && request.method !== "DELETE") {
          if (Number(request.headers.get("content-length") ?? 0) > 64_000) return json({ error: "Request is too large" }, 413);
          if (!request.headers.get("content-type")?.startsWith("application/json")) return json({ error: "Expected application/json" }, 415);
          try {
            const parsed = await boundedJson(request, 64_000);
            if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return json({ error: "JSON object required" }, 400);
            body = parsed as Record<string, unknown>;
          } catch (error) {
            return json({ error: error instanceof RangeError ? "Request is too large" : "Invalid JSON" }, error instanceof RangeError ? 413 : 400);
          }
        }
        const [resource, resourceId, action, childId] = platformSegments;
        if (resource === "workspaces" && resourceId) {
          const workspace = store.workspace(resourceId);
          if (!workspace || !store.organizationForUser(workspace.organization_id, userId)) return json({ error: "Not found" }, 404);
          if (request.method !== "GET") return json({ error: "Method not allowed" }, 405);
          return json({ workspace: publicWorkspace(workspace) });
        }
        if (resource === "organizations" && !resourceId) {
          if (request.method === "GET") return json({ organizations: store.organizationsForUser(userId).map(publicOrganization) });
          if (request.method !== "POST") return json({ error: "Method not allowed" }, 405);
          const name = boundedText(body.name, 160, "Organization name");
          const workspaceName = boundedText(body.workspaceName ?? "Managed Workspace", 160, "Workspace name");
          const crmCompanyId = boundedOptionalText(body.crmCompanyId, 160, "CRM company ID");
          const idpTenantId = boundedOptionalText(body.idpTenantId, 160, "IDP tenant ID");
          if (crmCompanyId || idpTenantId) return json({ error: "External CRM/IDP links require verified binding" }, 400);
          const result = store.createOrganization(name, userId, null, null, workspaceName);
          return json({ organization: publicOrganization(result.organization), workspace: publicWorkspace(result.workspace), membership: publicMembership(result.membership) }, 201);
        }
        if (resource !== "organizations" || !resourceId) return json({ error: "Not found" }, 404);
        const organization = store.organizationForUser(resourceId, userId);
        if (!organization) return json({ error: "Not found" }, 404);
        const membership = store.membershipForUser(resourceId, userId);
        if (!membership) return json({ error: "Not found" }, 404);
        if (!action) {
          if (request.method !== "GET") return json({ error: "Method not allowed" }, 405);
          return json({
            organization: publicOrganization(organization),
            role: membership.role,
            workspaces: store.workspacesForOrganization(resourceId).map(publicWorkspace),
          });
        }
        if (action === "workspaces") {
          if (!childId) {
            if (request.method === "GET") return json({ workspaces: store.workspacesForOrganization(resourceId).map(publicWorkspace) });
            if (request.method !== "POST") return json({ error: "Method not allowed" }, 405);
            if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
            const workspace = store.createWorkspace(resourceId, boundedText(body.name, 160, "Workspace name"), userId);
            return json({ workspace: publicWorkspace(workspace) }, 201);
          }
          const workspace = store.workspace(childId);
          if (!workspace || workspace.organization_id !== resourceId) return json({ error: "Not found" }, 404);
          if (request.method === "GET") return json({ workspace: publicWorkspace(workspace) });
          if (request.method !== "PATCH") return json({ error: "Method not allowed" }, 405);
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          const state = body.status;
          if (!isOneOf(state, ["provisioning", "active", "suspended", "deprovisioned"])) return json({ error: "Invalid workspace status" }, 400);
          return json({ workspace: publicWorkspace(store.setWorkspaceState(resourceId, childId, state, userId)!) });
        }
        if (action === "members") {
          if (!childId && request.method === "GET") return json({ memberships: store.membershipForOrganization(resourceId).map(publicMembership) });
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          if (!childId && request.method === "POST") {
            const memberUserId = boundedText(body.userId, 200, "User ID");
            const role = body.role === "admin" ? "admin" : body.role === "member" ? "member" : null;
            if (!role) return json({ error: "Role must be admin or member" }, 400);
            return json({ membership: publicMembership(store.addMembership(resourceId, memberUserId, role, userId)) }, 201);
          }
          if (childId && request.method === "DELETE") {
            const revoked = store.revokeMembership(resourceId, childId, userId);
            return revoked ? json({ membership: publicMembership(revoked) }) : json({ error: "Not found" }, 404);
          }
          return json({ error: "Method not allowed" }, 405);
        }
        if (action === "entitlements") {
          if (!childId && request.method === "GET") {
            const workspaceId = url.searchParams.get("workspaceId") ?? undefined;
            if (workspaceId) {
              const workspace = store.workspace(workspaceId);
              if (!workspace || workspace.organization_id !== resourceId) return json({ error: "Not found" }, 404);
            }
            return json({ entitlements: store.entitlements(resourceId, workspaceId).map(publicEntitlement) });
          }
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          const workspaceId = boundedText(body.workspaceId ?? url.searchParams.get("workspaceId"), 100, "Workspace ID");
          const appId = boundedText(body.appId ?? childId, 120, "App ID");
          const workspace = store.workspace(workspaceId);
          if (!workspace || workspace.organization_id !== resourceId) return json({ error: "Not found" }, 404);
          const granted = body.action !== "revoke" && body.status !== "revoked" && request.method !== "DELETE";
          if (request.method !== "POST" && request.method !== "PUT" && request.method !== "DELETE") return json({ error: "Method not allowed" }, 405);
          return json({ entitlement: publicEntitlement(store.setEntitlement(resourceId, workspaceId, appId, granted, userId)) });
        }
        if (action === "installations") {
          if (!childId && request.method === "GET") {
            const workspaceId = url.searchParams.get("workspaceId") ?? undefined;
            if (workspaceId) {
              const workspace = store.workspace(workspaceId);
              if (!workspace || workspace.organization_id !== resourceId) return json({ error: "Not found" }, 404);
            }
            return json({ installations: store.installations(resourceId, workspaceId).map(publicInstallation) });
          }
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          if (!childId && request.method === "POST") {
            const workspaceId = boundedText(body.workspaceId, 100, "Workspace ID");
            const appId = boundedText(body.appId, 120, "App ID");
            const workspace = store.workspace(workspaceId);
            if (!workspace || workspace.organization_id !== resourceId) return json({ error: "Not found" }, 404);
            return json({ installation: publicInstallation(store.createInstallation(resourceId, workspaceId, appId, userId)) }, 201);
          }
          if (childId && request.method === "PATCH") {
            const state = body.status;
            if (!isOneOf(state, ["pending", "installing", "installed", "suspended", "uninstalled", "failed"])) return json({ error: "Invalid installation status" }, 400);
            const installation = store.setInstallationState(resourceId, childId, state, userId);
            return installation ? json({ installation: publicInstallation(installation) }) : json({ error: "Not found" }, 404);
          }
          return json({ error: "Method not allowed" }, 405);
        }
        if (action === "domains") {
          if (!childId && request.method === "GET") {
            const workspaceId = url.searchParams.get("workspaceId") ?? undefined;
            if (workspaceId) {
              const workspace = store.workspace(workspaceId);
              if (!workspace || workspace.organization_id !== resourceId) return json({ error: "Not found" }, 404);
            }
            return json({ domains: store.domains(resourceId, workspaceId).map(publicDomain) });
          }
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          if (!childId && request.method === "POST") {
            const workspaceId = body.workspaceId == null ? null : boundedOptionalText(body.workspaceId, 100);
            if (workspaceId && (!store.workspace(workspaceId) || store.workspace(workspaceId)?.organization_id !== resourceId)) return json({ error: "Not found" }, 404);
            const hostname = boundedText(body.hostname, 253, "Hostname").toLowerCase();
            if (!/^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))+$/.test(hostname)) return json({ error: "Invalid hostname" }, 400);
            try {
              return json({ domain: publicDomain(store.createDomain(resourceId, workspaceId, hostname, userId)) }, 201);
            } catch (error) {
              if (error instanceof Error && error.message.includes("another organization")) return json({ error: error.message }, 409);
              throw error;
            }
          }
          if (childId && request.method === "PATCH") {
            const state = body.status;
            if (!isOneOf(state, ["pending", "verified", "active", "disabled"])) return json({ error: "Invalid domain status" }, 400);
            const domain = store.setDomainState(resourceId, childId, state, userId);
            return domain ? json({ domain: publicDomain(domain) }) : json({ error: "Not found" }, 404);
          }
          return json({ error: "Method not allowed" }, 405);
        }
        if (action === "subscription-management") {
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          const subscription = store.subscriptionForOrganization(resourceId);
          if (!subscription) return json({ error: "Subscription not found" }, 404);
          if (childId === "deliveries" && request.method === "GET") return json({ deliveries: store.recoveryDeliveries(resourceId) });
          if (childId === "recovery" && request.method === "POST") {
            const sent = await sendManagementRecovery(subscription, userId);
            return json({ status: sent ? "sent" : "failed" }, sent ? 202 : 503);
          }
          if (childId) return json({ error: "Not found" }, 404);
          if (request.method === "GET") return json(publicSubscription(subscription, productById));
          if (request.method !== "POST") return json({ error: "Method not allowed" }, 405);
          const token = randomBytes(32).toString("base64url");
          const rotated = store.rotateManageToken(subscription.manage_token_hash, hashToken(token));
          return rotated
            ? json({ manageToken: token, manageUrl: `${publicUrl}/subscriptions/manage/${encodeURIComponent(token)}`, message: "The previous management link has been revoked." })
            : json({ error: "Subscription management token could not be rotated" }, 409);
        }
        if (action === "backups" && request.method === "GET") {
          return json({ backups: store.backups(resourceId) });
        }
        if (action === "invoices" && request.method === "GET") {
          return json({ invoices: store.invoices(resourceId) });
        }
        if (action === "audit" && request.method === "GET") {
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          return json({ lifecycle: store.lifecycle(resourceId).map(publicLifecycleRevision), payments: store.paymentEvents(resourceId) });
        }
        if (action === "support" && request.method === "POST") {
          const subject = boundedText(body.subject, 160, "Support subject");
          const message = boundedText(body.message, 4_000, "Support message");
          const ticketId = `sup_${randomUUID()}`;
          store.recordGrowthEvent({
            id: `ge_${createHash("sha256").update(`support:${ticketId}`).digest("hex").slice(0, 14)}`,
            organization_id: resourceId, workspace_id: null, name: "support_requested/v1", stage: "retention",
            occurred_at: now().toISOString(), schema_version: GROWTH_EVENT_SCHEMA_VERSION, subscription_id: null,
            customer_email: null, actor_user_id: userId, attribution_json: null, consent_json: null,
            revenue_json: null, product_ids_json: null, interval: null,
            metadata_json: JSON.stringify({ ticketId, subject, messageLength: message.length }), created_at: now().toISOString(),
          });
          return json({ ticketId, status: "received" }, 201);
        }
        if (action === "lifecycle") {
          if (membership.role !== "admin") return json({ error: "Administrator role required" }, 403);
          if (childId === "export" && request.method === "POST") return json({ export: store.exportOrganization(resourceId, userId) }, 201);
          if (childId === "restore" && request.method === "POST") {
            const exportId = boundedText(body.exportId, 100, "Export ID");
            const restored = store.restoreOrganization(resourceId, exportId, userId);
            return restored ? json({ export: restored }) : json({ error: "A restorable export is required before retention ends" }, 409);
          }
          if (childId === "delete-request" && request.method === "POST") {
            const deletion = store.requestDeletion(resourceId, userId);
            return json({ deletionId: deletion.id, deleteToken: deletion.token, expiresAt: deletion.expiresAt, confirmation: `DELETE ${resourceId}` }, 201);
          }
          if (childId === "delete-confirm" && request.method === "POST") {
            if (body.confirmation !== `DELETE ${resourceId}`) return json({ error: "Explicit deletion confirmation is required" }, 400);
            const deletionId = boundedText(body.deletionId, 100, "Deletion ID");
            const deleteToken = boundedText(body.deleteToken, 100, "Deletion token");
            return store.confirmDeletion(resourceId, deletionId, deleteToken, userId)
              ? json({ status: "deleted" })
              : json({ error: "Deletion is unavailable until retention has ended or the gate expired" }, 409);
          }
          if (request.method !== "GET") return json({ error: "Method not allowed" }, 405);
          const entityType = url.searchParams.get("entityType");
          const entityId = url.searchParams.get("entityId");
          if (entityType && !isOneOf(entityType, ["organization", "workspace", "membership", "entitlement", "installation", "domain"])) return json({ error: "Invalid entity type" }, 400);
          const revisions = store.lifecycle(resourceId, entityType as LifecycleEntity | undefined, entityId ?? undefined);
          return json({ revisions: revisions.map(publicLifecycleRevision), truncated: !entityType && revisions.length === 500 });
        }
        return json({ error: "Not found" }, 404);
      }
      if (request.method === "POST" && url.pathname === "/api/contact") {
        if (Number(request.headers.get("content-length") ?? 0) > 16_384) return json({ error: "Request is too large" }, 413);
        if (!request.headers.get("content-type")?.startsWith("application/json")) return json({ error: "Expected application/json" }, 415);
        let body: Record<string, unknown>;
        try {
          const parsed = await boundedJson(request, 16_384);
          if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return json({ error: "JSON object required" }, 400);
          body = parsed as Record<string, unknown>;
        } catch (error) {
          return json({ error: error instanceof RangeError ? "Request is too large" : "Invalid JSON" }, error instanceof RangeError ? 413 : 400);
        }
        const name = boundedText(body.name, 160, "Name");
        const email = boundedText(body.email, 254, "Email");
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return json({ error: "Valid email is required" }, 400);
        const outcome = isOneOf(body.outcome, ["demo", "waitlist", "contact"]) ? body.outcome : "contact";
        const company = boundedOptionalText(body.company, 160, "Company");
        const intent = boundedOptionalText(body.intent, 500, "Intent");
        const locale = boundedText(body.locale ?? "en", 10, "Locale");
        const productIds = Array.isArray(body.productIds) ? (body.productIds as string[]).filter((id): id is string => typeof id === "string" && id.length <= 120).join(",") : null;
        const consent = body.consent as Record<string, unknown> | undefined;
        if (!consent || !consent.noticeVersion || !Array.isArray(consent.grants) || !consent.grants.length) {
          return json({ error: "Consent with notice version and at least one grant is required" }, 400);
        }
        const attribution: Record<string, unknown> = {};
        const utmKeys = ["utmSource", "utmMedium", "utmCampaign", "utmTerm", "utmContent"] as const;
        for (const key of utmKeys) {
          const val = body[key];
          if (typeof val === "string" && val) attribution[key] = val;
        }
        if (typeof body.referrer === "string" && body.referrer) attribution.referrer = body.referrer;
        if (typeof body.landingPage === "string" && body.landingPage) attribution.landingPage = body.landingPage;
        const timestamp = now().toISOString();
        const submissionId = `cs_${randomUUID()}`;
        const consentJson = JSON.stringify({
          consentedAt: timestamp,
          noticeVersion: consent.noticeVersion,
          grants: consent.grants,
          explicit: consent.explicit === true,
        });
        const attributionJson = Object.keys(attribution).length ? JSON.stringify(attribution) : null;
        const productIdsStr = productIds;
        store.insertContactSubmission({
          id: submissionId, name, email, company, outcome, intent, locale,
          productIds: productIdsStr, attributionJson, consentJson,
          growthEventId: null, timestamp,
        });
        const crmBaseUrl = trimSlash(env.PAYMENTS_CRM_API_BASE_URL ?? "https://akurai-crm.olibuijr.com:8103");
        const crmApiKey = env.PAYMENTS_CRM_API_KEY ?? "";
        if (crmApiKey) {
          trySyncContactToCRM(submissionId, store, { crmBaseUrl, crmApiKey, name, email, company, outcome, intent }).catch(
            (err: unknown) => console.error(JSON.stringify({ event: "contact_crm_sync_error", submissionId, error: safeError(err) }))
          );
        }
        return json({
          id: submissionId,
          outcome,
          message: locale.startsWith("is")
            ? "Skráning móttekin. Við höfum samband innan skamms."
            : "Your inquiry has been received. We will be in touch shortly.",
        }, 201);
      }
      if (request.method === "POST" && url.pathname === "/api/payments/recovery") {
        if (!allowRecovery(request)) return json({ error: "Too many recovery attempts; try again in a minute" }, 429);
        if (Number(request.headers.get("content-length") ?? 0) > 4_096) return json({ error: "Recovery request is too large" }, 413);
        if (!request.headers.get("content-type")?.startsWith("application/json")) return json({ error: "Expected application/json" }, 415);
        let body: Record<string, unknown>;
        try {
          const parsed = await boundedJson(request, 4_096);
          if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return json({ error: "JSON object required" }, 400);
          body = parsed as Record<string, unknown>;
        } catch (error) {
          return json({ error: error instanceof RangeError ? "Recovery request is too large" : "Invalid JSON" }, error instanceof RangeError ? 413 : 400);
        }
        const email = boundedText(body.email, 254, "Email").toLowerCase();
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return json({ error: "Valid email is required" }, 400);
        const subscription = store.subscriptionForEmail(email);
        if (subscription) await sendManagementRecovery(subscription, null);
        return json({ status: "accepted", message: "If this email is registered, a secure management link is being sent." }, 202);
      }
      if (request.method === "POST" && url.pathname === "/api/payments/checkout") {
        if (!allowCheckout(request)) return json({ error: "Too many checkout attempts; try again in a minute" }, 429);
        if (provider.name === "disabled") return json({ error: "Payments are not configured" }, 503);
        if (Number(request.headers.get("content-length") ?? 0) > 16_384) return json({ error: "Checkout request is too large" }, 413);
        if (!request.headers.get("content-type")?.startsWith("application/json")) return json({ error: "Expected application/json" }, 415);
        let body: unknown;
        try {
          body = await boundedJson(request, 16_384);
        } catch (error) {
          return json({ error: error instanceof RangeError ? "Checkout request is too large" : "Invalid JSON" }, error instanceof RangeError ? 413 : 400);
        }
        const idempotencyKey = request.headers.get("Idempotency-Key")?.trim() || null;
        if (idempotencyKey) {
          if (idempotencyKey.length > 200) return json({ error: "Idempotency-Key is too long" }, 400);
          const existing = store.orderByIdempotencyKey(idempotencyKey);
          if (existing) return json({ orderId: existing.id, status: existing.status, amountCents: existing.amount_cents, currency: existing.currency, idempotent: true }, 200);
        }
        let quote: CheckoutQuote;
        try { quote = quoteCheckout(catalog, body); } catch (error) { return json({ error: errorMessage(error) }, 400); }
        const manageToken = randomBytes(32).toString("base64url");
        let order: OrderRow;
        try {
          order = store.createInitial(quote, provider.name, hashToken(manageToken), idempotencyKey, authenticatedRequestUser(request));
        } catch (error) {
          const existing = idempotencyKey ? store.orderByIdempotencyKey(idempotencyKey) : null;
          if (existing) return json({ orderId: existing.id, status: existing.status, amountCents: existing.amount_cents, currency: existing.currency, idempotent: true }, 200);
          throw error;
        }
        try {
          const begin = await provider.begin(order, quote.products);
          if (begin.payload) store.setPayload(order.id, begin.payload);
          return json({
            orderId: order.id,
            redirectUrl: begin.redirectUrl,
            manageToken,
            amountCents: order.amount_cents,
            currency: order.currency,
          }, 201);
        } catch (error) {
          store.markOrder(order.id, "failed", "checkout_initialization_failed");
          console.error(JSON.stringify({ event: "payment_checkout_failed", orderId: order.id, provider: provider.name, error: safeError(error) }));
          return json({ error: "Payment checkout could not be started" }, 502);
        }
      }

      const orderStatus = matchPath(url.pathname, "/api/payments/orders/");
      if (request.method === "GET" && orderStatus) {
        const order = store.order(orderStatus);
        if (!order) return json({ error: "Order not found" }, 404);
        const provisioning = store.provisioningRequest(orderStatus);
        return json(publicOrder(order, productById, provisioning));
      }

      if (request.method === "POST" && url.pathname === "/api/payments/webhook") {
        if (provider.name !== "mypos") return new Response("Not available", { status: 404 });
        const result = (provider as WebhookProvider).validateWebhook(await request.formData());
        if (!result.isValid) return new Response("Invalid signature", { status: 400 });
        const order = store.order(result.orderId);
        if (!order) return new Response("Order not found", { status: 404 });
        if (Math.round(result.amount * 100) !== order.amount_cents || result.currency !== order.currency) {
          return new Response("Payment mismatch", { status: 409 });
        }
        if (result.method === "IPCPurchaseNotify") {
          await completeInitial(order, result.cardToken, result.transactionRef);
        } else if (result.method === "IPCPurchaseRollback") {
          store.rollbackOrder(order.id, result.transactionRef);
        } else {
          return new Response("Unsupported notification", { status: 400 });
        }
        return new Response("OK", { headers: { "content-type": "text/plain; charset=utf-8" } });
      }

      const myposRedirect = matchPath(url.pathname, "/api/payments/mypos/redirect/");
      if (request.method === "GET" && myposRedirect && provider.name === "mypos") {
        const order = store.order(myposRedirect);
        if (!order || order.status !== "pending" || !order.provider_payload) return htmlPage("Checkout unavailable", "This checkout session is no longer available.", 404);
        const fields = JSON.parse(order.provider_payload) as PaymentFormFields;
        return paymentRedirectPage((provider as MyPOSProvider).checkoutUrl, fields);
      }

      const mockCheckout = matchPath(url.pathname, "/api/payments/mock/checkout/");
      if (request.method === "GET" && mockCheckout && provider.name === "mock") {
        const order = store.order(mockCheckout);
        if (!order || order.status !== "pending") return htmlPage("Checkout unavailable", "This mock checkout session is no longer available.", 404);
        return mockCheckoutPage(order, productById);
      }

      const mockOutcome = url.pathname.match(/^\/api\/payments\/mock\/orders\/([^/]+)\/(approve|decline|cancel)$/);
      if (request.method === "POST" && mockOutcome && provider.name === "mock") {
        const order = store.order(decodeURIComponent(mockOutcome[1]));
        if (!order) return htmlPage("Order not found", "The mock order does not exist.", 404);
        const outcome = mockOutcome[2];
        if (outcome === "approve" && order.status === "pending") {
          await completeInitial(order, `mock_card_${order.id}`, `mock_txn_${order.id}`);
          return redirect(`${publicUrl}/checkout/success?order=${encodeURIComponent(order.id)}`);
        }
        if (outcome === "decline" && order.status === "pending") store.markOrder(order.id, "failed", "mock_declined");
        if (outcome === "cancel" && order.status === "pending") store.markOrder(order.id, "canceled", "customer_canceled");
        return redirect(`${publicUrl}/checkout/${outcome === "cancel" ? "cancel" : "success"}?order=${encodeURIComponent(order.id)}`);
      }

      const manageApi = matchPath(url.pathname, "/api/payments/subscriptions/");
      if (manageApi && validManageToken(manageApi)) {
        const hash = hashToken(manageApi);
        if (request.method === "GET") {
          const subscription = store.subscriptionByManageHash(hash);
          if (!subscription) return json({ error: "Subscription not found" }, 404);
          return json(publicSubscription(subscription, productById));
        }
        if (request.method === "POST" && url.searchParams.get("action") === "cancel") {
          const subscription = store.cancelSubscription(hash);
          if (!subscription) return json({ error: "Subscription not found" }, 404);
          if (request.headers.get("accept")?.includes("application/json")) return json(publicSubscription(subscription, productById));
          return redirect(`${publicUrl}/subscriptions/manage/${encodeURIComponent(manageApi)}`);
        }
        if (request.method === "POST" && url.searchParams.get("action") === "rotate-token") {
          const newToken = randomBytes(32).toString("base64url");
          const subscription = store.rotateManageToken(hash, hashToken(newToken));
          if (!subscription) return json({ error: "Subscription not found" }, 404);
          return json({
            manageToken: newToken,
            manageUrl: `${publicUrl}/subscriptions/manage/${encodeURIComponent(newToken)}`,
            message: "Management token rotated. The previous token is no longer valid.",
          });
        }
      }

      const managePage = matchPath(url.pathname, "/subscriptions/manage/");
      if (request.method === "GET" && managePage && validManageToken(managePage)) {
        const subscription = store.subscriptionByManageHash(hashToken(managePage));
        if (!subscription) return htmlPage("Subscription not found", "This management link is invalid or has expired.", 404);
        return subscriptionPage(subscription, managePage, productById);
      }

      if (request.method === "GET" && url.pathname === "/checkout/success") return checkoutResultPage("success");
      if (request.method === "GET" && url.pathname === "/checkout/cancel") return checkoutResultPage("cancel");

      return json({ error: "Not found" }, 404);
    } catch (error) {
      const message = errorMessage(error);
      if (/ is required$| is too long$|require verified binding$|does not belong to this organization$|does not match organization$/.test(message)) return json({ error: message }, 400);
      console.error(JSON.stringify({ event: "payment_request_error", path: url.pathname, error: safeError(error) }));
      if (url.pathname.startsWith("/auth/")) return htmlPage("Sign-in unavailable", "AkurAI ID could not complete the sign-in request. Please try again.", 502);
      return json({ error: "Payment service error" }, 500);
    }
  }

  async function runRenewals(): Promise<void> {
    if (renewalsRunning) return;
    renewalsRunning = true;
    try {
      const deprovisioned = store.deprovisionExpiredSubscriptions();
      if (deprovisioned > 0) console.error(JSON.stringify({ event: "deprovisioning_completed", count: deprovisioned }));
      const stale = store.staleOrderCleanup();
      if (stale > 0) console.error(JSON.stringify({ event: "pending_order_cleanup", count: stale }));
      if (provider.name === "disabled") return;
      for (const subscription of store.dueSubscriptions()) {
        if (!subscription.card_token_enc) continue;
        const leaseToken = store.acquireRenewalFence(subscription.id);
        if (!leaseToken) continue;
        try {
          let order = store.pendingRenewal(subscription.id);
          if (order) {
            const recovered = await provider.recover(order);
            if (recovered === "pending") continue;
            if (recovered?.success) {
              store.completeRenewal(order, recovered.transactionRef);
              continue;
            }
            if (recovered && !recovered.success) {
              store.failRenewal(order, recovered.errorCode ?? "renewal_failed");
              continue;
            }
            store.failRenewal(order, "renewal_recovery_failed");
            continue;
          }
          order = store.createRenewal(subscription);
          const products = productsFor(JSON.parse(subscription.products_json) as string[], productById);
          const cardToken = decrypt(subscription.card_token_enc, tokenKeys);
          if (!isCurrentTokenKey(subscription.card_token_enc, tokenKeys)) {
            store.setSubscriptionCardToken(subscription.id, encrypt(cardToken, tokenKeys));
          }
          const result = await provider.renew(order, subscription, cardToken, products);
          if (result.success) store.completeRenewal(order, result.transactionRef);
          else store.failRenewal(order, result.errorCode ?? "renewal_failed");
        } catch (error) {
          console.error(JSON.stringify({ event: "payment_renewal_error", subscriptionId: subscription.id, provider: provider.name, error: safeError(error) }));
        } finally {
          store.releaseRenewalFence(subscription.id, leaseToken);
        }
      }
    } finally {
      renewalsRunning = false;
    }
  }

  return {
    fetch: fetchHandler,
    runRenewals,
    close: () => {
      oidcStates.clear();
      browserSessions.clear();
      store.close();
    },
    provider: provider.name,
  };
}

function parsePlatformPath(pathname: string): string[] | null {
  const prefix = pathname.startsWith("/api/platform/") ? "/api/platform/" : pathname.startsWith("/api/") ? "/api/" : null;
  if (!prefix) return null;
  const parts = pathname.slice(prefix.length).split("/").filter(Boolean);
  if (!["organizations", "workspaces"].includes(parts[0] ?? "")) return null;
  try {
    return parts.map((part) => decodeURIComponent(part));
  } catch {
    return null;
  }
}

function oidcConfig(env: Record<string, string | undefined>, publicUrl: string): OidcConfig | null {
  const clientId = env.PAYMENTS_OIDC_CLIENT_ID?.trim() ?? "";
  const clientSecret = env.PAYMENTS_OIDC_CLIENT_SECRET?.trim() ?? "";
  if (!clientId && !clientSecret) return null;
  if (!clientId || !clientSecret) throw new Error("PAYMENTS_OIDC_CLIENT_ID and PAYMENTS_OIDC_CLIENT_SECRET must be configured together");
  const issuer = trimSlash(env.PAYMENTS_OIDC_ISSUER?.trim() || env.PAYMENTS_IDENTITY_ISSUER?.trim() || "https://auth.olibuijr.com");
  return {
    issuer,
    authorizationEndpoint: env.PAYMENTS_OIDC_AUTHORIZATION_ENDPOINT?.trim() || `${issuer}/authorize`,
    tokenEndpoint: env.PAYMENTS_OIDC_TOKEN_ENDPOINT?.trim() || `${issuer}/token`,
    clientId,
    clientSecret,
    redirectUri: env.PAYMENTS_OIDC_REDIRECT_URI?.trim() || `${publicUrl}/auth/callback`,
  };
}

function identityConfig(env: Record<string, string | undefined>): IdentityConfig {
  const value = (name: string): string | null => {
    const candidate = env[name]?.trim();
    return candidate ? candidate : null;
  };
  return {
    trustedProxySecret: value("PAYMENTS_TRUSTED_PROXY_SECRET"),
    credentialSecret: value("PAYMENTS_IDENTITY_SECRET"),
    idpPublicKey: value("PAYMENTS_IDP_PUBLIC_KEY")?.replaceAll("\\n", "\n") ?? null,
    issuer: value("PAYMENTS_IDENTITY_ISSUER"),
    audience: value("PAYMENTS_IDENTITY_AUDIENCE"),
  };
}

function authenticatedUser(request: Request, config: IdentityConfig): string | null {
  const userIdHeader = request.headers.get("x-akurai-user-id")?.trim() ?? "";
  const subjectHeader = request.headers.get("x-akurai-subject")?.trim() ?? "";
  const explicit = userIdHeader || subjectHeader;
  if (userIdHeader && subjectHeader && userIdHeader !== subjectHeader) return null;
  if (explicit) {
    if (!config.trustedProxySecret) return null;
    const timestamp = request.headers.get("x-akurai-identity-timestamp")?.trim() ?? "";
    const signature = request.headers.get("x-akurai-identity-signature")?.trim() ?? "";
    const issuedAt = Number(timestamp);
    if (!/^\d+$/.test(timestamp) || !Number.isSafeInteger(issuedAt) || Math.abs(Date.now() / 1000 - issuedAt) > 60) return null;
    const expected = createHmac("sha256", config.trustedProxySecret).update(`${timestamp}.${explicit}`).digest();
    const received = decodeBase64Url(signature);
    if (!received || received.length !== expected.length || !timingSafeEqual(received, expected)) return null;
    return boundedIdentity(explicit);
  }

  const authorization = request.headers.get("authorization") ?? "";
  const token = authorization.match(/^Bearer\s+(.+)$/i)?.[1]?.trim();
  if (!token) return null;
  return verifyIdentityToken(token, config);
}

function verifyIdentityToken(token: string, config: IdentityConfig): string | null {
  const parts = token.split(".");
  if (parts.length !== 3) return null;
  let header: Record<string, unknown>;
  let claims: Record<string, unknown>;
  try {
    header = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8")) as Record<string, unknown>;
    claims = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")) as Record<string, unknown>;
  } catch {
    return null;
  }
  const signingInput = `${parts[0]}.${parts[1]}`;
  const signature = decodeBase64Url(parts[2]);
  if (!signature) return null;
  let valid = false;
  if (header.alg === "HS256" && config.credentialSecret) {
    const expected = createHmac("sha256", config.credentialSecret).update(signingInput).digest();
    valid = signature.length === expected.length && timingSafeEqual(signature, expected);
  } else if (header.alg === "EdDSA" && config.idpPublicKey) {
    try {
      valid = verifySignature(null, Buffer.from(signingInput), config.idpPublicKey, signature);
    } catch {
      valid = false;
    }
  }
  if (!valid) return null;
  const now = Math.floor(Date.now() / 1000);
  if (typeof claims.exp !== "number" || claims.exp < now) return null;
  if (typeof claims.nbf === "number" && claims.nbf > now) return null;
  if (config.issuer && claims.iss !== config.issuer) return null;
  if (config.audience) {
    const aud = claims.aud;
    if (typeof aud !== "string" && !Array.isArray(aud)) return null;
    if (typeof aud === "string" ? aud !== config.audience : !aud.includes(config.audience)) return null;
  }
  return typeof claims.sub === "string" ? boundedIdentity(claims.sub) : null;
}

function boundedIdentity(value: string): string | null {
  const result = value.trim();
  return result && result.length <= 200 ? result : null;
}

function requestCookie(request: Request, name: string): string | null {
  for (const part of (request.headers.get("cookie") ?? "").split(";")) {
    const separator = part.indexOf("=");
    if (separator > 0 && part.slice(0, separator).trim() === name) return part.slice(separator + 1).trim() || null;
  }
  return null;
}


function decodeBase64Url(value: string): Buffer | null {
  if (!/^[A-Za-z0-9_-]+$/.test(value)) return null;
  try {
    return Buffer.from(value, "base64url");
  } catch {
    return null;
  }
}

function boundedText(value: unknown, max: number, label: string): string {
  if (typeof value !== "string" || !value.trim()) throw new Error(`${label} is required`);
  const result = value.trim();
  if (result.length > max) throw new Error(`${label} is too long`);
  return result;
}

function boundedOptionalText(value: unknown, max: number, label = "Value"): string | null {
  if (value == null || value === "") return null;
  return boundedText(value, max, label);
}

function isOneOf<T extends string>(value: unknown, values: readonly T[]): value is T {
  return typeof value === "string" && values.includes(value as T);
}

function publicOrganization(row: OrganizationRow): Record<string, unknown> {
  return { id: row.id, organizationId: row.id, name: row.name, crmCompanyId: row.crm_company_id, idpTenantId: row.idp_tenant_id, status: row.status, createdAt: row.created_at, updatedAt: row.updated_at };
}

function publicMembership(row: MembershipRow): Record<string, unknown> {
  return { id: row.id, membershipId: row.id, organizationId: row.organization_id, userId: row.user_id, role: row.role, status: row.status, createdAt: row.created_at, updatedAt: row.updated_at };
}

function publicWorkspace(row: WorkspaceRow): Record<string, unknown> {
  return { id: row.id, workspaceId: row.id, organizationId: row.organization_id, name: row.name, kind: row.kind, status: row.status, idpWorkspaceId: row.idp_workspace_id, createdAt: row.created_at, updatedAt: row.updated_at };
}

function publicEntitlement(row: EntitlementRow): Record<string, unknown> {
  return { id: row.id, entitlementId: row.id, organizationId: row.organization_id, workspaceId: row.workspace_id, appId: row.app_id, status: row.status, grantedAt: row.granted_at, revokedAt: row.revoked_at, createdAt: row.created_at, updatedAt: row.updated_at };
}

function publicInstallation(row: InstallationRow): Record<string, unknown> {
  return { id: row.id, installationId: row.id, organizationId: row.organization_id, workspaceId: row.workspace_id, appId: row.app_id, status: row.status, createdAt: row.created_at, updatedAt: row.updated_at };
}

function publicDomain(row: DomainRow): Record<string, unknown> {
  return { id: row.id, domainId: row.id, organizationId: row.organization_id, workspaceId: row.workspace_id, hostname: row.hostname, status: row.status, createdAt: row.created_at, updatedAt: row.updated_at };
}

function publicLifecycleRevision(row: LifecycleRevisionRow): Record<string, unknown> {
  return { id: row.id, revision: row.revision, organizationId: row.organization_id, workspaceId: row.workspace_id, entityType: row.entity_type, entityId: row.entity_id, fromState: row.from_state, toState: row.to_state, reason: row.reason, actorUserId: row.actor_user_id, metadata: JSON.parse(row.metadata_json), createdAt: row.created_at };
}

function loadCatalog(path: string): Catalog {
  return validateCatalog(JSON.parse(readFileSync(path, "utf8")));
}

function providerNameFromInitial(db: Database, orderId: string): string {
  const row = db.query("SELECT provider FROM payment_orders WHERE id = ?").get(orderId) as { provider: string } | null;
  if (!row) throw new Error("Initial order not found");
  return row.provider;
}

function productsFor(ids: string[], products: Map<string, Product>): Product[] {
  const result = ids.map((id) => products.get(id));
  if (result.some((product) => !product)) throw new Error("Subscription contains an unknown product");
  return result as Product[];
}

function publicOrder(order: OrderRow, products: Map<string, Product>, provisioning?: ProvisioningRequestRow | null): Record<string, unknown> {
  const ids = JSON.parse(order.products_json) as string[];
  const result: Record<string, unknown> = {
    id: order.id,
    status: order.status,
    interval: order.interval,
    amountCents: order.amount_cents,
    currency: order.currency,
    products: productsFor(ids, products).map(({ id, name }) => ({ id, name })),
    createdAt: order.created_at,
    paidAt: order.paid_at,
  };
  if (provisioning) {
    result.provisioning = { status: provisioning.status, errorCode: provisioning.error_code };
    result.ready = provisioning.status === "ready";
  }
  return result;
}

function publicSubscription(subscription: SubscriptionRow, products: Map<string, Product>): Record<string, unknown> {
  const ids = JSON.parse(subscription.products_json) as string[];
  return {
    id: subscription.id,
    status: subscription.status,
    interval: subscription.interval,
    amountCents: subscription.amount_cents,
    currency: subscription.currency,
    email: maskEmail(subscription.customer_email),
    products: productsFor(ids, products).map(({ id, name }) => ({ id, name })),
    nextChargeAt: subscription.next_charge_at,
    canceledAt: subscription.canceled_at,
    retentionDeadline: subscription.retention_deadline,
  };
}

function tokenKeyring(env: Record<string, string | undefined>, provider: "disabled" | "mock" | "mypos"): TokenKeyring {
  const current = tokenKey(env.PAYMENTS_TOKEN_KEY, "PAYMENTS_TOKEN_KEY");
  if (!current) {
    if (provider === "mypos") throw new Error("PAYMENTS_TOKEN_KEY is required in myPOS mode");
    return { currentId: "mock", keys: new Map([["mock", createHash("sha256").update("akurai-platform-mock-token-key").digest()]]) };
  }
  const currentId = env.PAYMENTS_TOKEN_KEY_ID?.trim() || "v1";
  const keys = new Map([[currentId, current]]);
  const previous = tokenKey(env.PAYMENTS_TOKEN_KEY_PREVIOUS, "PAYMENTS_TOKEN_KEY_PREVIOUS");
  if (previous) keys.set(env.PAYMENTS_TOKEN_KEY_PREVIOUS_ID?.trim() || "previous", previous);
  return { currentId, keys };
}

function tokenKey(value: string | undefined, name: string): Buffer | null {
  if (!value) return null;
  const key = Buffer.from(value, "base64");
  if (key.length !== 32) throw new Error(`${name} must decode to exactly 32 bytes`);
  return key;
}

function encrypt(value: string, keyring: TokenKeyring): string {
  const key = keyring.keys.get(keyring.currentId)!;
  const iv = randomBytes(12);
  const cipher = createCipheriv("aes-256-gcm", key, iv);
  const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
  return `${keyring.currentId}.${Buffer.concat([iv, cipher.getAuthTag(), encrypted]).toString("base64url")}`;
}

function decrypt(value: string, keyring: TokenKeyring): string {
  const separator = value.indexOf(".");
  const keyId = separator === -1 ? keyring.currentId : value.slice(0, separator);
  const key = keyring.keys.get(keyId);
  if (!key) throw new Error("Encrypted card token uses an unavailable key");
  const payload = Buffer.from(separator === -1 ? value : value.slice(separator + 1), "base64url");
  if (payload.length < 29) throw new Error("Invalid encrypted card token");
  const decipher = createDecipheriv("aes-256-gcm", key, payload.subarray(0, 12));
  decipher.setAuthTag(payload.subarray(12, 28));
  return Buffer.concat([decipher.update(payload.subarray(28)), decipher.final()]).toString("utf8");
}

function isCurrentTokenKey(value: string, keyring: TokenKeyring): boolean {
  return value.startsWith(`${keyring.currentId}.`);
}

function hashToken(token: string): string {
  return createHash("sha256").update(token).digest("hex");
}

function validManageToken(token: string): boolean {
  return /^[A-Za-z0-9_-]{43}$/.test(token);
}

function required(env: Record<string, string | undefined>, key: string): string {
  const value = env[key];
  if (!value) throw new Error(`${key} is required in myPOS mode`);
  return value;
}

function matchPath(pathname: string, prefix: string): string | null {
  if (!pathname.startsWith(prefix)) return null;
  const value = pathname.slice(prefix.length);
  if (!value || value.includes("/")) return null;
  try {
    return decodeURIComponent(value);
  } catch {
    return null;
  }
}

async function boundedJson(request: Request, maxBytes: number): Promise<unknown> {
  const reader = request.body?.getReader();
  if (!reader) throw new SyntaxError("Request body is required");
  const chunks: Uint8Array[] = [];
  let size = 0;
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    size += value.byteLength;
    if (size > maxBytes) throw new RangeError("Request body is too large");
    chunks.push(value);
  }
  const body = new Uint8Array(size);
  let offset = 0;
  for (const chunk of chunks) {
    body.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return JSON.parse(new TextDecoder().decode(body));
}

function json(value: unknown, status = 200): Response {
  return new Response(JSON.stringify(value), {
    status,
    headers: {
      "content-type": "application/json; charset=utf-8",
      "cache-control": "no-store",
    },
  });
}

function redirect(location: string): Response {
  return new Response(null, { status: 303, headers: { location, "cache-control": "no-store" } });
}


function paymentRedirectPage(action: string, fields: PaymentFormFields): Response {
  const nonce = randomBytes(18).toString("base64url");
  const inputs = Object.entries(fields).map(([name, value]) => `<input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`).join("");
  const origin = new URL(action).origin;
  return new Response(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Opening secure checkout</title></head><body><form id="checkout" method="post" action="${escapeHtml(action)}">${inputs}<button type="submit">Continue to secure checkout</button></form><script nonce="${nonce}">document.getElementById("checkout").submit()</script></body></html>`, {
    headers: htmlHeaders(`default-src 'none'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline'; form-action ${origin}; base-uri 'none'`),
  });
}

function mockCheckoutPage(order: OrderRow, products: Map<string, Product>): Response {
  const names = productsFor(JSON.parse(order.products_json) as string[], products).map((product) => product.name);
  const rows = names.map((name) => `<li>${escapeHtml(name)}</li>`).join("");
  const base = `/api/payments/mock/orders/${encodeURIComponent(order.id)}`;
  return themedPage("myPOS sandbox simulation", `
    <span class="eyebrow">Test payment</span>
    <h1>Complete your AkurAI subscription</h1>
    <p class="lede">This local gateway follows the production redirect and callback flow. No money or card data is used.</p>
    <div class="panel"><ul>${rows}</ul><div class="total"><span>${order.interval === "annual" ? "Annual" : "Monthly"} total</span><strong>${money(order.amount_cents, order.currency)}</strong></div></div>
    <div class="test-card"><span>Sandbox test card</span><strong>•••• 4242</strong></div>
    <div class="actions">
      <form method="post" action="${base}/approve"><button class="primary" type="submit">Approve test payment</button></form>
      <form method="post" action="${base}/decline"><button type="submit">Decline</button></form>
      <form method="post" action="${base}/cancel"><button type="submit">Cancel</button></form>
    </div>
  `);
}

function subscriptionPage(subscription: SubscriptionRow, token: string, products: Map<string, Product>): Response {
  const names = productsFor(JSON.parse(subscription.products_json) as string[], products).map((product) => product.name);
  const rows = names.map((name) => `<li>${escapeHtml(name)}</li>`).join("");
  const statusCopy: Record<SubscriptionStatus, { title: string; summary: string; canCancel: boolean }> = {
    active: { title: "Manage your plan", summary: `Next charge · ${money(subscription.amount_cents)} · ${subscription.next_charge_at.slice(0, 10)}`, canCancel: true },
    past_due: { title: "Payment retry pending", summary: "A renewal payment did not complete. We will retry automatically.", canCancel: true },
    unpaid: { title: "Payment required", summary: "Automatic renewal has stopped. Contact support to update payment details.", canCancel: false },
    requires_payment_method: { title: "Payment method required", summary: "Your subscription needs a reusable payment method before renewal.", canCancel: true },
    canceled: { title: "Subscription canceled", summary: subscription.retention_deadline ? `Data retention ends ${subscription.retention_deadline.slice(0, 10)}.` : "Canceled", canCancel: false },
  };
  const copy = statusCopy[subscription.status];
  return themedPage("Manage subscription", `
    <span class="eyebrow">Subscription</span>
    <h1>${copy.title}</h1>
    <p class="lede">${escapeHtml(maskEmail(subscription.customer_email))} · ${subscription.interval} billing</p>
    <div class="panel"><ul>${rows}</ul><div class="total"><span>Current status</span><strong>${escapeHtml(copy.summary)}</strong></div></div>
    <div class="actions">
      ${copy.canCancel ? `<form method="post" action="/api/payments/subscriptions/${encodeURIComponent(token)}?action=cancel"><button class="danger" type="submit">Cancel automatic renewal</button></form>` : ""}
      <a class="button" href="/">Back to AkurAI Platform</a>
    </div>
  `);
}

function checkoutResultPage(kind: "success" | "cancel"): Response {
  const nonce = randomBytes(18).toString("base64url");
  const initialTitle = kind === "cancel" ? "Checkout canceled" : "Confirming payment";
  const initialText = kind === "cancel" ? "No payment was taken. You can return and try again when ready." : "Waiting for the secure payment confirmation.";
  const script = kind === "success" ? `
    const params=new URLSearchParams(location.search);const order=params.get("order");
    const title=document.getElementById("result-title");const text=document.getElementById("result-text");const manage=document.getElementById("manage");
    if(order){let checks=0;const check=async()=>{try{const response=await fetch("/api/payments/orders/"+encodeURIComponent(order),{headers:{accept:"application/json"}});const data=await response.json();if(data.status==="paid"&&data.ready){title.textContent="Subscription active";text.textContent="Payment confirmed. Your selected AkurAI apps are now ready.";const token=sessionStorage.getItem("akurai_manage_"+order);if(token){manage.href="/subscriptions/manage/"+encodeURIComponent(token);manage.hidden=false}return}if(data.status==="paid"&&data.provisioning){const s=data.provisioning.status;if(s==="failed"){title.textContent="Payment confirmed";text.textContent="Payment was successful but setup encountered an issue. Our team has been notified.";return}title.textContent="Payment confirmed";text.textContent="Payment confirmed. Preparing your AkurAI workspace...";return}if(data.status==="paid"){title.textContent="Payment confirmed";text.textContent="Payment confirmed. Setting up your AkurAI apps...";return}if(["failed","canceled","rolled_back"].includes(data.status)){title.textContent="Payment not completed";text.textContent="The payment was not completed. No subscription is active.";return}}catch{}if(++checks<15)setTimeout(check,1000);else{text.textContent="Confirmation is taking longer than expected. Keep this order reference: "+order}};check()}
  ` : "";
  return new Response(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${initialTitle}</title><style>${THEME_CSS}</style></head><body><main class="card"><span class="eyebrow">AkurAI Platform</span><h1 id="result-title">${initialTitle}</h1><p id="result-text" class="lede">${initialText}</p><div class="actions"><a id="manage" class="button primary" href="#" hidden>Manage subscription</a><a class="button" href="/">Back to platform</a></div></main>${script ? `<script nonce="${nonce}">${script}</script>` : ""}</body></html>`, {
    headers: htmlHeaders(`default-src 'self'; script-src 'nonce-${nonce}'; style-src 'unsafe-inline'; connect-src 'self'; base-uri 'none'; frame-ancestors 'none'`),
  });
}

function htmlPage(title: string, text: string, status: number): Response {
  const response = themedPage(title, `<span class="eyebrow">AkurAI Platform</span><h1>${escapeHtml(title)}</h1><p class="lede">${escapeHtml(text)}</p><a class="button" href="/">Back to platform</a>`);
  return new Response(response.body, { status, headers: response.headers });
}

function themedPage(title: string, body: string): Response {
  return new Response(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)}</title><style>${THEME_CSS}</style></head><body><main class="card">${body}</main></body></html>`, {
    headers: htmlHeaders("default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'"),
  });
}

function htmlHeaders(csp: string): Headers {
  return new Headers({
    "content-type": "text/html; charset=utf-8",
    "cache-control": "no-store",
    "content-security-policy": csp,
    "referrer-policy": "no-referrer",
    "x-frame-options": "DENY",
  });
}

function escapeHtml(value: string): string {
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
}

function trimSlash(value: string): string {
  return value.replace(/\/+$/, "");
}

function errorMessage(error: unknown): string {
  return error instanceof Error ? error.message : "Invalid checkout details";
}

function safeError(error: unknown): string {
  return (error instanceof Error ? error.message : String(error)).slice(0, 240).replace(/[\r\n]/g, " ");
}

interface CrmSyncConfig {
  crmBaseUrl: string;
  crmApiKey: string;
  name: string;
  email: string;
  company: string | null;
  outcome: string;
  intent: string | null;
}

async function trySyncContactToCRM(
  submissionId: string,
  store: Store,
  config: CrmSyncConfig,
): Promise<void> {
  const nameParts = config.name.trim().split(/\s+/);
  const firstName = nameParts[0] ?? config.name;
  const lastName = nameParts.length > 1 ? nameParts.slice(1).join(" ") : "";
  const headers: Record<string, string> = {
    "content-type": "application/json",
    accept: "application/json",
    authorization: `Bearer ${config.crmApiKey}`,
  };

  try {
    // 1. Find existing person by email or create one
    const searchUrl = `${config.crmBaseUrl}/api/people?query=${encodeURIComponent(config.email)}`;
    const searchRes = await fetch(searchUrl, { headers, signal: AbortSignal.timeout(10_000) });
    let personId: string | null = null;
    let companyId: number | null = null;
    let opportunityId: number | null = null;

    if (searchRes.ok) {
      const searchData = await searchRes.json() as { data?: Array<{ id: string }> };
      const existing = searchData.data?.[0];
      if (existing) {
        personId = existing.id;
      }
    }

    if (!personId) {
      const createBody: Record<string, unknown> = {
        firstName,
        lastName,
        email: config.email,
      };
      if (config.company) createBody.jobTitle = config.intent ?? null;
      const createRes = await fetch(`${config.crmBaseUrl}/api/people`, {
        method: "POST",
        headers,
        body: JSON.stringify(createBody),
        signal: AbortSignal.timeout(10_000),
      });
      if (createRes.ok) {
        const created = await createRes.json() as { data?: { id: string } };
        personId = created.data?.id ?? null;
      }
    }

    // 2. Find or create company
    if (config.company) {
      const companySearchRes = await fetch(`${config.crmBaseUrl}/api/companies`, {
        headers,
        signal: AbortSignal.timeout(10_000),
      });
      if (companySearchRes.ok) {
        const companies = await companySearchRes.json() as { data?: Array<{ id: number; name: string }> };
        const match = companies.data?.find(
          (c) => c.name.toLowerCase() === config.company!.toLowerCase()
        );
        if (match) {
          companyId = match.id;
        } else {
          const createRes = await fetch(`${config.crmBaseUrl}/api/companies`, {
            method: "POST",
            headers,
            body: JSON.stringify({ name: config.company }),
            signal: AbortSignal.timeout(10_000),
          });
          if (createRes.ok) {
            const created = await createRes.json() as { data?: { id: number } };
            companyId = created.data?.id ?? null;
          }
        }
      }
    }

    // 3. Create opportunity
    const oppBody: Record<string, unknown> = {
      name: `${config.outcome === "demo" ? "Demo" : config.outcome === "waitlist" ? "Waitlist" : "Inquiry"}: ${config.name}`,
      stage: "new",
      personId,
    };
    if (companyId) oppBody.companyId = companyId;
    const oppRes = await fetch(`${config.crmBaseUrl}/api/opportunities`, {
      method: "POST",
      headers,
      body: JSON.stringify(oppBody),
      signal: AbortSignal.timeout(10_000),
    });
    if (oppRes.ok) {
      const created = await oppRes.json() as { data?: { id: number } };
      opportunityId = created.data?.id ?? null;
    }

    store.setContactCrmSync(submissionId, "synced", personId, companyId, opportunityId, null);
  } catch (error) {
    const msg = safeError(error);
    console.error(JSON.stringify({ event: "contact_crm_sync", submissionId, error: msg }));
    store.setContactCrmSync(submissionId, "failed", null, null, null, msg);
  }
}

const THEME_CSS = `
  :root{color-scheme:dark;--bg:#080b12;--panel:#111722;--line:#273244;--text:#f7f9fc;--muted:#9aa8bc;--accent:#6ee7c2;--danger:#ff8a8a;font:16px/1.5 Inter,ui-sans-serif,system-ui,sans-serif}
  *{box-sizing:border-box}body{min-height:100vh;margin:0;display:grid;place-items:center;padding:24px;background:radial-gradient(circle at 20% 0,#142536 0,transparent 38%),var(--bg);color:var(--text)}
  .card{width:min(620px,100%);padding:clamp(28px,6vw,56px);border:1px solid var(--line);border-radius:24px;background:color-mix(in srgb,var(--panel) 94%,transparent);box-shadow:0 24px 80px #0008}
  .eyebrow{color:var(--accent);font-size:.75rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}h1{margin:.6rem 0 1rem;font-size:clamp(2rem,6vw,3.5rem);line-height:1.02}.lede{color:var(--muted);font-size:1.08rem}
  .panel,.test-card{margin:24px 0;padding:20px;border:1px solid var(--line);border-radius:16px;background:#090d15}.panel ul{margin:0 0 18px;padding-left:20px}.total,.test-card{display:flex;justify-content:space-between;gap:20px}.total{padding-top:16px;border-top:1px solid var(--line)}.total span,.test-card span{color:var(--muted)}
  .actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:28px}.actions form{margin:0}.button,button{appearance:none;display:inline-flex;align-items:center;justify-content:center;min-height:46px;padding:0 18px;border:1px solid var(--line);border-radius:12px;background:#171e2b;color:var(--text);font:inherit;font-weight:750;text-decoration:none;cursor:pointer}.primary{border-color:var(--accent);background:var(--accent);color:#06120f}.danger{border-color:color-mix(in srgb,var(--danger) 55%,var(--line));color:var(--danger)}button:hover,.button:hover{filter:brightness(1.12)}[hidden]{display:none!important}
`;

if (import.meta.main) {
  const port = Number(Bun.env.PAYMENTS_PORT ?? 8198);
  if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error("PAYMENTS_PORT must be a valid port");
  const hostname = Bun.env.PAYMENTS_HOST ?? "127.0.0.1";
  const service = createPaymentService();
  const server = Bun.serve({ hostname, port, fetch: service.fetch });
  const renewalTimer = setInterval(() => void service.runRenewals(), 60 * 60 * 1000);
  renewalTimer.unref();
  const shutdown = () => {
    clearInterval(renewalTimer);
    service.close();
    void server.stop();
  };
  process.once("SIGINT", shutdown);
  process.once("SIGTERM", shutdown);
  console.log(JSON.stringify({ service: "akurai-platform-payments", provider: service.provider, hostname, port: server.port }));
}