import { beforeEach, describe, expect, it, vi } from 'vitest';
import type {
  StripeAccount,
  StripeCharge,
  StripeInvoice,
  StripePaymentIntent,
  StripePrice,
  StripeProduct,
  StripeSubscription,
} from '../../src/types/payments';

const { mockPool, mockProvider, mockGetSecretByKey, mockEncrypt, mockLogger } = vi.hoisted(() => ({
  mockPool: {
    query: vi.fn(),
    connect: vi.fn(),
  },
  mockProvider: {
    retrieveAccount: vi.fn(),
    syncCatalog: vi.fn(),
    createProduct: vi.fn(),
    updateProduct: vi.fn(),
    deleteProduct: vi.fn(),
    createPrice: vi.fn(),
    updatePrice: vi.fn(),
    createCustomer: vi.fn(),
    listCustomers: vi.fn(),
    createCustomerPortalSession: vi.fn(),
    createCheckoutSession: vi.fn(),
    constructWebhookEvent: vi.fn(),
    listWebhookEndpoints: vi.fn(),
    createWebhookEndpoint: vi.fn(),
    deleteWebhookEndpoint: vi.fn(),
    listSubscriptions: vi.fn(),
    listSubscriptionItems: vi.fn(),
    retrievePaymentIntent: vi.fn(),
    retrieveCharge: vi.fn(),
    retrieveInvoiceByPaymentIntent: vi.fn(),
  },
  mockGetSecretByKey: vi.fn(),
  mockEncrypt: vi.fn(),
  mockLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}));

vi.mock('../../src/infra/database/database.manager', () => ({
  DatabaseManager: {
    getInstance: () => ({
      getPool: () => mockPool,
    }),
  },
}));

vi.mock('../../src/providers/payments/stripe.provider', () => ({
  StripeProvider: vi.fn(function () {
    return mockProvider;
  }),
  maskStripeKey: (apiKey: string) => `masked:${apiKey.slice(-4)}`,
  validateStripeSecretKey: (environment: 'test' | 'live', value: string) => {
    const prefix = environment === 'test' ? 'sk_test_' : 'sk_live_';
    if (!value.startsWith(prefix)) {
      throw new Error(`STRIPE_${environment.toUpperCase()}_SECRET_KEY must start with ${prefix}`);
    }
  },
}));

vi.mock('../../src/services/secrets/secret.service', () => ({
  SecretService: {
    getInstance: () => ({
      getSecretByKey: mockGetSecretByKey,
    }),
  },
}));

vi.mock('../../src/infra/security/encryption.manager', () => ({
  EncryptionManager: {
    encrypt: mockEncrypt,
  },
}));

vi.mock('../../src/utils/logger', () => ({ default: mockLogger }));

function expectStripeScopedDelete(
  queryMock: typeof mockPool.query,
  tableName: string,
  environment: string
) {
  expect(queryMock).toHaveBeenCalledWith(
    expect.stringMatching(
      new RegExp(
        `DELETE FROM payments\\.${tableName}[\\s\\S]*provider\\s*=\\s*(?:'stripe'|\\$2)`,
        'i'
      )
    ),
    [environment, 'stripe']
  );
}

function expectStripeRuntimeDelete(
  queryMock: typeof mockPool.query,
  tableName: string,
  environment: string
) {
  expect(queryMock).toHaveBeenCalledWith(
    expect.stringMatching(
      new RegExp(`DELETE FROM payments\\.${tableName}[\\s\\S]*environment\\s*=\\s*\\$1`, 'i')
    ),
    [environment]
  );
}

function expectProviderRuntimeDelete(
  queryMock: typeof mockPool.query,
  tableName: string,
  environment: string,
  provider: string
) {
  expect(queryMock).toHaveBeenCalledWith(
    expect.stringMatching(
      new RegExp(`DELETE FROM payments\\.${tableName}[\\s\\S]*provider\\s*=\\s*\\$1`, 'i')
    ),
    [provider, environment]
  );
}

import { PaymentCustomerService } from '../../src/services/payments/payment-customer.service';
import { StripeCheckoutService } from '../../src/services/payments/stripe/checkout.service';
import { StripeConfigService } from '../../src/services/payments/stripe/config.service';
import { StripeCustomerPortalService } from '../../src/services/payments/stripe/customer-portal.service';
import { PaymentTransactionService } from '../../src/services/payments/transaction.service';
import { StripePriceService } from '../../src/services/payments/stripe/price.service';
import { StripeProductService } from '../../src/services/payments/stripe/product.service';
import { StripeSubscriptionService } from '../../src/services/payments/stripe/subscription.service';
import { StripeSyncService } from '../../src/services/payments/stripe/sync.service';
import { StripeWebhookService } from '../../src/services/payments/stripe/webhook.service';

const stripeServices = {
  getConfig: () => StripeConfigService.getInstance().getConfig(),
  setStripeSecretKey: (environment: 'test' | 'live', secretKey: string) =>
    StripeConfigService.getInstance().setStripeSecretKey(
      environment,
      secretKey,
      async (syncEnvironment, provider) => {
        await StripeSyncService.getInstance().syncPaymentsEnvironmentAfterKeyChange(
          syncEnvironment,
          provider
        );
      }
    ),
  removeStripeSecretKey: (environment: 'test' | 'live') =>
    StripeConfigService.getInstance().removeStripeSecretKey(environment),
  seedStripeKeysFromEnv: () => StripeSyncService.getInstance().seedStripeKeysFromEnv(),
  getStatus: () => StripeConfigService.getInstance().getStatus(),
  configureWebhook: async (environment: 'test' | 'live') => ({
    connection: await StripeConfigService.getInstance().configureManagedStripeWebhook(environment),
  }),
  syncPayments: (input: { environment: 'test' | 'live' | 'all' }) =>
    StripeSyncService.getInstance().syncPayments(input),
  listProducts: (input: { environment: 'test' | 'live' }) =>
    StripeProductService.getInstance().listProducts(input),
  getProduct: (environment: 'test' | 'live', productId: string) =>
    StripeProductService.getInstance().getProduct(environment, productId),
  createProduct: (input: Parameters<StripeProductService['createProduct']>[0]) =>
    StripeProductService.getInstance().createProduct(input),
  updateProduct: (productId: string, input: Parameters<StripeProductService['updateProduct']>[1]) =>
    StripeProductService.getInstance().updateProduct(productId, input),
  deleteProduct: (environment: 'test' | 'live', productId: string) =>
    StripeProductService.getInstance().deleteProduct(environment, productId),
  listPrices: (input: Parameters<StripePriceService['listPrices']>[0]) =>
    StripePriceService.getInstance().listPrices(input),
  getPrice: (environment: 'test' | 'live', priceId: string) =>
    StripePriceService.getInstance().getPrice(environment, priceId),
  createPrice: (input: Parameters<StripePriceService['createPrice']>[0]) =>
    StripePriceService.getInstance().createPrice(input),
  updatePrice: (priceId: string, input: Parameters<StripePriceService['updatePrice']>[1]) =>
    StripePriceService.getInstance().updatePrice(priceId, input),
  archivePrice: (environment: 'test' | 'live', priceId: string) =>
    StripePriceService.getInstance().archivePrice(environment, priceId),
  createCheckoutSession: (
    input: Parameters<StripeCheckoutService['createCheckoutSession']>[0],
    user: Parameters<StripeCheckoutService['createCheckoutSession']>[1]
  ) => StripeCheckoutService.getInstance().createCheckoutSession(input, user),
  createCustomerPortalSession: (
    input: Parameters<StripeCustomerPortalService['createCustomerPortalSession']>[0],
    user: Parameters<StripeCustomerPortalService['createCustomerPortalSession']>[1]
  ) => StripeCustomerPortalService.getInstance().createCustomerPortalSession(input, user),
  handleStripeWebhook: (environment: 'test' | 'live', rawBody: Buffer, signature: string) =>
    StripeWebhookService.getInstance().handleStripeWebhook(environment, rawBody, signature),
  listTransactions: (input: Parameters<PaymentTransactionService['listTransactions']>[0]) =>
    PaymentTransactionService.getInstance().listTransactions(input, 'stripe'),
  listCustomers: (input: Parameters<PaymentCustomerService['listCustomers']>[0]) =>
    PaymentCustomerService.getInstance().listCustomers(input),
  listSubscriptions: (input: Parameters<StripeSubscriptionService['listSubscriptions']>[0]) =>
    StripeSubscriptionService.getInstance().listSubscriptions(input),
};

describe('Stripe payment services', () => {
  const connectedTestConnectionRow = {
    environment: 'test',
    status: 'connected',
    accountId: 'acct_123',
    accountEmail: 'owner@example.com',
    accountLivemode: false,
    webhookEndpointId: 'we_123',
    webhookEndpointUrl: 'http://localhost:7130/api/webhooks/stripe/test',
    webhookConfiguredAt: new Date('2026-04-27T00:00:00.000Z'),
    lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
    lastSyncStatus: 'succeeded',
    lastSyncError: null,
    lastSyncCounts: { products: 1, prices: 1 },
  };
  const checkoutUser = {
    id: '00000000-0000-4000-8000-000000000001',
    email: 'buyer@example.com',
    role: 'authenticated' as const,
  };
  const anonCheckoutUser = {
    role: 'anon' as const,
  };
  const checkoutSessionRow = {
    id: '2a22bd54-2e90-4d33-94bd-e1fb59dfb111',
    environment: 'test',
    mode: 'subscription',
    status: 'open',
    paymentStatus: 'unpaid',
    subjectType: 'team',
    subjectId: 'team_123',
    customerEmail: 'buyer@example.com',
    checkoutSessionId: 'cs_test_123',
    customerId: 'cus_123',
    paymentIntentId: null,
    subscriptionId: null,
    url: 'https://checkout.stripe.com/c/pay/cs_test_123',
    lastError: null,
    createdAt: new Date('2026-04-28T00:00:00.000Z'),
    updatedAt: new Date('2026-04-28T00:00:00.000Z'),
  };
  const customerPortalSessionRow = {
    id: '5d0d37dc-6304-4be5-8424-0a3de87d01d8',
    environment: 'test',
    status: 'initialized',
    subjectType: 'team',
    subjectId: 'team_123',
    customerId: null,
    returnUrl: 'https://example.com/account',
    configuration: 'bpc_123',
    url: null,
    lastError: null,
    createdAt: new Date('2026-04-29T00:00:00.000Z'),
    updatedAt: new Date('2026-04-29T00:00:00.000Z'),
  };

  beforeEach(() => {
    vi.clearAllMocks();
    mockPool.query.mockReset();
    mockPool.connect.mockReset();
    mockGetSecretByKey.mockResolvedValue('sk_test_1234567890');
    mockEncrypt.mockReturnValue('encrypted-secret');
    mockPool.query.mockResolvedValue({ rowCount: 1, rows: [] });
    mockPool.connect.mockResolvedValue({
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    });
    mockProvider.retrieveAccount.mockResolvedValue({
      id: 'acct_123',
      object: 'account',
      email: 'owner@example.com',
      charges_enabled: true,
      details_submitted: true,
    } as unknown as StripeAccount);
    mockProvider.createProduct.mockResolvedValue({
      id: 'prod_new',
      object: 'product',
      name: 'New Product',
      active: true,
      description: null,
      default_price: null,
      metadata: {},
    } as unknown as StripeProduct);
    mockProvider.updateProduct.mockResolvedValue({
      id: 'prod_123',
      object: 'product',
      name: 'Updated Product',
      active: false,
      description: 'Updated description',
      default_price: null,
      metadata: { tier: 'updated' },
    } as unknown as StripeProduct);
    mockProvider.deleteProduct.mockResolvedValue({
      id: 'prod_123',
      deleted: true,
    });
    mockProvider.createPrice.mockResolvedValue({
      id: 'price_new',
      object: 'price',
      product: 'prod_123',
      active: true,
      currency: 'usd',
      unit_amount: 2000,
      unit_amount_decimal: null,
      type: 'recurring',
      lookup_key: 'pro_monthly',
      billing_scheme: 'per_unit',
      tax_behavior: 'exclusive',
      recurring: { interval: 'month', interval_count: 1 },
      metadata: {},
    } as unknown as StripePrice);
    mockProvider.updatePrice.mockResolvedValue({
      id: 'price_123',
      object: 'price',
      product: 'prod_123',
      active: false,
      currency: 'usd',
      unit_amount: 1000,
      unit_amount_decimal: null,
      type: 'recurring',
      lookup_key: 'pro_monthly',
      billing_scheme: 'per_unit',
      tax_behavior: 'exclusive',
      recurring: { interval: 'month', interval_count: 1 },
      metadata: { archived: 'true' },
    } as unknown as StripePrice);
    mockProvider.createCustomer.mockResolvedValue({
      id: 'cus_123',
      object: 'customer',
      email: 'buyer@example.com',
      metadata: { insforge_subject_type: 'team', insforge_subject_id: 'team_123' },
    });
    mockProvider.listCustomers.mockResolvedValue([]);
    mockProvider.createCustomerPortalSession.mockResolvedValue({
      id: 'bps_123',
      object: 'billing_portal.session',
      customer: 'cus_123',
      return_url: 'https://example.com/account',
      url: 'https://billing.stripe.com/p/session/test_123',
    });
    mockProvider.createCheckoutSession.mockResolvedValue({
      id: 'cs_test_123',
      object: 'checkout.session',
      mode: 'payment',
      url: 'https://checkout.stripe.com/c/pay/cs_test_123',
      status: 'open',
      payment_status: 'unpaid',
      customer: 'cus_123',
      payment_intent: null,
      subscription: null,
    });
    mockProvider.listWebhookEndpoints.mockResolvedValue([]);
    mockProvider.createWebhookEndpoint.mockResolvedValue({
      id: 'we_new',
      object: 'webhook_endpoint',
      url: 'http://localhost:7130/api/webhooks/stripe/test',
      secret: 'whsec_new',
    });
    mockProvider.listSubscriptions.mockResolvedValue([]);
    mockProvider.listSubscriptionItems.mockResolvedValue([]);
    mockProvider.retrievePaymentIntent.mockResolvedValue({
      id: 'pi_123',
      object: 'payment_intent',
      status: 'succeeded',
      customer: 'cus_123',
      latest_charge: 'ch_123',
      amount: 5000,
      amount_received: 5000,
      currency: 'usd',
      description: null,
      receipt_email: 'buyer@example.com',
      created: 1777334400,
      metadata: {},
    } as unknown as StripePaymentIntent);
    mockProvider.retrieveCharge.mockResolvedValue({
      id: 'ch_123',
      object: 'charge',
      customer: 'cus_123',
      payment_intent: 'pi_123',
      amount_refunded: 0,
      refunded: false,
      refunds: { data: [] },
      billing_details: { email: 'buyer@example.com' },
      description: null,
      metadata: {},
    } as unknown as StripeCharge);
    mockProvider.retrieveInvoiceByPaymentIntent.mockResolvedValue(null as StripeInvoice | null);
    mockProvider.syncCatalog.mockResolvedValue({
      account: {
        id: 'acct_123',
        object: 'account',
        email: 'owner@example.com',
        charges_enabled: true,
        details_submitted: true,
      } as unknown as StripeAccount,
      products: [
        {
          id: 'prod_123',
          object: 'product',
          name: 'Pro',
          active: true,
          metadata: {},
        },
      ] as unknown as StripeProduct[],
      prices: [
        {
          id: 'price_123',
          object: 'price',
          product: 'prod_123',
          active: true,
          currency: 'usd',
          type: 'recurring',
          lookup_key: 'pro_monthly_usd',
          recurring: { interval: 'month', interval_count: 1 },
          metadata: {},
        },
      ] as unknown as StripePrice[],
    });
  });

  it('reports Stripe key configuration from the secret store', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('sk_test_secret1234')
      .mockResolvedValueOnce('sk_live_secret5678');

    await expect(stripeServices.getConfig()).resolves.toEqual({
      keys: [
        {
          environment: 'test',
          value: 'sk_test_secret1234',
        },
        {
          environment: 'live',
          value: 'sk_live_secret5678',
        },
      ],
    });

    expect(mockGetSecretByKey).toHaveBeenCalledWith('STRIPE_TEST_SECRET_KEY');
    expect(mockGetSecretByKey).toHaveBeenCalledWith('STRIPE_LIVE_SECRET_KEY');
  });

  it('upserts encrypted Stripe keys into the canonical secret names and syncs payments immediately', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockGetSecretByKey.mockResolvedValue(null);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_123' }],
      })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] });

    await stripeServices.setStripeSecretKey('test', ' sk_test_newsecret1234 ');

    expect(mockProvider.retrieveAccount).toHaveBeenCalledTimes(1);
    expect(mockEncrypt).toHaveBeenCalledWith('sk_test_newsecret1234');
    expect(mockProvider.listWebhookEndpoints).toHaveBeenCalledTimes(1);
    expect(mockProvider.createWebhookEndpoint).toHaveBeenCalledWith({
      url: 'http://localhost:7130/api/webhooks/stripe/test',
      enabledEvents: [
        'customer.created',
        'customer.updated',
        'customer.deleted',
        'checkout.session.completed',
        'checkout.session.async_payment_succeeded',
        'checkout.session.async_payment_failed',
        'checkout.session.expired',
        'invoice.paid',
        'invoice.payment_failed',
        'payment_intent.succeeded',
        'payment_intent.payment_failed',
        'charge.refunded',
        'refund.created',
        'refund.updated',
        'refund.failed',
        'customer.subscription.created',
        'customer.subscription.updated',
        'customer.subscription.deleted',
        'customer.subscription.paused',
        'customer.subscription.resumed',
      ],
      metadata: {
        managed_by: 'insforge',
        insforge_webhook: 'stripe_payments',
        insforge_environment: 'test',
        insforge_endpoint_path: '/api/webhooks/stripe/test',
        insforge_endpoint_url: 'http://localhost:7130/api/webhooks/stripe/test',
      },
    });
    expect(mockEncrypt).toHaveBeenCalledWith('whsec_new');
    expect(mockClient.query).toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_TEST_SECRET_KEY',
      'encrypted-secret',
    ]);
    expect(mockClient.query).toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_TEST_WEBHOOK_SECRET',
      'encrypted-secret',
    ]);
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.stripe_subscription_items WHERE environment = $1',
      ['test']
    );
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.stripe_subscriptions WHERE environment = $1',
      ['test']
    );
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.stripe_products WHERE environment = $1',
      ['test']
    );
    expect(mockProvider.syncCatalog).toHaveBeenCalledTimes(1);
    expect(mockProvider.listCustomers).toHaveBeenCalledTimes(1);
    expect(mockProvider.listSubscriptions).toHaveBeenCalledTimes(1);
  });

  it('saves Stripe keys even when managed webhook setup fails', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockGetSecretByKey.mockResolvedValue(null);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_123' }],
      })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] });
    mockProvider.createWebhookEndpoint.mockRejectedValueOnce(
      new Error(
        'Invalid URL: URL must be publicly accessible. Consider using a tool like the Stripe CLI to test webhooks locally.'
      )
    );

    await expect(
      stripeServices.setStripeSecretKey('test', 'sk_test_newsecret1234')
    ).resolves.toBeUndefined();

    expect(mockProvider.retrieveAccount).toHaveBeenCalledTimes(1);
    expect(mockProvider.createWebhookEndpoint).toHaveBeenCalledTimes(1);
    expect(mockClient.query).toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_TEST_SECRET_KEY',
      'encrypted-secret',
    ]);
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/UPDATE system\.secrets/i),
      ['STRIPE_TEST_WEBHOOK_SECRET']
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.provider_connections/i),
      [
        'test',
        'acct_123',
        'owner@example.com',
        false,
        null,
        null,
        expect.objectContaining({ id: 'acct_123' }),
        false,
      ]
    );
    expect(mockEncrypt).not.toHaveBeenCalledWith('whsec_new');
    expect(mockProvider.syncCatalog).toHaveBeenCalledTimes(1);
    expect(mockProvider.listCustomers).toHaveBeenCalledTimes(1);
    expect(mockProvider.listSubscriptions).toHaveBeenCalledTimes(1);
  });

  it('recreates existing InsForge-managed Stripe webhooks even when the stored endpoint URL is stale', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockGetSecretByKey.mockResolvedValue(null);
    mockPool.query.mockResolvedValueOnce({
      rows: [{ accountId: 'acct_123' }],
    });
    mockProvider.listWebhookEndpoints.mockResolvedValueOnce([
      {
        id: 'we_old',
        object: 'webhook_endpoint',
        url: 'https://old.example.com/api/webhooks/stripe/test',
        metadata: {
          managed_by: 'insforge',
          insforge_webhook: 'stripe_payments',
          insforge_environment: 'test',
        },
      },
      {
        id: 'we_live',
        object: 'webhook_endpoint',
        url: 'https://old.example.com/api/webhooks/stripe/live',
        metadata: {
          managed_by: 'insforge',
          insforge_webhook: 'stripe_payments',
          insforge_environment: 'live',
        },
      },
      {
        id: 'we_developer',
        object: 'webhook_endpoint',
        metadata: { managed_by: 'developer' },
      },
    ]);

    await stripeServices.setStripeSecretKey('test', 'sk_test_newsecret1234');

    expect(mockProvider.deleteWebhookEndpoint).toHaveBeenCalledWith('we_old');
    expect(mockProvider.deleteWebhookEndpoint).not.toHaveBeenCalledWith('we_live');
    expect(mockProvider.deleteWebhookEndpoint).not.toHaveBeenCalledWith('we_developer');
    expect(mockProvider.createWebhookEndpoint).toHaveBeenCalledTimes(1);
  });

  it('skips webhook recreation and sync when saving the same Stripe key', async () => {
    mockPool.query.mockResolvedValueOnce({
      rows: [{ accountId: 'acct_123' }],
    });

    await stripeServices.setStripeSecretKey('test', ' sk_test_1234567890 ');

    expect(mockProvider.retrieveAccount).not.toHaveBeenCalled();
    expect(mockEncrypt).not.toHaveBeenCalled();
    expect(mockProvider.listWebhookEndpoints).not.toHaveBeenCalled();
    expect(mockProvider.createWebhookEndpoint).not.toHaveBeenCalled();
    expect(mockProvider.syncCatalog).not.toHaveBeenCalled();
    expect(mockProvider.listCustomers).not.toHaveBeenCalled();
    expect(mockProvider.listSubscriptions).not.toHaveBeenCalled();
  });

  it('configures managed Stripe webhooks on demand using the stored key', async () => {
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/RETURNING\s+environment/i.test(sql)) {
          return Promise.resolve({
            rows: [
              {
                ...connectedTestConnectionRow,
                webhookEndpointId: 'we_new',
                webhookEndpointUrl: 'http://localhost:7130/api/webhooks/stripe/test',
                webhookConfiguredAt: new Date('2026-04-28T00:00:00.000Z'),
              },
            ],
          });
        }

        return Promise.resolve({ rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockResolvedValueOnce({
      rows: [{ accountId: 'acct_123' }],
    });

    const result = await stripeServices.configureWebhook('test');

    expect(mockProvider.retrieveAccount).toHaveBeenCalledTimes(1);
    expect(mockProvider.listWebhookEndpoints).toHaveBeenCalledTimes(1);
    expect(mockProvider.createWebhookEndpoint).toHaveBeenCalledTimes(1);
    expect(mockProvider.syncCatalog).not.toHaveBeenCalled();
    expect(mockProvider.listCustomers).not.toHaveBeenCalled();
    expect(mockProvider.listSubscriptions).not.toHaveBeenCalled();
    expect(mockEncrypt).toHaveBeenCalledWith('whsec_new');
    expect(mockClient.query).toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_TEST_WEBHOOK_SECRET',
      'encrypted-secret',
    ]);
    expect(result.connection.webhookEndpointId).toBe('we_new');
    expect(result.connection.webhookConfiguredAt).toBe('2026-04-28T00:00:00.000Z');
  });

  it('clears the payment mirror when on-demand webhook configuration finds another Stripe account', async () => {
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/RETURNING\s+environment/i.test(sql)) {
          return Promise.resolve({
            rows: [
              {
                ...connectedTestConnectionRow,
                accountId: 'acct_new',
                accountEmail: 'new-owner@example.com',
                webhookEndpointId: 'we_new',
                webhookEndpointUrl: 'http://localhost:7130/api/webhooks/stripe/test',
                webhookConfiguredAt: new Date('2026-04-28T00:00:00.000Z'),
                lastSyncedAt: null,
                lastSyncStatus: null,
                lastSyncError: null,
                lastSyncCounts: {},
              },
            ],
          });
        }

        return Promise.resolve({ rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockResolvedValueOnce({
      rows: [{ accountId: 'acct_old' }],
    });
    mockProvider.retrieveAccount.mockResolvedValueOnce({
      id: 'acct_new',
      object: 'account',
      email: 'new-owner@example.com',
    } as unknown as StripeAccount);

    const result = await stripeServices.configureWebhook('test');

    expectStripeRuntimeDelete(mockClient.query, 'stripe_subscription_items', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_subscriptions', 'test');
    expectProviderRuntimeDelete(mockClient.query, 'transactions', 'test', 'stripe');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_checkout_sessions', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_customer_portal_sessions', 'test');
    expectStripeScopedDelete(mockClient.query, 'customers', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_prices', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_products', 'test');
    expect(result.connection.accountId).toBe('acct_new');
  });

  it('requires a configured Stripe key before configuring managed webhooks', async () => {
    mockGetSecretByKey.mockResolvedValueOnce(null);

    await expect(stripeServices.configureWebhook('test')).rejects.toThrow(
      'STRIPE_TEST_SECRET_KEY is not configured'
    );

    expect(mockProvider.retrieveAccount).not.toHaveBeenCalled();
    expect(mockProvider.listWebhookEndpoints).not.toHaveBeenCalled();
    expect(mockProvider.createWebhookEndpoint).not.toHaveBeenCalled();
  });

  it('treats a missing stored Stripe account id as unknown and re-syncs', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: null }],
      })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] });

    await stripeServices.setStripeSecretKey('test', 'sk_test_1234567890');

    expect(mockProvider.retrieveAccount).toHaveBeenCalledTimes(1);
    expect(mockProvider.createWebhookEndpoint).toHaveBeenCalledTimes(1);
    expectStripeRuntimeDelete(mockClient.query, 'stripe_subscription_items', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_subscriptions', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_prices', 'test');
    expectStripeScopedDelete(mockClient.query, 'customers', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_products', 'test');
    expect(mockProvider.syncCatalog).toHaveBeenCalledTimes(1);
    expect(mockProvider.listCustomers).toHaveBeenCalledTimes(1);
    expect(mockProvider.listSubscriptions).toHaveBeenCalledTimes(1);
  });

  it('persists rotated keys without recreating webhooks or syncing when the Stripe account is unchanged', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockGetSecretByKey.mockResolvedValue('sk_test_oldsecret1234');
    mockPool.query.mockResolvedValueOnce({
      rows: [{ accountId: 'acct_123' }],
    });

    await stripeServices.setStripeSecretKey('test', 'sk_test_rotated1234');

    expect(mockProvider.retrieveAccount).toHaveBeenCalledTimes(1);
    expect(mockEncrypt).toHaveBeenCalledWith('sk_test_rotated1234');
    expect(mockProvider.listWebhookEndpoints).not.toHaveBeenCalled();
    expect(mockProvider.createWebhookEndpoint).not.toHaveBeenCalled();
    expect(mockProvider.syncCatalog).not.toHaveBeenCalled();
    expect(mockProvider.listCustomers).not.toHaveBeenCalled();
    expect(mockProvider.listSubscriptions).not.toHaveBeenCalled();
    expect(mockClient.query).toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_TEST_SECRET_KEY',
      'encrypted-secret',
    ]);
    expect(mockClient.query).not.toHaveBeenCalledWith(expect.any(String), [
      'STRIPE_TEST_WEBHOOK_SECRET',
    ]);
    expect(mockClient.query).not.toHaveBeenCalledWith(expect.any(String), [
      'STRIPE_TEST_WEBHOOK_SECRET',
      'encrypted-secret',
    ]);
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.provider_connections/i),
      ['test', 'acct_123', 'owner@example.com', false, expect.objectContaining({ id: 'acct_123' })]
    );
  });

  it('clears the environment catalog mirror when a new key points to another Stripe account', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockProvider.retrieveAccount.mockResolvedValueOnce({
      id: 'acct_new',
      object: 'account',
      email: 'new-owner@example.com',
    } as unknown as StripeAccount);
    mockProvider.syncCatalog.mockRejectedValueOnce(new Error('sync failed'));
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_old' }],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            status: 'error',
            accountId: 'acct_new',
            accountEmail: 'new-owner@example.com',
            accountLivemode: false,
            lastSyncedAt: null,
            lastSyncStatus: 'failed',
            lastSyncError: 'sync failed',
            lastSyncCounts: {},
          },
        ],
      });

    await expect(
      stripeServices.setStripeSecretKey('test', 'sk_test_newsecret1234')
    ).resolves.toBeUndefined();

    expectStripeRuntimeDelete(mockClient.query, 'stripe_subscription_items', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_subscriptions', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_prices', 'test');
    expectStripeRuntimeDelete(mockClient.query, 'stripe_products', 'test');
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.provider_connections/i),
      ['test', 'error', 'sync failed']
    );
  });

  it('soft-removes Stripe keys without clearing the mirrored payment data', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rowCount: 1, rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);

    await expect(stripeServices.removeStripeSecretKey('live')).resolves.toBe(true);

    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/UPDATE system\.secrets/i),
      ['STRIPE_LIVE_SECRET_KEY']
    );
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.stripe_subscription_items WHERE environment = $1',
      ['live']
    );
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.stripe_subscriptions WHERE environment = $1',
      ['live']
    );
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.stripe_prices WHERE environment = $1',
      ['live']
    );
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.customers WHERE environment = $1',
      ['live']
    );
    expect(mockClient.query).not.toHaveBeenCalledWith(
      'DELETE FROM payments.stripe_products WHERE environment = $1',
      ['live']
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/UPDATE payments\.provider_connections/i),
      ['live', 'STRIPE_LIVE_SECRET_KEY is not configured']
    );
  });

  it('seeds Stripe keys from environment variables', async () => {
    const originalEnv = { ...process.env };
    process.env.STRIPE_TEST_SECRET_KEY = 'sk_test_seed1234';
    process.env.STRIPE_LIVE_SECRET_KEY = 'sk_live_seed5678';
    mockGetSecretByKey.mockResolvedValue(null);

    try {
      await stripeServices.seedStripeKeysFromEnv();
    } finally {
      process.env = originalEnv;
    }

    expect(mockEncrypt).toHaveBeenCalledWith('sk_test_seed1234');
    expect(mockEncrypt).toHaveBeenCalledWith('sk_live_seed5678');
  });

  it('does not overwrite active Stripe keys when seeding from environment variables', async () => {
    const originalEnv = { ...process.env };
    process.env.STRIPE_TEST_SECRET_KEY = 'sk_test_seed1234';
    mockGetSecretByKey.mockResolvedValue('sk_test_existing1234');

    try {
      await stripeServices.seedStripeKeysFromEnv();
    } finally {
      process.env = originalEnv;
    }

    expect(mockEncrypt).not.toHaveBeenCalledWith('sk_test_seed1234');
  });

  it('does not seed Stripe webhook secrets from environment variables', async () => {
    const originalEnv = { ...process.env };
    delete process.env.STRIPE_TEST_SECRET_KEY;
    delete process.env.STRIPE_LIVE_SECRET_KEY;
    process.env.STRIPE_TEST_WEBHOOK_SECRET = 'whsec_test_seed1234';
    process.env.STRIPE_LIVE_WEBHOOK_SECRET = 'whsec_live_seed5678';
    mockGetSecretByKey.mockResolvedValue(null);

    try {
      await stripeServices.seedStripeKeysFromEnv();
    } finally {
      process.env = originalEnv;
    }

    expect(mockEncrypt).not.toHaveBeenCalledWith('whsec_test_seed1234');
    expect(mockEncrypt).not.toHaveBeenCalledWith('whsec_live_seed5678');
    expect(mockPool.query).not.toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_TEST_WEBHOOK_SECRET',
      'encrypted-secret',
    ]);
    expect(mockPool.query).not.toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_LIVE_WEBHOOK_SECRET',
      'encrypted-secret',
    ]);
  });

  it('records an unconfigured status when an environment key is missing', async () => {
    mockGetSecretByKey.mockResolvedValue(null);
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          environment: 'live',
          status: 'unconfigured',
          accountId: null,
          accountEmail: null,
          accountLivemode: null,
          lastSyncedAt: null,
          lastSyncStatus: 'failed',
          lastSyncError: 'STRIPE_LIVE_SECRET_KEY is not configured',
          lastSyncCounts: {},
        },
      ],
    });

    const result = await stripeServices.syncPayments({ environment: 'live' });

    expect(result.results[0]?.connection.status).toBe('unconfigured');
    expect(result.results[0]?.connection.lastSyncStatus).toBe('failed');
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/payments\.provider_connections/i),
      expect.any(Array)
    );
  });

  it('fetches Stripe products and prices and commits a successful sync', async () => {
    const mockLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockSyncClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockSubscriptionsClient = {
      query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
      release: vi.fn(),
    };
    mockPool.connect
      .mockResolvedValueOnce(mockLockClient)
      .mockResolvedValueOnce(mockSyncClient)
      .mockResolvedValueOnce(mockSubscriptionsClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_123' }],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            status: 'connected',
            accountId: 'acct_123',
            accountEmail: 'owner@example.com',
            accountLivemode: false,
            lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
            lastSyncStatus: 'succeeded',
            lastSyncError: null,
            lastSyncCounts: { products: 1, prices: 1 },
          },
        ],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            status: 'connected',
            accountId: 'acct_123',
            accountEmail: 'owner@example.com',
            accountLivemode: false,
            lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
            lastSyncStatus: 'succeeded',
            lastSyncError: null,
            lastSyncCounts: { products: 1, prices: 1 },
          },
        ],
      });

    const result = await stripeServices.syncPayments({ environment: 'test' });

    expect(result.results[0]?.connection.status).toBe('connected');
    expect(mockProvider.syncCatalog).toHaveBeenCalledTimes(1);
    expect(mockProvider.listCustomers).toHaveBeenCalledTimes(1);
    expect(mockLockClient.query).toHaveBeenCalledWith('SELECT pg_advisory_lock(hashtext($1))', [
      'payments_environment_test',
    ]);
    expect(mockLockClient.query).toHaveBeenCalledWith('SELECT pg_advisory_unlock(hashtext($1))', [
      'payments_environment_test',
    ]);
    expect(mockSyncClient.query).toHaveBeenCalledWith('BEGIN');
    expect(mockSyncClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/DELETE FROM payments\.stripe_prices/i),
      ['test', ['price_123']]
    );
    expect(mockSyncClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/DELETE FROM payments\.stripe_products/i),
      ['test', ['prod_123']]
    );
    expect(mockSyncClient.query).toHaveBeenCalledWith('COMMIT');
  });

  it('syncs payments by refreshing catalog before subscriptions', async () => {
    const mockLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockCatalogClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockCustomersClient = {
      query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
      release: vi.fn(),
    };
    const mockSubscriptionsClient = {
      query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
      release: vi.fn(),
    };
    const connectedRow = {
      environment: 'test',
      status: 'connected',
      accountId: 'acct_123',
      accountEmail: 'owner@example.com',
      accountLivemode: false,
      webhookEndpointId: 'we_123',
      webhookEndpointUrl: 'https://example.com/api/webhooks/stripe/test',
      webhookConfiguredAt: new Date('2026-04-27T00:00:00.000Z'),
      lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
      lastSyncStatus: 'succeeded',
      lastSyncError: null,
      lastSyncCounts: { products: 1, prices: 1 },
    };

    mockPool.connect
      .mockResolvedValueOnce(mockLockClient)
      .mockResolvedValueOnce(mockCatalogClient)
      .mockResolvedValueOnce(mockCustomersClient)
      .mockResolvedValueOnce(mockSubscriptionsClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_123' }],
      })
      .mockResolvedValueOnce({ rows: [connectedRow] })
      .mockResolvedValueOnce({ rows: [connectedRow] });

    const result = await stripeServices.syncPayments({ environment: 'test' });

    expect(result.results).toHaveLength(1);
    expect(result.results[0]).toMatchObject({
      environment: 'test',
      connection: { status: 'connected', accountId: 'acct_123' },
      subscriptions: { environment: 'test', synced: 0, unmapped: 0, deleted: 0 },
    });
    expect(mockProvider.syncCatalog).toHaveBeenCalledTimes(1);
    expect(mockProvider.listCustomers).toHaveBeenCalledTimes(1);
    expect(mockProvider.listSubscriptions).toHaveBeenCalledTimes(1);
    expect(mockProvider.syncCatalog.mock.invocationCallOrder[0]).toBeLessThan(
      mockProvider.listCustomers.mock.invocationCallOrder[0]
    );
    expect(mockProvider.listCustomers.mock.invocationCallOrder[0]).toBeLessThan(
      mockProvider.listSubscriptions.mock.invocationCallOrder[0]
    );
    expect(mockProvider.createWebhookEndpoint).not.toHaveBeenCalled();
    expect(mockCustomersClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/UPDATE payments\.customers/i),
      ['test', expect.any(Date), []]
    );
    expect(mockSubscriptionsClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/DELETE FROM payments\.stripe_subscriptions/i),
      ['test', []]
    );
  });

  it('continues syncing subscriptions when customer mirroring fails', async () => {
    const mockLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockCatalogClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockSubscriptionsClient = {
      query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
      release: vi.fn(),
    };
    const connectedRow = {
      environment: 'test',
      status: 'connected',
      accountId: 'acct_123',
      accountEmail: 'owner@example.com',
      accountLivemode: false,
      webhookEndpointId: 'we_123',
      webhookEndpointUrl: 'https://example.com/api/webhooks/stripe/test',
      webhookConfiguredAt: new Date('2026-04-27T00:00:00.000Z'),
      lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
      lastSyncStatus: 'succeeded',
      lastSyncError: null,
      lastSyncCounts: { products: 1, prices: 1 },
    };

    mockPool.connect
      .mockResolvedValueOnce(mockLockClient)
      .mockResolvedValueOnce(mockCatalogClient)
      .mockResolvedValueOnce(mockSubscriptionsClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_123' }],
      })
      .mockResolvedValueOnce({ rows: [connectedRow] })
      .mockResolvedValueOnce({ rows: [connectedRow] });
    mockProvider.listCustomers.mockRejectedValueOnce(new Error('customer sync failed'));

    await expect(stripeServices.syncPayments({ environment: 'test' })).resolves.toMatchObject({
      results: [
        {
          environment: 'test',
          connection: { status: 'connected', accountId: 'acct_123' },
          subscriptions: { environment: 'test', synced: 0, unmapped: 0, deleted: 0 },
        },
      ],
    });

    expect(mockProvider.listSubscriptions).toHaveBeenCalledTimes(1);
    expect(mockLogger.warn).toHaveBeenCalledWith(
      'Stripe customer mirror sync failed during payments sync',
      {
        environment: 'test',
        error: 'customer sync failed',
      }
    );
  });

  it('clears account-scoped payment mirrors when catalog sync resolves to a different account', async () => {
    const mockLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockSyncClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockSubscriptionsClient = {
      query: vi.fn().mockResolvedValue({ rows: [], rowCount: 0 }),
      release: vi.fn(),
    };
    mockPool.connect
      .mockResolvedValueOnce(mockLockClient)
      .mockResolvedValueOnce(mockSyncClient)
      .mockResolvedValueOnce(mockSubscriptionsClient);
    mockProvider.syncCatalog.mockResolvedValueOnce({
      account: {
        id: 'acct_new',
        object: 'account',
        email: 'new-owner@example.com',
        charges_enabled: true,
        details_submitted: true,
      } as unknown as StripeAccount,
      products: [],
      prices: [],
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_old' }],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            status: 'connected',
            accountId: 'acct_new',
            accountEmail: 'new-owner@example.com',
            accountLivemode: false,
            lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
            lastSyncStatus: 'succeeded',
            lastSyncError: null,
            lastSyncCounts: { products: 0, prices: 0 },
          },
        ],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            status: 'connected',
            accountId: 'acct_new',
            accountEmail: 'new-owner@example.com',
            accountLivemode: false,
            lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
            lastSyncStatus: 'succeeded',
            lastSyncError: null,
            lastSyncCounts: { products: 0, prices: 0 },
          },
        ],
      });

    await expect(stripeServices.syncPayments({ environment: 'test' })).resolves.toMatchObject({
      results: [
        {
          connection: {
            accountId: 'acct_new',
            status: 'connected',
          },
        },
      ],
    });

    expectProviderRuntimeDelete(mockSyncClient.query, 'transactions', 'test', 'stripe');
    expectStripeRuntimeDelete(mockSyncClient.query, 'stripe_checkout_sessions', 'test');
    expectStripeRuntimeDelete(mockSyncClient.query, 'stripe_customer_portal_sessions', 'test');
    expectStripeScopedDelete(mockSyncClient.query, 'customers', 'test');
    expectStripeScopedDelete(mockSyncClient.query, 'customer_mappings', 'test');
    expectStripeScopedDelete(mockSyncClient.query, 'webhook_events', 'test');
    expectStripeRuntimeDelete(mockSyncClient.query, 'stripe_prices', 'test');
    expectStripeRuntimeDelete(mockSyncClient.query, 'stripe_products', 'test');
    expect(mockProvider.createWebhookEndpoint).toHaveBeenCalledTimes(1);
    expect(mockEncrypt).toHaveBeenCalledWith('whsec_new');
    expect(mockSyncClient.query).toHaveBeenCalledWith(expect.stringMatching(/system\.secrets/i), [
      'STRIPE_TEST_WEBHOOK_SECRET',
      'encrypted-secret',
    ]);
    expect(mockSyncClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.provider_connections/i),
      [
        'test',
        'acct_new',
        'new-owner@example.com',
        false,
        { products: 0, prices: 0 },
        expect.objectContaining({ id: 'acct_new' }),
        'we_new',
        'http://localhost:7130/api/webhooks/stripe/test',
        true,
      ]
    );
    expect(mockSyncClient.query).toHaveBeenCalledWith('COMMIT');
  });

  it('lists products from the requested local Stripe mirror environment', async () => {
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            productId: 'prod_123',
            name: 'Pro',
            description: null,
            active: true,
            defaultPriceId: 'price_123',
            metadata: {},
            syncedAt: new Date('2026-04-27T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] });

    await expect(stripeServices.listProducts({ environment: 'test' })).resolves.toEqual({
      products: [
        {
          environment: 'test',
          productId: 'prod_123',
          name: 'Pro',
          description: null,
          active: true,
          defaultPriceId: 'price_123',
          metadata: {},
          syncedAt: '2026-04-27T00:00:00.000Z',
        },
      ],
    });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/FROM payments\.stripe_products/i),
      ['test']
    );
  });

  it('creates products with the requested Stripe key and updates only the returned product mirror', async () => {
    mockGetSecretByKey.mockResolvedValue('sk_live_1234567890');
    const mockLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockMirrorClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValueOnce(mockLockClient).mockResolvedValueOnce(mockMirrorClient);
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          environment: 'live',
          status: 'connected',
          accountId: 'acct_123',
          accountEmail: 'owner@example.com',
          accountLivemode: true,
          lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
          lastSyncStatus: 'succeeded',
          lastSyncError: null,
          lastSyncCounts: { products: 1, prices: 1 },
        },
      ],
    });

    const result = await stripeServices.createProduct({
      environment: 'live',
      name: 'New Product',
      active: true,
      metadata: { tier: 'new' },
      idempotencyKey: 'agent-product-123',
    });

    expect(mockProvider.createProduct).toHaveBeenCalledWith({
      name: 'New Product',
      active: true,
      metadata: { tier: 'new' },
      idempotencyKey: 'insforge:live:product:agent-product-123',
    });
    expect(mockGetSecretByKey).toHaveBeenCalledWith('STRIPE_LIVE_SECRET_KEY');
    expect(mockMirrorClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_products/i),
      expect.arrayContaining(['live', 'prod_new', 'New Product'])
    );
    expect(mockProvider.syncCatalog).not.toHaveBeenCalled();
    expect(result.product).toMatchObject({
      environment: 'live',
      productId: 'prod_new',
      name: 'New Product',
      active: true,
    });
  });

  it('updates and deletes products through the requested Stripe provider', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockResolvedValue({
      rows: [
        {
          environment: 'test',
          status: 'connected',
          accountId: 'acct_123',
          accountEmail: 'owner@example.com',
          accountLivemode: false,
          lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
          lastSyncStatus: 'succeeded',
          lastSyncError: null,
          lastSyncCounts: { products: 1, prices: 1 },
        },
      ],
    });

    await expect(
      stripeServices.updateProduct('prod_123', {
        environment: 'test',
        name: 'Updated Product',
        active: false,
      })
    ).resolves.toMatchObject({
      product: {
        environment: 'test',
        productId: 'prod_123',
        active: false,
      },
    });

    await expect(stripeServices.deleteProduct('test', 'prod_123')).resolves.toEqual({
      productId: 'prod_123',
      deleted: true,
    });

    expect(mockProvider.updateProduct).toHaveBeenCalledWith('prod_123', {
      name: 'Updated Product',
      active: false,
    });
    expect(mockProvider.deleteProduct).toHaveBeenCalledWith('prod_123');
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_products/i),
      expect.arrayContaining(['test', 'prod_123', 'Updated Product'])
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/DELETE FROM payments\.stripe_prices[\s\S]*product_id = \$2/i),
      ['test', 'prod_123']
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/DELETE FROM payments\.stripe_products[\s\S]*product_id = \$2/i),
      ['test', 'prod_123']
    );
    expect(mockProvider.syncCatalog).not.toHaveBeenCalled();
  });

  it('lists prices from the requested local Stripe mirror with an optional product filter', async () => {
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          environment: 'test',
          priceId: 'price_123',
          productId: 'prod_123',
          active: true,
          currency: 'usd',
          unitAmount: 1000,
          unitAmountDecimal: null,
          type: 'recurring',
          lookupKey: 'pro_monthly',
          billingScheme: 'per_unit',
          taxBehavior: 'exclusive',
          recurringInterval: 'month',
          recurringIntervalCount: 1,
          metadata: {},
          syncedAt: new Date('2026-04-27T00:00:00.000Z'),
        },
      ],
    });

    await expect(
      stripeServices.listPrices({
        environment: 'test',
        productId: 'prod_123',
      })
    ).resolves.toEqual({
      prices: [
        {
          environment: 'test',
          priceId: 'price_123',
          productId: 'prod_123',
          active: true,
          currency: 'usd',
          unitAmount: 1000,
          unitAmountDecimal: null,
          type: 'recurring',
          lookupKey: 'pro_monthly',
          billingScheme: 'per_unit',
          taxBehavior: 'exclusive',
          recurringInterval: 'month',
          recurringIntervalCount: 1,
          metadata: {},
          syncedAt: '2026-04-27T00:00:00.000Z',
        },
      ],
    });
  });

  it('creates, updates, and archives prices through the requested Stripe provider', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockResolvedValue({
      rows: [
        {
          environment: 'test',
          status: 'connected',
          accountId: 'acct_123',
          accountEmail: 'owner@example.com',
          accountLivemode: false,
          lastSyncedAt: new Date('2026-04-27T00:00:00.000Z'),
          lastSyncStatus: 'succeeded',
          lastSyncError: null,
          lastSyncCounts: { products: 1, prices: 1 },
        },
      ],
    });

    await expect(
      stripeServices.createPrice({
        environment: 'test',
        productId: 'prod_123',
        currency: 'usd',
        unitAmount: 2000,
        recurring: { interval: 'month', intervalCount: 1 },
        idempotencyKey: 'agent-price-123',
      })
    ).resolves.toMatchObject({
      price: {
        environment: 'test',
        priceId: 'price_new',
        productId: 'prod_123',
        active: true,
      },
    });

    await expect(
      stripeServices.updatePrice('price_123', {
        environment: 'test',
        active: false,
        metadata: { archived: 'true' },
      })
    ).resolves.toMatchObject({
      price: {
        environment: 'test',
        priceId: 'price_123',
        active: false,
      },
    });

    await expect(stripeServices.archivePrice('test', 'price_123')).resolves.toMatchObject({
      price: {
        environment: 'test',
        priceId: 'price_123',
        active: false,
      },
      archived: true,
    });

    expect(mockProvider.createPrice).toHaveBeenCalledWith({
      productId: 'prod_123',
      currency: 'usd',
      unitAmount: 2000,
      recurring: { interval: 'month', intervalCount: 1 },
      idempotencyKey: 'insforge:test:price:agent-price-123',
    });
    expect(mockProvider.updatePrice).toHaveBeenCalledWith('price_123', {
      active: false,
      metadata: { archived: 'true' },
    });
    expect(mockProvider.updatePrice).toHaveBeenCalledWith('price_123', { active: false });
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_prices/i),
      expect.arrayContaining(['test', 'price_new', 'prod_123', true, 'usd'])
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_prices/i),
      expect.arrayContaining(['test', 'price_123', 'prod_123', false, 'usd'])
    );
    expect(mockProvider.syncCatalog).not.toHaveBeenCalled();
  });

  it('rejects subscription checkout without a billing subject', async () => {
    await expect(
      stripeServices.createCheckoutSession(
        {
          environment: 'test',
          mode: 'subscription',
          lineItems: [{ priceId: 'price_123', quantity: 1 }],
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
        },
        checkoutUser
      )
    ).rejects.toThrow(/billing subject/i);

    expect(mockProvider.createCheckoutSession).not.toHaveBeenCalled();
  });

  it('uses a shared environment lock and a key-scoped lock for idempotent checkout creation', async () => {
    const mockEnvironmentLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockIdempotencyLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockCheckoutClient = {
      query: vi.fn().mockResolvedValue({ rows: [], rowCount: 1 }),
      release: vi.fn(),
    };
    mockPool.connect
      .mockResolvedValueOnce(mockEnvironmentLockClient)
      .mockResolvedValueOnce(mockIdempotencyLockClient)
      .mockResolvedValueOnce(mockCheckoutClient);
    mockPool.query.mockResolvedValueOnce({ rows: [] }).mockResolvedValueOnce({
      rows: [checkoutSessionRow],
    });

    await stripeServices.createCheckoutSession(
      {
        environment: 'test',
        mode: 'subscription',
        lineItems: [{ priceId: 'price_123', quantity: 1 }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        customerEmail: 'buyer@example.com',
        subject: { type: 'team', id: 'team_123' },
        idempotencyKey: 'checkout-123',
      },
      checkoutUser
    );

    expect(mockEnvironmentLockClient.query).toHaveBeenCalledWith(
      'SELECT pg_advisory_lock_shared(hashtext($1))',
      ['payments_environment_test']
    );
    expect(mockEnvironmentLockClient.query).toHaveBeenCalledWith(
      'SELECT pg_advisory_unlock_shared(hashtext($1))',
      ['payments_environment_test']
    );
    expect(mockIdempotencyLockClient.query).toHaveBeenCalledWith(
      'SELECT pg_advisory_lock(hashtext($1))',
      ['payments_checkout_test_checkout-123']
    );
    expect(mockIdempotencyLockClient.query).toHaveBeenCalledWith(
      'SELECT pg_advisory_unlock(hashtext($1))',
      ['payments_checkout_test_checkout-123']
    );
  });

  it('uses only the shared environment lock when checkout has no caller idempotency key', async () => {
    const mockEnvironmentLockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const mockCheckoutClient = {
      query: vi.fn().mockResolvedValue({ rows: [], rowCount: 1 }),
      release: vi.fn(),
    };
    mockPool.connect
      .mockResolvedValueOnce(mockEnvironmentLockClient)
      .mockResolvedValueOnce(mockCheckoutClient);
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          ...checkoutSessionRow,
          id: '8cf48e7a-8e8c-44be-b6f1-68151d4e7331',
          mode: 'payment',
          subjectType: null,
          subjectId: null,
          customerEmail: 'anon@example.com',
        },
      ],
    });

    await stripeServices.createCheckoutSession(
      {
        environment: 'test',
        mode: 'payment',
        lineItems: [{ priceId: 'price_123', quantity: 1 }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        customerEmail: 'anon@example.com',
      },
      anonCheckoutUser
    );

    expect(mockEnvironmentLockClient.query).toHaveBeenCalledWith(
      'SELECT pg_advisory_lock_shared(hashtext($1))',
      ['payments_environment_test']
    );
    expect(mockEnvironmentLockClient.query).toHaveBeenCalledWith(
      'SELECT pg_advisory_unlock_shared(hashtext($1))',
      ['payments_environment_test']
    );
    expect(mockPool.connect).toHaveBeenCalledTimes(2);
    expect(mockEnvironmentLockClient.query).not.toHaveBeenCalledWith(
      'SELECT pg_advisory_lock(hashtext($1))',
      [expect.stringMatching(/^payments_checkout_test_/)]
    );
  });

  it('creates an authorized checkout row before identified Stripe checkout', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [checkoutSessionRow] });
    mockProvider.createCheckoutSession.mockResolvedValueOnce({
      id: 'cs_test_123',
      object: 'checkout.session',
      mode: 'subscription',
      url: 'https://checkout.stripe.com/c/pay/cs_test_123',
      status: 'open',
      payment_status: 'unpaid',
      customer: 'cus_123',
      payment_intent: null,
      subscription: null,
    });

    await expect(
      stripeServices.createCheckoutSession(
        {
          environment: 'test',
          mode: 'subscription',
          lineItems: [{ priceId: 'price_123', quantity: 1 }],
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
          customerEmail: 'buyer@example.com',
          subject: { type: 'team', id: 'team_123' },
          metadata: { plan: 'pro' },
          idempotencyKey: 'checkout-123',
        },
        checkoutUser
      )
    ).resolves.toMatchObject({
      checkoutSession: {
        id: checkoutSessionRow.id,
        environment: 'test',
        checkoutSessionId: 'cs_test_123',
        mode: 'subscription',
        customerId: 'cus_123',
      },
    });

    expect(mockProvider.createCustomer).not.toHaveBeenCalled();
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/SET LOCAL ROLE authenticated/i)
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_checkout_sessions/i),
      expect.arrayContaining([
        'test',
        'subscription',
        'team',
        'team_123',
        'buyer@example.com',
        JSON.stringify([{ priceId: 'price_123', quantity: 1 }]),
        'https://example.com/success',
        'https://example.com/cancel',
      ])
    );
    expect(mockProvider.createCheckoutSession).toHaveBeenCalledWith({
      mode: 'subscription',
      lineItems: [{ priceId: 'price_123', quantity: 1 }],
      successUrl: 'https://example.com/success',
      cancelUrl: 'https://example.com/cancel',
      customerId: null,
      customerEmail: 'buyer@example.com',
      clientReferenceId: expect.any(String),
      metadata: {
        plan: 'pro',
        insforge_checkout_mode: 'subscription',
        insforge_subject_type: 'team',
        insforge_subject_id: 'team_123',
        insforge_checkout_session_id: expect.any(String),
      },
      idempotencyKey: 'insforge:test:checkout_session:checkout-123',
    });
    const checkoutUpdateCall = mockPool.query.mock.calls.find(([sql]) =>
      /UPDATE payments\.stripe_checkout_sessions/i.test(String(sql))
    );
    expect(checkoutUpdateCall?.[1]?.[8]).toEqual({
      plan: 'pro',
      insforge_checkout_mode: 'subscription',
      insforge_subject_type: 'team',
      insforge_subject_id: 'team_123',
    });
  });

  it('returns an existing checkout session for matching caller idempotency retries', async () => {
    const existingCheckoutSessionRow = {
      ...checkoutSessionRow,
      id: 'f3478541-f24c-4833-a060-b81691a761ef',
      checkoutSessionId: 'cs_existing_123',
      url: 'https://checkout.stripe.com/c/pay/cs_existing_123',
    };
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/INSERT INTO payments\.stripe_checkout_sessions/i.test(sql)) {
          return Promise.resolve({ rowCount: 0, rows: [] });
        }

        if (/FROM payments\.stripe_checkout_sessions[\s\S]*idempotency_key = \$2/i.test(sql)) {
          return Promise.resolve({ rows: [existingCheckoutSessionRow] });
        }

        return Promise.resolve({ rowCount: 1, rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);

    await expect(
      stripeServices.createCheckoutSession(
        {
          environment: 'test',
          mode: 'subscription',
          lineItems: [{ priceId: 'price_123', quantity: 1 }],
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
          customerEmail: 'buyer@example.com',
          subject: { type: 'team', id: 'team_123' },
          idempotencyKey: 'checkout-123',
        },
        checkoutUser
      )
    ).resolves.toMatchObject({
      checkoutSession: {
        id: existingCheckoutSessionRow.id,
        checkoutSessionId: 'cs_existing_123',
      },
    });

    expect(mockProvider.createCheckoutSession).not.toHaveBeenCalled();
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/SET LOCAL ROLE authenticated/i)
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/FROM payments\.stripe_checkout_sessions[\s\S]*idempotency_key = \$2/i),
      [
        'test',
        'checkout-123',
        expect.stringMatching(/^[a-f0-9]{64}$/),
        'subscription',
        'team',
        'team_123',
        'buyer@example.com',
        JSON.stringify([{ priceId: 'price_123', quantity: 1 }]),
        'https://example.com/success',
        'https://example.com/cancel',
        'insforge_checkout_session_id',
        JSON.stringify({
          insforge_checkout_mode: 'subscription',
          insforge_subject_type: 'team',
          insforge_subject_id: 'team_123',
        }),
      ]
    );
    expect(mockPool.query).not.toHaveBeenCalled();
  });

  it('resumes incomplete idempotent checkout rows instead of returning unusable rows', async () => {
    const existingCheckoutSessionRow = {
      ...checkoutSessionRow,
      id: 'f3478541-f24c-4833-a060-b81691a761ef',
      status: 'initialized',
      checkoutSessionId: null,
      customerId: null,
      url: null,
    };
    const openedCheckoutSessionRow = {
      ...checkoutSessionRow,
      id: existingCheckoutSessionRow.id,
      checkoutSessionId: 'cs_test_123',
      url: 'https://checkout.stripe.com/c/pay/cs_test_123',
    };
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/INSERT INTO payments\.stripe_checkout_sessions/i.test(sql)) {
          return Promise.resolve({ rowCount: 0, rows: [] });
        }

        if (/FROM payments\.stripe_checkout_sessions[\s\S]*idempotency_key = \$2/i.test(sql)) {
          return Promise.resolve({ rows: [existingCheckoutSessionRow] });
        }

        return Promise.resolve({ rowCount: 1, rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockImplementation((sql: string) => {
      if (/SELECT customer_id AS "providerCustomerId"/i.test(sql)) {
        return Promise.resolve({ rows: [] });
      }

      if (/UPDATE payments\.stripe_checkout_sessions/i.test(sql)) {
        return Promise.resolve({ rows: [openedCheckoutSessionRow] });
      }

      return Promise.resolve({ rowCount: 1, rows: [] });
    });

    await expect(
      stripeServices.createCheckoutSession(
        {
          environment: 'test',
          mode: 'subscription',
          lineItems: [{ priceId: 'price_123', quantity: 1 }],
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
          customerEmail: 'buyer@example.com',
          subject: { type: 'team', id: 'team_123' },
          idempotencyKey: 'checkout-123',
        },
        checkoutUser
      )
    ).resolves.toMatchObject({
      checkoutSession: {
        id: existingCheckoutSessionRow.id,
        checkoutSessionId: 'cs_test_123',
        url: 'https://checkout.stripe.com/c/pay/cs_test_123',
      },
    });

    expect(mockProvider.createCheckoutSession).toHaveBeenCalledWith({
      mode: 'subscription',
      lineItems: [{ priceId: 'price_123', quantity: 1 }],
      successUrl: 'https://example.com/success',
      cancelUrl: 'https://example.com/cancel',
      customerId: null,
      customerEmail: 'buyer@example.com',
      clientReferenceId: existingCheckoutSessionRow.id,
      metadata: {
        insforge_checkout_mode: 'subscription',
        insforge_subject_type: 'team',
        insforge_subject_id: 'team_123',
        insforge_checkout_session_id: existingCheckoutSessionRow.id,
      },
      idempotencyKey: 'insforge:test:checkout_session:checkout-123',
    });
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/UPDATE payments\.stripe_checkout_sessions/i),
      expect.arrayContaining([existingCheckoutSessionRow.id, expect.any(String)])
    );
  });

  it('does not expose idempotent checkout rows hidden by checkout session RLS', async () => {
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/INSERT INTO payments\.stripe_checkout_sessions/i.test(sql)) {
          return Promise.resolve({ rowCount: 0, rows: [] });
        }

        if (/FROM payments\.stripe_checkout_sessions[\s\S]*idempotency_key = \$2/i.test(sql)) {
          return Promise.resolve({ rows: [] });
        }

        return Promise.resolve({ rowCount: 1, rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);

    await expect(
      stripeServices.createCheckoutSession(
        {
          environment: 'test',
          mode: 'subscription',
          lineItems: [{ priceId: 'price_123', quantity: 1 }],
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
          customerEmail: 'buyer@example.com',
          subject: { type: 'team', id: 'team_123' },
          idempotencyKey: 'checkout-123',
        },
        checkoutUser
      )
    ).rejects.toThrow(/Idempotency key is already used/);

    expect(mockProvider.createCheckoutSession).not.toHaveBeenCalled();
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/SET LOCAL ROLE authenticated/i)
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/FROM payments\.stripe_checkout_sessions[\s\S]*idempotency_key = \$2/i),
      [
        'test',
        'checkout-123',
        expect.stringMatching(/^[a-f0-9]{64}$/),
        'subscription',
        'team',
        'team_123',
        'buyer@example.com',
        JSON.stringify([{ priceId: 'price_123', quantity: 1 }]),
        'https://example.com/success',
        'https://example.com/cancel',
        'insforge_checkout_session_id',
        JSON.stringify({
          insforge_checkout_mode: 'subscription',
          insforge_subject_type: 'team',
          insforge_subject_id: 'team_123',
        }),
      ]
    );
    expect(mockClient.query).toHaveBeenCalledWith('ROLLBACK');
    expect(mockPool.query).not.toHaveBeenCalled();
  });

  it('reuses an existing Stripe customer mapping for identified checkout', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ providerCustomerId: 'cus_existing' }],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            ...checkoutSessionRow,
            id: '0dd01831-1742-4f1f-9622-f2274ca7f6c5',
            mode: 'payment',
            subjectType: 'organization',
            subjectId: 'org_123',
            customerId: 'cus_existing',
          },
        ],
      });

    await stripeServices.createCheckoutSession(
      {
        environment: 'test',
        mode: 'payment',
        lineItems: [{ priceId: 'price_123', quantity: 1 }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        subject: { type: 'organization', id: 'org_123' },
      },
      checkoutUser
    );

    expect(mockProvider.createCustomer).not.toHaveBeenCalled();
    expect(mockProvider.createCheckoutSession).toHaveBeenCalledWith(
      expect.objectContaining({
        customerId: 'cus_existing',
        customerEmail: null,
        metadata: {
          insforge_checkout_mode: 'payment',
          insforge_subject_type: 'organization',
          insforge_subject_id: 'org_123',
          insforge_checkout_session_id: expect.any(String),
        },
        clientReferenceId: expect.any(String),
        idempotencyKey: expect.stringMatching(/^insforge:test:checkout_session:/),
      })
    );
  });

  it('asks Stripe to create a Customer for identified one-time checkout without a mapping', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockResolvedValueOnce({ rows: [] }).mockResolvedValueOnce({
      rows: [
        {
          ...checkoutSessionRow,
          id: '81b9f7aa-87c7-4f5a-8d3b-9112730f13af',
          mode: 'payment',
          subjectType: 'organization',
          subjectId: 'org_123',
          customerEmail: 'buyer@example.com',
          customerId: 'cus_123',
        },
      ],
    });

    await stripeServices.createCheckoutSession(
      {
        environment: 'test',
        mode: 'payment',
        lineItems: [{ priceId: 'price_123', quantity: 1 }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        customerEmail: 'buyer@example.com',
        subject: { type: 'organization', id: 'org_123' },
      },
      checkoutUser
    );

    expect(mockProvider.createCustomer).not.toHaveBeenCalled();
    expect(mockProvider.createCheckoutSession).toHaveBeenCalledWith(
      expect.objectContaining({
        mode: 'payment',
        customerId: null,
        customerEmail: 'buyer@example.com',
        customerCreation: 'always',
        metadata: {
          insforge_checkout_mode: 'payment',
          insforge_subject_type: 'organization',
          insforge_subject_id: 'org_123',
          insforge_checkout_session_id: expect.any(String),
        },
      })
    );
  });

  it('allows anonymous one-time checkout without creating a customer mapping', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          ...checkoutSessionRow,
          id: '7b98f4d1-880f-4e2a-b9f2-414398e376a1',
          mode: 'payment',
          subjectType: null,
          subjectId: null,
          customerEmail: 'anon@example.com',
        },
      ],
    });

    await stripeServices.createCheckoutSession(
      {
        environment: 'test',
        mode: 'payment',
        lineItems: [{ priceId: 'price_123', quantity: 2 }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        customerEmail: 'anon@example.com',
      },
      anonCheckoutUser
    );

    expect(mockProvider.createCustomer).not.toHaveBeenCalled();
    expect(mockProvider.createCheckoutSession).toHaveBeenCalledWith({
      mode: 'payment',
      lineItems: [{ priceId: 'price_123', quantity: 2 }],
      successUrl: 'https://example.com/success',
      cancelUrl: 'https://example.com/cancel',
      customerId: null,
      customerEmail: 'anon@example.com',
      clientReferenceId: expect.any(String),
      metadata: {
        insforge_checkout_mode: 'payment',
        insforge_checkout_session_id: expect.any(String),
      },
      idempotencyKey: expect.stringMatching(/^insforge:test:checkout_session:/),
    });
    expect(mockPool.query).not.toHaveBeenCalledWith(
      expect.stringMatching(/payments\.customer_mappings/i),
      expect.any(Array)
    );
  });

  it('rejects caller-controlled InsForge checkout metadata before creating Stripe checkout', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);

    await expect(
      stripeServices.createCheckoutSession(
        {
          environment: 'test',
          mode: 'payment',
          lineItems: [{ priceId: 'price_123', quantity: 1 }],
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
          customerEmail: 'anon@example.com',
          metadata: {
            insforge_subject_type: 'team',
            insforge_subject_id: 'team_victim',
          },
        },
        anonCheckoutUser
      )
    ).rejects.toThrow(/reserved for InsForge/i);

    expect(mockProvider.createCheckoutSession).not.toHaveBeenCalled();
    expect(mockProvider.createCustomer).not.toHaveBeenCalled();
    expect(mockClient.query).not.toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_checkout_sessions/i),
      expect.any(Array)
    );
  });

  it('does not call Stripe when stripe_checkout_sessions RLS denies the insert', async () => {
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/INSERT INTO payments\.stripe_checkout_sessions/i.test(sql)) {
          return Promise.reject({ code: '42501', message: 'new row violates row-level security' });
        }

        return Promise.resolve({ rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);

    await expect(
      stripeServices.createCheckoutSession(
        {
          environment: 'test',
          mode: 'payment',
          lineItems: [{ priceId: 'price_123', quantity: 1 }],
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
          subject: { type: 'team', id: 'team_123' },
        },
        checkoutUser
      )
    ).rejects.toThrow(/RLS policies/i);

    expect(mockProvider.createCheckoutSession).not.toHaveBeenCalled();
    expect(mockProvider.createCustomer).not.toHaveBeenCalled();
  });

  it('creates a Stripe customer portal session from an existing billing subject mapping', async () => {
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/INSERT INTO payments\.stripe_customer_portal_sessions/i.test(sql)) {
          return Promise.resolve({ rows: [customerPortalSessionRow] });
        }

        return Promise.resolve({ rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ providerCustomerId: 'cus_123' }],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            ...customerPortalSessionRow,
            status: 'created',
            customerId: 'cus_123',
            url: 'https://billing.stripe.com/p/session/test_123',
          },
        ],
      });

    await expect(
      stripeServices.createCustomerPortalSession(
        {
          environment: 'test',
          subject: { type: 'team', id: 'team_123' },
          returnUrl: 'https://example.com/account',
          configuration: 'bpc_123',
        },
        checkoutUser
      )
    ).resolves.toEqual({
      customerPortalSession: {
        id: customerPortalSessionRow.id,
        environment: 'test',
        status: 'created',
        subjectType: 'team',
        subjectId: 'team_123',
        customerId: 'cus_123',
        returnUrl: 'https://example.com/account',
        configuration: 'bpc_123',
        url: 'https://billing.stripe.com/p/session/test_123',
        lastError: null,
        createdAt: '2026-04-29T00:00:00.000Z',
        updatedAt: '2026-04-29T00:00:00.000Z',
      },
    });

    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/SET LOCAL ROLE authenticated/i)
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_customer_portal_sessions/i),
      expect.arrayContaining(['test', 'team', 'team_123', 'https://example.com/account', 'bpc_123'])
    );
    expect(mockProvider.createCustomerPortalSession).toHaveBeenCalledWith({
      customerId: 'cus_123',
      returnUrl: 'https://example.com/account',
      configuration: 'bpc_123',
    });
  });

  it('rejects customer portal sessions when the billing subject has no Stripe customer mapping', async () => {
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/INSERT INTO payments\.stripe_customer_portal_sessions/i.test(sql)) {
          return Promise.resolve({
            rows: [{ ...customerPortalSessionRow, subjectId: 'missing_team' }],
          });
        }

        return Promise.resolve({ rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query.mockResolvedValue({ rows: [] });

    await expect(
      stripeServices.createCustomerPortalSession(
        {
          environment: 'test',
          subject: { type: 'team', id: 'missing_team' },
          returnUrl: 'https://example.com/account',
        },
        checkoutUser
      )
    ).rejects.toThrow(/No Stripe customer is mapped/i);

    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_customer_portal_sessions/i),
      expect.any(Array)
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/UPDATE payments\.stripe_customer_portal_sessions/i),
      [expect.any(String), expect.stringMatching(/No Stripe customer is mapped/i)]
    );
    expect(mockProvider.createCustomerPortalSession).not.toHaveBeenCalled();
  });

  it('does not call Stripe when stripe_customer_portal_sessions RLS denies the insert', async () => {
    const mockClient = {
      query: vi.fn().mockImplementation((sql: string) => {
        if (/INSERT INTO payments\.stripe_customer_portal_sessions/i.test(sql)) {
          return Promise.reject({ code: '42501', message: 'new row violates row-level security' });
        }

        return Promise.resolve({ rows: [] });
      }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);

    await expect(
      stripeServices.createCustomerPortalSession(
        {
          environment: 'test',
          subject: { type: 'team', id: 'team_123' },
          returnUrl: 'https://example.com/account',
        },
        checkoutUser
      )
    ).rejects.toThrow(/stripe_customer_portal_sessions RLS policies/i);

    expect(mockProvider.createCustomerPortalSession).not.toHaveBeenCalled();
    expect(mockPool.query).not.toHaveBeenCalledWith(
      expect.stringMatching(/customer_mappings/i),
      expect.any(Array)
    );
  });

  it('rejects anonymous customer portal sessions', async () => {
    await expect(
      stripeServices.createCustomerPortalSession(
        {
          environment: 'test',
          subject: { type: 'team', id: 'team_123' },
          returnUrl: 'https://example.com/account',
        },
        anonCheckoutUser
      )
    ).rejects.toThrow(/authenticated user/i);

    expect(mockPool.query).not.toHaveBeenCalledWith(
      expect.stringMatching(/customer_mappings/i),
      expect.any(Array)
    );
    expect(mockProvider.createCustomerPortalSession).not.toHaveBeenCalled();
  });

  it('stores duplicate processed Stripe webhook events without reprocessing', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_123',
      type: 'checkout.session.completed',
      livemode: false,
      data: { object: { id: 'cs_test_123', object: 'checkout.session' } },
    });
    mockPool.query
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook('test', Buffer.from('{"id":"evt_123"}'), 'sig_123')
    ).resolves.toMatchObject({
      received: true,
      handled: false,
      event: {
        eventId: 'evt_123',
        processingStatus: 'processed',
      },
    });

    expect(mockPool.query).not.toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.transactions/i),
      expect.any(Array)
    );
  });

  it('records one-time transactions from checkout.session.completed webhooks', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_123',
      type: 'checkout.session.completed',
      created: 1777334700,
      livemode: false,
      data: {
        object: {
          id: 'cs_test_123',
          object: 'checkout.session',
          mode: 'payment',
          payment_status: 'paid',
          amount_total: 4500,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          customer_details: { email: 'buyer@example.com' },
          payment_intent: 'pi_123',
          subscription: null,
          metadata: {
            insforge_subject_type: 'team',
            insforge_subject_id: 'team_123',
          },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook('test', Buffer.from('{"id":"evt_123"}'), 'sig_123')
    ).resolves.toMatchObject({
      received: true,
      handled: true,
      event: {
        eventId: 'evt_123',
        processingStatus: 'processed',
      },
    });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'payment_intent',
        'pi_123',
        'one_time_payment',
        'succeeded',
        'team',
        'team_123',
        'cus_123',
        'buyer@example.com',
        4500,
        'usd',
        new Date('2026-04-28T00:05:00.000Z'),
        new Date('2026-04-28T00:00:00.000Z'),
        expect.objectContaining({ id: 'cs_test_123' }),
      ])
    );
    const transactionCall = mockPool.query.mock.calls.find(([sql]) =>
      /WITH refs[\s\S]*INSERT INTO payments\.transactions/i.test(String(sql))
    );
    expect(String(transactionCall?.[0])).toMatch(/RETURNING\s+tx\.id/i);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/WITH refund_totals[\s\S]*UPDATE payments\.transactions original/i),
      ['test', 'pi_123', null]
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.customer_mappings/i),
      ['test', 'team', 'team_123', 'cus_123']
    );
  });

  it('does not mark webhook events failed when finalization fails after processing', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_postprocess_123',
      type: 'checkout.session.completed',
      created: 1777334700,
      livemode: false,
      data: {
        object: {
          id: 'cs_postprocess_123',
          object: 'checkout.session',
          mode: 'payment',
          payment_status: 'paid',
          amount_total: 4500,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          customer_details: { email: 'buyer@example.com' },
          payment_intent: 'pi_postprocess_123',
          subscription: null,
          metadata: {
            insforge_subject_type: 'team',
            insforge_subject_id: 'team_123',
          },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_postprocess_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_postprocess_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockRejectedValueOnce(new Error('mark processed failed'));

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_postprocess_123"}'),
        'sig_123'
      )
    ).rejects.toThrow('mark processed failed');

    expect(mockPool.query).toHaveBeenCalledTimes(6);
    expect(mockLogger.error).toHaveBeenCalledWith(
      'Failed to finalize Stripe webhook event after processing',
      {
        environment: 'test',
        eventId: 'evt_postprocess_123',
        handled: true,
        error: 'mark processed failed',
      }
    );
  });

  it('marks webhook events failed when applying the webhook event fails', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_apply_fail_123',
      type: 'invoice.paid',
      livemode: false,
      data: {
        object: {
          id: 'in_apply_fail_123',
          object: 'invoice',
          amount_due: 9900,
          amount_paid: 9900,
          currency: 'usd',
          customer: 'cus_123',
          customer_email: 'buyer@example.com',
          description: 'Failure invoice',
          created: 1777334400,
          status_transitions: { paid_at: 1777334700 },
          lines: { data: [] },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_apply_fail_123',
            eventType: 'invoice.paid',
            livemode: false,
            accountId: null,
            objectType: 'invoice',
            objectId: 'in_apply_fail_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockRejectedValueOnce(new Error('activity write failed'))
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_apply_fail_123',
            eventType: 'invoice.paid',
            livemode: false,
            accountId: null,
            objectType: 'invoice',
            objectId: 'in_apply_fail_123',
            processingStatus: 'failed',
            attemptCount: 1,
            lastError: 'activity write failed',
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_apply_fail_123"}'),
        'sig_123'
      )
    ).rejects.toThrow('activity write failed');

    expect(mockPool.query).toHaveBeenLastCalledWith(
      expect.stringMatching(/UPDATE payments\.webhook_events/i),
      ['stripe', 'test', 'evt_apply_fail_123', 'failed', 'activity write failed']
    );
  });

  it('maps customer identity for completed delayed checkout sessions before payment settles', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_delayed_completed_123',
      type: 'checkout.session.completed',
      livemode: false,
      data: {
        object: {
          id: 'cs_test_delayed_123',
          object: 'checkout.session',
          mode: 'payment',
          payment_status: 'unpaid',
          amount_total: 4500,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          customer_details: { email: 'buyer@example.com' },
          payment_intent: 'pi_delayed_123',
          subscription: null,
          metadata: {
            insforge_subject_type: 'team',
            insforge_subject_id: 'team_123',
          },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_delayed_completed_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_delayed_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_delayed_completed_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_delayed_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_delayed_completed_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.customer_mappings/i),
      ['test', 'team', 'team_123', 'cus_123']
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining(['test', 'pending', 'team', 'team_123', 'cus_123'])
    );
  });

  it('deduplicates one-time checkout activity by checkout session when PaymentIntent is absent', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_no_payment_required_123',
      type: 'checkout.session.completed',
      livemode: false,
      data: {
        object: {
          id: 'cs_test_no_payment_required_123',
          object: 'checkout.session',
          mode: 'payment',
          payment_status: 'no_payment_required',
          amount_total: 0,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          customer_details: { email: 'buyer@example.com' },
          payment_intent: null,
          subscription: null,
          metadata: {
            insforge_subject_type: 'team',
            insforge_subject_id: 'team_123',
          },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_no_payment_required_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_no_payment_required_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_no_payment_required_123',
            eventType: 'checkout.session.completed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_no_payment_required_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_no_payment_required_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'checkout_session',
        'cs_test_no_payment_required_123',
        'one_time_payment',
        'pending',
        'team',
        'team_123',
        'cus_123',
        'buyer@example.com',
        0,
        'usd',
        new Date('2026-04-28T00:00:00.000Z'),
        expect.objectContaining({ id: 'cs_test_no_payment_required_123' }),
      ])
    );
  });

  it('records failed delayed checkout payments from async failure webhooks', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_async_failed_123',
      type: 'checkout.session.async_payment_failed',
      created: 1777335000,
      livemode: false,
      data: {
        object: {
          id: 'cs_test_async_123',
          object: 'checkout.session',
          mode: 'payment',
          payment_status: 'unpaid',
          amount_total: 4500,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          customer_details: { email: 'buyer@example.com' },
          payment_intent: 'pi_async_123',
          subscription: null,
          metadata: {
            insforge_subject_type: 'team',
            insforge_subject_id: 'team_123',
          },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_async_failed_123',
            eventType: 'checkout.session.async_payment_failed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_async_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_async_failed_123',
            eventType: 'checkout.session.async_payment_failed',
            livemode: false,
            accountId: null,
            objectType: 'checkout.session',
            objectId: 'cs_test_async_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_async_failed_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'payment_intent',
        'pi_async_123',
        'one_time_payment',
        'failed',
        'team',
        'team_123',
        'cus_123',
        'buyer@example.com',
        4500,
        'usd',
        new Date('2026-04-28T00:10:00.000Z'),
        expect.objectContaining({ id: 'cs_test_async_123' }),
      ])
    );
  });

  it('records subscription invoice transactions from invoice webhooks', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_invoice_123',
      type: 'invoice.paid',
      livemode: false,
      data: {
        object: {
          id: 'in_123',
          object: 'invoice',
          amount_due: 9900,
          amount_paid: 9900,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          customer_email: 'buyer@example.com',
          description: 'Subscription invoice',
          number: 'INV-123',
          metadata: {},
          status_transitions: { paid_at: 1777334500 },
          parent: {
            type: 'subscription_details',
            quote_details: null,
            subscription_details: {
              subscription: 'sub_123',
              metadata: {
                insforge_subject_type: 'organization',
                insforge_subject_id: 'org_123',
              },
            },
          },
          payments: {
            data: [
              {
                payment: {
                  type: 'payment_intent',
                  payment_intent: 'pi_invoice_123',
                },
              },
            ],
          },
          lines: {
            data: [
              {
                pricing: {
                  price_details: {
                    product: 'prod_123',
                    price: 'price_123',
                  },
                },
              },
            ],
          },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_invoice_123',
            eventType: 'invoice.paid',
            livemode: false,
            accountId: null,
            objectType: 'invoice',
            objectId: 'in_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rowCount: 1, rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_invoice_123',
            eventType: 'invoice.paid',
            livemode: false,
            accountId: null,
            objectType: 'invoice',
            objectId: 'in_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook('test', Buffer.from('{"id":"evt_invoice_123"}'), 'sig_123')
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'payment_intent',
        'pi_invoice_123',
        'subscription_invoice',
        'succeeded',
        'organization',
        'org_123',
        'cus_123',
        'buyer@example.com',
        9900,
        'usd',
        'Subscription invoice',
        new Date('2026-04-28T00:01:40.000Z'),
        new Date('2026-04-28T00:00:00.000Z'),
        expect.objectContaining({ id: 'in_123' }),
      ])
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /related_object_ids = tx\.related_object_ids \|\| EXCLUDED\.related_object_ids/i
      ),
      expect.any(Array)
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.customer_mappings[\s\S]*ON CONFLICT DO NOTHING/i
      ),
      ['stripe', 'test', 'organization', 'org_123', 'cus_123']
    );
  });

  it('resolves invoice subjects from existing customer mappings without backfilling', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_invoice_456',
      type: 'invoice.paid',
      livemode: false,
      data: {
        object: {
          id: 'in_456',
          object: 'invoice',
          amount_due: 9900,
          amount_paid: 9900,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_456',
          customer_email: 'buyer@example.com',
          description: 'Subscription invoice',
          number: 'INV-456',
          metadata: {},
          status_transitions: { paid_at: 1777334500 },
          parent: {
            type: 'subscription_details',
            quote_details: null,
            subscription_details: {
              subscription: 'sub_456',
              metadata: {},
            },
          },
          payments: {
            data: [
              {
                payment: {
                  type: 'payment_intent',
                  payment_intent: 'pi_invoice_456',
                },
              },
            ],
          },
          lines: { data: [] },
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_invoice_456',
            eventType: 'invoice.paid',
            livemode: false,
            accountId: null,
            objectType: 'invoice',
            objectId: 'in_456',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [{ subjectType: 'user', subjectId: 'user_456' }] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_invoice_456',
            eventType: 'invoice.paid',
            livemode: false,
            accountId: null,
            objectType: 'invoice',
            objectId: 'in_456',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook('test', Buffer.from('{"id":"evt_invoice_456"}'), 'sig_123')
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/FROM payments\.customer_mappings/i),
      ['test', 'cus_456']
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining(['test', 'payment_intent', 'pi_invoice_456', 'user', 'user_456'])
    );
    expect(mockPool.query).not.toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.customer_mappings/i),
      expect.any(Array)
    );
  });

  it('ignores PaymentIntent webhooks that are not InsForge one-time checkout payments', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_pi_invoice_123',
      type: 'payment_intent.succeeded',
      livemode: false,
      data: {
        object: {
          id: 'pi_invoice_123',
          object: 'payment_intent',
          amount: 9900,
          amount_received: 9900,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          latest_charge: 'ch_123',
          metadata: {},
          receipt_email: null,
          description: null,
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_pi_invoice_123',
            eventType: 'payment_intent.succeeded',
            livemode: false,
            accountId: null,
            objectType: 'payment_intent',
            objectId: 'pi_invoice_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_pi_invoice_123',
            eventType: 'payment_intent.succeeded',
            livemode: false,
            accountId: null,
            objectType: 'payment_intent',
            objectId: 'pi_invoice_123',
            processingStatus: 'ignored',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_pi_invoice_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({
      received: true,
      handled: false,
      event: { processingStatus: 'ignored' },
    });

    expect(mockPool.query).not.toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.transactions/i),
      expect.any(Array)
    );
  });

  it('records InsForge one-time PaymentIntent webhooks with non-refund uniqueness', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_pi_checkout_123',
      type: 'payment_intent.succeeded',
      livemode: false,
      data: {
        object: {
          id: 'pi_checkout_123',
          object: 'payment_intent',
          amount: 4500,
          amount_received: 4500,
          currency: 'usd',
          created: 1777334400,
          customer: 'cus_123',
          latest_charge: 'ch_123',
          metadata: {
            insforge_checkout_mode: 'payment',
            insforge_subject_type: 'team',
            insforge_subject_id: 'team_123',
          },
          receipt_email: 'buyer@example.com',
          description: 'One-time checkout',
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_pi_checkout_123',
            eventType: 'payment_intent.succeeded',
            livemode: false,
            accountId: null,
            objectType: 'payment_intent',
            objectId: 'pi_checkout_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_pi_checkout_123',
            eventType: 'payment_intent.succeeded',
            livemode: false,
            accountId: null,
            objectType: 'payment_intent',
            objectId: 'pi_checkout_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_pi_checkout_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'payment_intent',
        'pi_checkout_123',
        'one_time_payment',
        'succeeded',
        'team',
        'team_123',
        'cus_123',
        'buyer@example.com',
        4500,
        'usd',
        'One-time checkout',
        new Date('2026-04-28T00:00:00.000Z'),
        new Date('2026-04-28T00:00:00.000Z'),
        expect.objectContaining({ id: 'pi_checkout_123' }),
      ])
    );
    const transactionSql = mockPool.query.mock.calls.find(([sql]) =>
      /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i.test(
        String(sql)
      )
    )?.[0];
    expect(String(transactionSql)).toMatch(
      /ORDER BY\s+CASE WHEN tx\.provider_object_type = \$2 AND tx\.provider_object_id = \$3 THEN 0 ELSE 1 END,\s+tx\.created_at DESC/i
    );
    expect(String(transactionSql)).toMatch(
      /ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)[\s\S]*DO UPDATE SET[\s\S]*amount_refunded = COALESCE\(\$14, tx\.amount_refunded, 0\)/i
    );
  });

  it('records refund activity and copies context from the original payment', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_refund_123',
      type: 'refund.created',
      livemode: false,
      data: {
        object: {
          id: 're_123',
          object: 'refund',
          amount: 2500,
          currency: 'usd',
          created: 1777334600,
          charge: 'ch_123',
          payment_intent: 'pi_invoice_123',
          reason: 'requested_by_customer',
          description: null,
          status: 'succeeded',
          metadata: {},
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_refund_123',
            eventType: 'refund.created',
            livemode: false,
            accountId: null,
            objectType: 'refund',
            objectId: 're_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            subjectType: 'organization',
            subjectId: 'org_123',
            customerId: 'cus_123',
            customerEmailSnapshot: 'buyer@example.com',
            invoiceId: 'in_123',
            subscriptionId: 'sub_123',
            productId: 'prod_123',
            priceId: 'price_123',
            description: 'Subscription invoice',
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_refund_123',
            eventType: 'refund.created',
            livemode: false,
            accountId: null,
            objectType: 'refund',
            objectId: 're_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook('test', Buffer.from('{"id":"evt_refund_123"}'), 'sig_123')
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'refund',
        're_123',
        'payment_intent',
        'pi_invoice_123',
        'refund',
        'refunded',
        'organization',
        'org_123',
        'buyer@example.com',
        2500,
        'usd',
        'requested_by_customer',
        new Date('2026-04-28T00:03:20.000Z'),
        new Date('2026-04-28T00:03:20.000Z'),
        expect.objectContaining({ id: 're_123' }),
      ])
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /DO UPDATE SET[\s\S]*subject_type = COALESCE\(EXCLUDED\.subject_type, tx\.subject_type\)[\s\S]*related_object_ids = tx\.related_object_ids \|\| EXCLUDED\.related_object_ids/i
      ),
      expect.any(Array)
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /WITH refund_totals[\s\S]*UPDATE payments\.transactions original[\s\S]*related_object_ids = original_context\.related_object_ids \|\| refund\.related_object_ids/i
      ),
      ['test', 'pi_invoice_123', 'ch_123']
    );
  });

  it('hydrates refund context from Stripe when refund arrives before the payment event', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_refund_early_123',
      type: 'refund.created',
      livemode: false,
      data: {
        object: {
          id: 're_early_123',
          object: 'refund',
          amount: 1500,
          currency: 'usd',
          created: 1777334600,
          charge: 'ch_early_123',
          payment_intent: 'pi_early_123',
          reason: 'requested_by_customer',
          description: null,
          status: 'succeeded',
          metadata: {},
        },
      },
    });
    mockProvider.retrievePaymentIntent.mockResolvedValueOnce({
      id: 'pi_early_123',
      object: 'payment_intent',
      status: 'succeeded',
      customer: 'cus_early_123',
      latest_charge: 'ch_early_123',
      amount: 4500,
      amount_received: 4500,
      currency: 'usd',
      description: 'Early checkout',
      receipt_email: 'early@example.com',
      created: 1777334400,
      metadata: {
        insforge_checkout_mode: 'payment',
        insforge_subject_type: 'team',
        insforge_subject_id: 'team_early_123',
      },
    } as unknown as StripePaymentIntent);
    mockProvider.retrieveCharge.mockResolvedValueOnce({
      id: 'ch_early_123',
      object: 'charge',
      customer: 'cus_early_123',
      payment_intent: 'pi_early_123',
      amount_refunded: 1500,
      refunded: false,
      refunds: { data: [] },
      billing_details: { email: 'early@example.com' },
      description: 'Early checkout',
      metadata: {},
    } as unknown as StripeCharge);
    mockProvider.retrieveInvoiceByPaymentIntent.mockResolvedValueOnce(null);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_refund_early_123',
            eventType: 'refund.created',
            livemode: false,
            accountId: null,
            objectType: 'refund',
            objectId: 're_early_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            subjectType: 'team',
            subjectId: 'team_early_123',
            customerId: 'cus_early_123',
            customerEmailSnapshot: 'early@example.com',
            invoiceId: null,
            subscriptionId: null,
            productId: null,
            priceId: null,
            description: 'Early checkout',
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_refund_early_123',
            eventType: 'refund.created',
            livemode: false,
            accountId: null,
            objectType: 'refund',
            objectId: 're_early_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_refund_early_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockProvider.retrievePaymentIntent).toHaveBeenCalledWith('pi_early_123');
    expect(mockProvider.retrieveCharge).toHaveBeenCalledWith('ch_early_123');
    expect(mockProvider.retrieveInvoiceByPaymentIntent).toHaveBeenCalledWith('pi_early_123');
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'payment_intent',
        'pi_early_123',
        'one_time_payment',
        'succeeded',
        'team',
        'team_early_123',
        'cus_early_123',
        'early@example.com',
        4500,
        'usd',
        'Early checkout',
        new Date('2026-04-28T00:00:00.000Z'),
        new Date('2026-04-28T00:00:00.000Z'),
        expect.objectContaining({ id: 'pi_early_123' }),
      ])
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*provider_parent_object_type/i
      ),
      expect.arrayContaining([
        'test',
        'refund',
        're_early_123',
        'payment_intent',
        'pi_early_123',
        'refund',
        'refunded',
        'team',
        'team_early_123',
        'early@example.com',
      ])
    );
  });

  it('hydrates subscription refund context from Stripe invoice payments when refund arrives first', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_refund_sub_early_123',
      type: 'refund.created',
      livemode: false,
      data: {
        object: {
          id: 're_sub_early_123',
          object: 'refund',
          amount: 3300,
          currency: 'usd',
          created: 1777334600,
          charge: 'ch_sub_early_123',
          payment_intent: 'pi_sub_early_123',
          reason: 'requested_by_customer',
          description: null,
          status: 'succeeded',
          metadata: {},
        },
      },
    });
    mockProvider.retrievePaymentIntent.mockResolvedValueOnce({
      id: 'pi_sub_early_123',
      object: 'payment_intent',
      status: 'succeeded',
      customer: 'cus_sub_early_123',
      latest_charge: 'ch_sub_early_123',
      amount: 9900,
      amount_received: 9900,
      currency: 'usd',
      description: null,
      receipt_email: null,
      created: 1777334400,
      metadata: {},
    } as unknown as StripePaymentIntent);
    mockProvider.retrieveCharge.mockResolvedValueOnce({
      id: 'ch_sub_early_123',
      object: 'charge',
      customer: 'cus_sub_early_123',
      payment_intent: 'pi_sub_early_123',
      amount_refunded: 3300,
      refunded: false,
      refunds: { data: [] },
      billing_details: { email: 'subscription@example.com' },
      description: null,
      metadata: {},
    } as unknown as StripeCharge);
    mockProvider.retrieveInvoiceByPaymentIntent.mockResolvedValueOnce({
      id: 'in_sub_early_123',
      object: 'invoice',
      amount_due: 9900,
      amount_paid: 9900,
      currency: 'usd',
      created: 1777334400,
      customer: 'cus_sub_early_123',
      customer_email: 'subscription@example.com',
      description: 'Subscription invoice',
      number: 'INV-SUB-123',
      metadata: {},
      status_transitions: { paid_at: 1777334500 },
      parent: {
        type: 'subscription_details',
        quote_details: null,
        subscription_details: {
          subscription: 'sub_early_123',
          metadata: {
            insforge_subject_type: 'organization',
            insforge_subject_id: 'org_early_123',
          },
        },
      },
      payments: {
        data: [
          {
            id: 'inpay_sub_early_123',
            payment: {
              type: 'payment_intent',
              payment_intent: 'pi_sub_early_123',
            },
          },
        ],
      },
      lines: {
        data: [
          {
            pricing: {
              price_details: {
                product: 'prod_sub_early_123',
                price: 'price_sub_early_123',
              },
            },
          },
        ],
      },
    } as unknown as StripeInvoice);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_refund_sub_early_123',
            eventType: 'refund.created',
            livemode: false,
            accountId: null,
            objectType: 'refund',
            objectId: 're_sub_early_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_refund_sub_early_123',
            eventType: 'refund.created',
            livemode: false,
            accountId: null,
            objectType: 'refund',
            objectId: 're_sub_early_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_refund_sub_early_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockProvider.retrieveInvoiceByPaymentIntent).toHaveBeenCalledWith('pi_sub_early_123');
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*ON CONFLICT \(provider, environment, provider_object_type, provider_object_id\)/i
      ),
      expect.arrayContaining([
        'test',
        'payment_intent',
        'pi_sub_early_123',
        'subscription_invoice',
        'succeeded',
        'organization',
        'org_early_123',
        'cus_sub_early_123',
        'subscription@example.com',
      ])
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.transactions[\s\S]*provider_parent_object_type/i
      ),
      expect.arrayContaining([
        'test',
        'refund',
        're_sub_early_123',
        'payment_intent',
        'pi_sub_early_123',
        'refund',
        'refunded',
        'organization',
        'org_early_123',
        'cus_sub_early_123',
        'subscription@example.com',
      ])
    );
  });

  it('upserts subscription projections from subscription webhooks', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_sub_123',
      type: 'customer.subscription.updated',
      livemode: false,
      data: {
        object: {
          id: 'sub_123',
          object: 'subscription',
          customer: 'cus_123',
          status: 'active',
          current_period_start: 1777334400,
          current_period_end: 1779926400,
          cancel_at_period_end: true,
          cancel_at: 1779926400,
          canceled_at: null,
          trial_start: null,
          trial_end: null,
          latest_invoice: 'in_123',
          metadata: {
            insforge_subject_type: 'organization',
            insforge_subject_id: 'org_123',
          },
          items: {
            data: [
              {
                id: 'si_123',
                object: 'subscription_item',
                quantity: 1,
                metadata: {},
                price: {
                  id: 'price_123',
                  product: 'prod_123',
                },
              },
            ],
          },
        },
      },
    });
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_sub_123',
            eventType: 'customer.subscription.updated',
            livemode: false,
            accountId: null,
            objectType: 'subscription',
            objectId: 'sub_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rowCount: 1, rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_sub_123',
            eventType: 'customer.subscription.updated',
            livemode: false,
            accountId: null,
            objectType: 'subscription',
            objectId: 'sub_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-04-28T00:00:00.000Z'),
            processedAt: new Date('2026-04-28T00:00:01.000Z'),
            createdAt: new Date('2026-04-28T00:00:00.000Z'),
            updatedAt: new Date('2026-04-28T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook('test', Buffer.from('{"id":"evt_sub_123"}'), 'sig_123')
    ).resolves.toMatchObject({ received: true, handled: true });

    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_subscriptions/i),
      expect.arrayContaining(['test', 'sub_123', 'cus_123', 'organization', 'org_123', 'active'])
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_subscriptions/i),
      expect.arrayContaining([true, new Date(1779926400 * 1000)])
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_subscription_items/i),
      expect.arrayContaining(['test', 'si_123', 'sub_123', 'prod_123', 'price_123', 1])
    );
    expect(mockClient.query).toHaveBeenCalledWith('COMMIT');
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.customer_mappings[\s\S]*ON CONFLICT DO NOTHING/i
      ),
      ['stripe', 'test', 'organization', 'org_123', 'cus_123']
    );
  });

  it('syncs existing Stripe subscriptions as unmapped when no billing subject exists', async () => {
    const mockClient = {
      query: vi.fn().mockResolvedValue({ rowCount: 0, rows: [] }),
      release: vi.fn(),
    };
    mockPool.connect.mockResolvedValue(mockClient);
    mockPool.query
      .mockResolvedValueOnce({
        rows: [{ accountId: 'acct_123' }],
      })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rows: [connectedTestConnectionRow] });
    mockProvider.listSubscriptions.mockResolvedValueOnce([
      {
        id: 'sub_existing',
        object: 'subscription',
        customer: 'cus_existing',
        status: 'active',
        current_period_start: null,
        current_period_end: null,
        cancel_at_period_end: false,
        cancel_at: null,
        canceled_at: null,
        trial_start: null,
        trial_end: null,
        latest_invoice: 'in_existing',
        metadata: {},
        items: {
          has_more: true,
          data: [
            {
              id: 'si_existing',
              object: 'subscription_item',
              current_period_start: 1777334400,
              current_period_end: 1779926400,
              quantity: 1,
              metadata: {},
              price: {
                id: 'price_existing',
                product: 'prod_existing',
              },
            },
          ],
        },
      } as unknown as StripeSubscription,
    ]);
    mockProvider.listSubscriptionItems.mockResolvedValueOnce([
      {
        id: 'si_existing',
        object: 'subscription_item',
        current_period_start: 1777334400,
        current_period_end: 1779926400,
        quantity: 1,
        metadata: {},
        price: {
          id: 'price_existing',
          product: 'prod_existing',
        },
      },
      {
        id: 'si_extra',
        object: 'subscription_item',
        current_period_start: 1777248000,
        current_period_end: 1780012800,
        quantity: 2,
        metadata: {},
        price: {
          id: 'price_extra',
          product: 'prod_extra',
        },
      },
    ]);

    await expect(stripeServices.syncPayments({ environment: 'test' })).resolves.toMatchObject({
      results: [
        {
          subscriptions: {
            environment: 'test',
            synced: 1,
            unmapped: 1,
            deleted: 0,
          },
        },
      ],
    });

    expect(mockProvider.listSubscriptions).toHaveBeenCalledTimes(1);
    expect(mockProvider.listCustomers).toHaveBeenCalledTimes(1);
    expect(mockProvider.listSubscriptionItems).toHaveBeenCalledWith('sub_existing');
    expect(mockLogger.warn).not.toHaveBeenCalledWith(
      'Stripe subscription projection is missing InsForge billing subject',
      expect.any(Object)
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_subscriptions/i),
      expect.arrayContaining(['test', 'sub_existing', 'cus_existing', null, null, 'active'])
    );
    const subscriptionInsertCall = mockClient.query.mock.calls.find(([sql]) =>
      /INSERT INTO payments\.stripe_subscriptions/i.test(String(sql))
    );
    expect(subscriptionInsertCall?.[1]).toEqual(
      expect.arrayContaining([new Date(1777248000 * 1000), new Date(1780012800 * 1000)])
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_subscription_items/i),
      expect.arrayContaining([
        'test',
        'si_existing',
        'sub_existing',
        'prod_existing',
        'price_existing',
        1,
      ])
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.stripe_subscription_items/i),
      expect.arrayContaining(['test', 'si_extra', 'sub_existing', 'prod_extra', 'price_extra', 2])
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /DELETE FROM payments\.stripe_subscription_items[\s\S]*NOT \(subscription_item_id = ANY/i
      ),
      ['test', 'sub_existing', ['si_existing', 'si_extra']]
    );
    expect(mockClient.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /DELETE FROM payments\.stripe_subscriptions[\s\S]*NOT \(subscription_id = ANY/i
      ),
      ['test', ['sub_existing']]
    );
  });

  it('lists transactions for an environment and billing subject', async () => {
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          environment: 'test',
          provider: 'stripe',
          type: 'one_time_payment',
          status: 'succeeded',
          subjectType: 'team',
          subjectId: 'team_123',
          providerCustomerId: 'cus_123',
          customerEmailSnapshot: 'buyer@example.com',
          providerReferenceId: 'pi_123',
          providerReferenceType: 'payment_intent',
          amount: '4500',
          amountRefunded: null,
          currency: 'usd',
          description: null,
          paidAt: new Date('2026-04-28T00:00:00.000Z'),
          failedAt: null,
          refundedAt: null,
          providerCreatedAt: new Date('2026-04-28T00:00:00.000Z'),
          createdAt: new Date('2026-04-28T00:00:01.000Z'),
          updatedAt: new Date('2026-04-28T00:00:01.000Z'),
        },
      ],
    });

    await expect(
      stripeServices.listTransactions({
        environment: 'test',
        subjectType: 'team',
        subjectId: 'team_123',
        limit: 25,
      })
    ).resolves.toEqual({
      transactions: [
        {
          environment: 'test',
          provider: 'stripe',
          type: 'one_time_payment',
          status: 'succeeded',
          subjectType: 'team',
          subjectId: 'team_123',
          providerCustomerId: 'cus_123',
          customerEmailSnapshot: 'buyer@example.com',
          providerReferenceId: 'pi_123',
          providerReferenceType: 'payment_intent',
          amount: 4500,
          amountRefunded: null,
          currency: 'usd',
          description: null,
          paidAt: '2026-04-28T00:00:00.000Z',
          failedAt: null,
          refundedAt: null,
          providerCreatedAt: '2026-04-28T00:00:00.000Z',
          createdAt: '2026-04-28T00:00:01.000Z',
          updatedAt: '2026-04-28T00:00:01.000Z',
        },
      ],
    });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/FROM payments\.transactions/i),
      ['test', 'stripe', 'team', 'team_123', 25]
    );
  });

  it('lists mirrored Stripe customers for one environment', async () => {
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          environment: 'test',
          provider: 'stripe',
          providerCustomerId: 'cus_123',
          email: 'buyer@example.com',
          name: 'Buyer Example',
          phone: '+1 555-0100',
          deleted: false,
          metadata: { segment: 'pro' },
          raw: {
            address: { country: 'us' },
            invoice_settings: {
              default_payment_method: {
                card: {
                  brand: 'visa',
                  last4: '4242',
                },
              },
            },
          },
          providerCreatedAt: new Date('2026-05-01T00:00:00.000Z'),
          syncedAt: new Date('2026-05-02T00:00:00.000Z'),
          paymentsCount: 3,
          lastPaymentAt: new Date('2026-05-03T12:30:00.000Z'),
          totalSpend: 4200,
          totalSpendCurrency: 'usd',
        },
      ],
    });

    await expect(
      stripeServices.listCustomers({
        environment: 'test',
        limit: 10,
      })
    ).resolves.toEqual({
      customers: [
        {
          environment: 'test',
          provider: 'stripe',
          providerCustomerId: 'cus_123',
          email: 'buyer@example.com',
          name: 'Buyer Example',
          phone: '+1 555-0100',
          deleted: false,
          metadata: { segment: 'pro' },
          providerCreatedAt: '2026-05-01T00:00:00.000Z',
          syncedAt: '2026-05-02T00:00:00.000Z',
          paymentsCount: 3,
          lastPaymentAt: '2026-05-03T12:30:00.000Z',
          totalSpend: 4200,
          totalSpendCurrency: 'usd',
          paymentMethodBrand: 'visa',
          paymentMethodLast4: '4242',
          countryCode: 'US',
        },
      ],
    });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/FROM payments\.customers/i),
      ['test', 10, 'stripe']
    );
  });

  it('lists mirrored Stripe customers without live Stripe enrichment', async () => {
    mockPool.query.mockResolvedValueOnce({
      rows: [
        {
          environment: 'test',
          provider: 'stripe',
          providerCustomerId: 'cus_sparse',
          email: 'sparse@example.com',
          name: 'Sparse Customer',
          phone: null,
          deleted: false,
          metadata: {},
          raw: {},
          providerCreatedAt: new Date('2026-05-01T00:00:00.000Z'),
          syncedAt: new Date('2026-05-02T00:00:00.000Z'),
          paymentsCount: 0,
          lastPaymentAt: null,
          totalSpend: null,
          totalSpendCurrency: null,
        },
      ],
    });

    await expect(
      stripeServices.listCustomers({
        environment: 'test',
        limit: 10,
      })
    ).resolves.toEqual({
      customers: [
        {
          environment: 'test',
          provider: 'stripe',
          providerCustomerId: 'cus_sparse',
          email: 'sparse@example.com',
          name: 'Sparse Customer',
          phone: null,
          deleted: false,
          metadata: {},
          providerCreatedAt: '2026-05-01T00:00:00.000Z',
          syncedAt: '2026-05-02T00:00:00.000Z',
          paymentsCount: 0,
          lastPaymentAt: null,
          totalSpend: null,
          totalSpendCurrency: null,
          paymentMethodBrand: null,
          paymentMethodLast4: null,
          countryCode: null,
        },
      ],
    });

    expect(mockGetSecretByKey).not.toHaveBeenCalled();
  });

  it('lists subscriptions with their subscription items', async () => {
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            subscriptionId: 'sub_123',
            customerId: 'cus_123',
            subjectType: 'organization',
            subjectId: 'org_123',
            status: 'active',
            currentPeriodStart: new Date('2026-04-28T00:00:00.000Z'),
            currentPeriodEnd: new Date('2026-05-28T00:00:00.000Z'),
            cancelAtPeriodEnd: false,
            cancelAt: null,
            canceledAt: null,
            trialStart: null,
            trialEnd: null,
            latestInvoiceId: 'in_123',
            metadata: { plan: 'pro' },
            syncedAt: new Date('2026-04-28T00:00:02.000Z'),
            createdAt: new Date('2026-04-28T00:00:01.000Z'),
            updatedAt: new Date('2026-04-28T00:00:02.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            subscriptionItemId: 'si_123',
            subscriptionId: 'sub_123',
            productId: 'prod_123',
            priceId: 'price_123',
            quantity: '1',
            metadata: {},
            createdAt: new Date('2026-04-28T00:00:01.000Z'),
            updatedAt: new Date('2026-04-28T00:00:02.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.listSubscriptions({
        environment: 'test',
        subjectType: 'organization',
        subjectId: 'org_123',
        limit: 10,
      })
    ).resolves.toMatchObject({
      subscriptions: [
        {
          environment: 'test',
          subscriptionId: 'sub_123',
          subjectType: 'organization',
          subjectId: 'org_123',
          status: 'active',
          items: [
            {
              subscriptionItemId: 'si_123',
              productId: 'prod_123',
              priceId: 'price_123',
              quantity: 1,
            },
          ],
        },
      ],
    });
  });

  it('records mirrored Stripe customers from customer.updated webhooks', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_customer_123',
      type: 'customer.updated',
      created: 1777334700,
      livemode: false,
      data: {
        object: {
          id: 'cus_123',
          object: 'customer',
          email: 'buyer@example.com',
          name: 'Buyer Example',
          phone: '+1 555-0100',
          metadata: { segment: 'pro' },
          created: 1777334400,
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_customer_123',
            eventType: 'customer.updated',
            livemode: false,
            accountId: null,
            objectType: 'customer',
            objectId: 'cus_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-05-02T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-05-02T00:00:00.000Z'),
            updatedAt: new Date('2026-05-02T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_customer_123',
            eventType: 'customer.updated',
            livemode: false,
            accountId: null,
            objectType: 'customer',
            objectId: 'cus_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-05-02T00:00:00.000Z'),
            processedAt: new Date('2026-05-02T00:00:01.000Z'),
            createdAt: new Date('2026-05-02T00:00:00.000Z'),
            updatedAt: new Date('2026-05-02T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_customer_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({
      received: true,
      handled: true,
      event: {
        eventId: 'evt_customer_123',
        processingStatus: 'processed',
      },
    });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.customers/i),
      [
        'test',
        'cus_123',
        'buyer@example.com',
        'Buyer Example',
        '+1 555-0100',
        false,
        { segment: 'pro' },
        expect.objectContaining({ id: 'cus_123' }),
        new Date('2026-04-28T00:00:00.000Z'),
        expect.any(Date),
        false,
      ]
    );
  });

  it('clears Stripe customer mappings when customer.deleted webhooks arrive', async () => {
    mockGetSecretByKey
      .mockResolvedValueOnce('whsec_test_123')
      .mockResolvedValueOnce('sk_test_1234567890');
    mockProvider.constructWebhookEvent.mockReturnValueOnce({
      id: 'evt_customer_deleted_123',
      type: 'customer.deleted',
      created: 1777334700,
      livemode: false,
      data: {
        object: {
          id: 'cus_deleted_123',
          object: 'customer',
          deleted: true,
        },
      },
    });
    mockPool.query
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_customer_deleted_123',
            eventType: 'customer.deleted',
            livemode: false,
            accountId: null,
            objectType: 'customer',
            objectId: 'cus_deleted_123',
            processingStatus: 'pending',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-05-02T00:00:00.000Z'),
            processedAt: null,
            createdAt: new Date('2026-05-02T00:00:00.000Z'),
            updatedAt: new Date('2026-05-02T00:00:00.000Z'),
          },
        ],
      })
      .mockResolvedValueOnce({ rows: [] })
      .mockResolvedValueOnce({ rowCount: 1, rows: [] })
      .mockResolvedValueOnce({
        rows: [
          {
            environment: 'test',
            eventId: 'evt_customer_deleted_123',
            eventType: 'customer.deleted',
            livemode: false,
            accountId: null,
            objectType: 'customer',
            objectId: 'cus_deleted_123',
            processingStatus: 'processed',
            attemptCount: 1,
            lastError: null,
            receivedAt: new Date('2026-05-02T00:00:00.000Z'),
            processedAt: new Date('2026-05-02T00:00:01.000Z'),
            createdAt: new Date('2026-05-02T00:00:00.000Z'),
            updatedAt: new Date('2026-05-02T00:00:01.000Z'),
          },
        ],
      });

    await expect(
      stripeServices.handleStripeWebhook(
        'test',
        Buffer.from('{"id":"evt_customer_deleted_123"}'),
        'sig_123'
      )
    ).resolves.toMatchObject({
      received: true,
      handled: true,
      event: {
        eventId: 'evt_customer_deleted_123',
        processingStatus: 'processed',
      },
    });

    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.customers/i),
      [
        'test',
        'cus_deleted_123',
        null,
        null,
        null,
        true,
        {},
        expect.objectContaining({ id: 'cus_deleted_123', deleted: true }),
        null,
        expect.any(Date),
        true,
      ]
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/DELETE FROM payments\.customer_mappings/i),
      ['test', 'cus_deleted_123']
    );
  });
});
