AkurAI Build
Menu

Public repository / f7056749a17a422909a828ea639ae776c4e020dc

AkurAI-Platform / payment/service.test.ts

Latest change 391a147709e20a60e52fffcc611f3b082def927d - fix: keep workspace provisioning pending by Ólafur Búi Ólafsson

import { afterEach, describe, expect, test } from "bun:test";
import { createHmac } from "node:crypto";
import { mkdtempSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { createPaymentService, type PaymentProvider, type PaymentService, type RecoveryDelivery } from "./service";
import { nextBillingDate, quoteCheckout, validateCatalog, type Catalog } from "./core";

const catalog: Catalog = validateCatalog({
  currency: "EUR",
  monthly_unit_cents: 900,
  annual_discount_percent: 15,
  products: [
    { id: "mail", name: "AkurAI Mail", category: "Communication", purchasable: true, bundle: [] },
    { id: "passvault", name: "AkurAI PassVault", category: "Security", purchasable: true, bundle: [] },
    { id: "crm", name: "AkurAI CRM", category: "Business", purchasable: false, bundle: [], available: false },
  ],
});

let directory: string | null = null;
let service: PaymentService | null = null;

afterEach(() => {
  service?.close();
  service = null;
  if (directory) rmSync(directory, { recursive: true, force: true });
  directory = null;
});

const identitySecret = "test-identity-secret";
const proxySecret = "test-proxy-secret";

function signedIdentityHeaders(userId: string): Record<string, string> {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const signature = createHmac("sha256", proxySecret).update(`${timestamp}.${userId}`).digest("base64url");
  return {
    "x-akurai-user-id": userId,
    "x-akurai-identity-timestamp": timestamp,
    "x-akurai-identity-signature": signature,
  };
}

function identityCredential(userId: string): string {
  const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url");
  const header = encode({ alg: "HS256", typ: "JWT" });
  const payload = encode({ sub: userId, exp: Math.floor(Date.now() / 1000) + 3600 });
  const signature = createHmac("sha256", identitySecret).update(`${header}.${payload}`).digest("base64url");
  return `${header}.${payload}.${signature}`;
}

function createService(
  now: () => Date = () => new Date("2026-07-17T12:00:00Z"),
  paymentProvider?: PaymentProvider,
  recoveryDelivery?: RecoveryDelivery,
): PaymentService {
  directory = mkdtempSync(join(tmpdir(), "akurai-payments-test-"));
  service = createPaymentService({
    catalog,
    dbPath: join(directory, "payments.db"),
    publicUrl: "http://payments.test",
    env: {
      PAYMENTS_PROVIDER: "mock", PAYMENTS_IDENTITY_SECRET: identitySecret, PAYMENTS_TRUSTED_PROXY_SECRET: proxySecret,
      ...(paymentProvider?.name === "mypos" ? { PAYMENTS_TOKEN_KEY: Buffer.alloc(32, 3).toString("base64") } : {}),
    },
    paymentProvider,
    recoveryDelivery,
    now,
  });
  return service;
}
async function checkout(
  app: PaymentService,
  interval: "monthly" | "annual" = "monthly",
  productIds = ["mail"],
  userId?: string,
): Promise<{ orderId: string; manageToken: string; amountCents: number }> {
  const response = await app.fetch(new Request("http://payments.test/api/payments/checkout", {
    method: "POST",
    headers: { "content-type": "application/json", ...(userId ? { authorization: `Bearer ${identityCredential(userId)}` } : {}) },
    body: JSON.stringify({
      productIds,
      interval,
      customer: { name: "Mock Customer", email: "mock@example.test" },
    }),
  }));
  expect(response.status).toBe(201);
  return response.json();
}

describe("subscription pricing", () => {
  test("locks monthly and discounted annual totals on the server", () => {
    const monthly = quoteCheckout(catalog, {
      productIds: ["mail", "passvault"],
      interval: "monthly",
      customer: { name: "Customer", email: "customer@example.test" },
    });
    const annual = quoteCheckout(catalog, {
      productIds: ["mail", "passvault"],
      interval: "annual",
      customer: { name: "Customer", email: "customer@example.test" },
    });
    expect(monthly.amountCents).toBe(1_800);
    expect(annual.amountCents).toBe(18_360);
    expect(() => quoteCheckout(catalog, {
      productIds: ["crm"],
      interval: "monthly",
      customer: { name: "Customer", email: "customer@example.test" },
    })).toThrow("unavailable");
  });

  test("preserves end-of-month billing dates", () => {
    expect(nextBillingDate(new Date("2026-01-31T12:00:00Z"), "monthly").toISOString()).toBe("2026-02-28T12:00:00.000Z");
    expect(nextBillingDate(new Date("2024-02-29T12:00:00Z"), "annual").toISOString()).toBe("2025-02-28T12:00:00.000Z");
  });
});

describe("mock checkout", () => {
  test("uses the hosted redirect flow, activates, exposes management, and cancels", async () => {
    const app = createService();
    const checkoutResult = await checkout(app, "annual", ["mail", "passvault"]);
    expect(checkoutResult.amountCents).toBe(18_360);

    const hosted = await app.fetch(new Request(`http://payments.test/api/payments/mock/checkout/${checkoutResult.orderId}`));
    expect(hosted.status).toBe(200);
    expect(await hosted.text()).toContain("Approve test payment");

    const approval = await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${checkoutResult.orderId}/approve`, { method: "POST" }));
    expect(approval.status).toBe(303);

    const order = await (await app.fetch(new Request(`http://payments.test/api/payments/orders/${checkoutResult.orderId}`))).json();
    expect(order.status).toBe("paid");
    expect(order.amountCents).toBe(18_360);

    const managed = await (await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${checkoutResult.manageToken}`))).json();
    expect(managed.status).toBe("active");
    expect(managed.email).toBe("m***@example.test");
    expect(managed.products.map((product: { id: string }) => product.id)).toEqual(["mail", "passvault"]);

    const canceled = await (await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${checkoutResult.manageToken}?action=cancel`, {
      method: "POST",
      headers: { accept: "application/json" },
    }))).json();
    expect(canceled.status).toBe("canceled");
    expect(canceled.retentionDeadline).toBeTruthy();
  });

  test("recovers management access through customer email without persisting a failed rotation", async () => {
    const deliveries: string[] = [];
    let fail = true;
    const app = createService(undefined, undefined, {
      async send({ managementUrl }) {
        if (fail) throw new Error("mailbox_unavailable");
        deliveries.push(managementUrl);
      },
    });
    const checkoutResult = await checkout(app);
    await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${checkoutResult.orderId}/approve`, { method: "POST" }));
    const recover = () => app.fetch(new Request("http://payments.test/api/payments/recovery", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ email: "mock@example.test" }),
    }));

    expect((await recover()).status).toBe(202);
    expect((await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${checkoutResult.manageToken}`))).status).toBe(200);
    fail = false;
    expect((await recover()).status).toBe(202);
    expect(deliveries).toHaveLength(1);
    expect((await app.fetch(new Request(`http://payments.test${new URL(deliveries[0]).pathname}`))).status).toBe(200);
    expect((await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${checkoutResult.manageToken}`))).status).toBe(404);
  });

  test("accepts myPOS notify replay, records payment-method-required, and rolls back", async () => {
    let notification: Record<string, unknown> = {};
    const provider: PaymentProvider & { validateWebhook(form: FormData): Record<string, unknown> } = {
      name: "mypos",
      async begin(order) { return { redirectUrl: `http://payments.test/mypos/${order.id}` }; },
      async renew() { return { success: true }; },
      async recover() { return null; },
      validateWebhook() { return notification; },
    };
    const app = createService(undefined, provider);
    const result = await checkout(app);
    notification = {
      isValid: true, orderId: result.orderId, amount: 9, currency: "EUR",
      method: "IPCPurchaseNotify", cardToken: undefined, transactionRef: "notify-1",
    };
    expect((await app.fetch(new Request("http://payments.test/api/payments/webhook", { method: "POST", body: new FormData() }))).status).toBe(200);
    expect((await app.fetch(new Request("http://payments.test/api/payments/webhook", { method: "POST", body: new FormData() }))).status).toBe(200);
    expect((await (await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${result.manageToken}`))).json()).status).toBe("requires_payment_method");
    notification = { ...notification, method: "IPCPurchaseRollback", transactionRef: "rollback-1" };
    expect((await app.fetch(new Request("http://payments.test/api/payments/webhook", { method: "POST", body: new FormData() }))).status).toBe(200);
    expect((await (await app.fetch(new Request(`http://payments.test/api/payments/orders/${result.orderId}`))).json()).status).toBe("rolled_back");
  });

  test("moves failed renewals through past_due, unpaid, and a cancellation race without charging", async () => {
    let renewCalls = 0;
    const provider: PaymentProvider = {
      name: "mock",
      async begin(order) { return { redirectUrl: `http://payments.test/api/payments/mock/checkout/${order.id}` }; },
      async renew() { renewCalls += 1; return { success: false, errorCode: "declined" }; },
      async recover() { return null; },
    };
    let current = new Date("2026-01-31T12:00:00Z");
    const app = createService(() => new Date(current), provider);
    const result = await checkout(app);
    await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${result.orderId}/approve`, { method: "POST" }));
    current = new Date("2026-03-01T12:00:00Z");
    await app.runRenewals();
    expect((await (await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${result.manageToken}`))).json()).status).toBe("past_due");
    current = new Date("2026-03-02T12:00:00Z");
    await app.runRenewals();
    current = new Date("2026-03-05T12:00:00Z");
    await app.runRenewals();
    expect((await (await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${result.manageToken}`))).json()).status).toBe("unpaid");

    const paid = await checkout(app);
    await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${paid.orderId}/approve`, { method: "POST" }));
    await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${paid.manageToken}?action=cancel`, { method: "POST" }));
    const before = renewCalls;
    current = new Date("2026-04-01T12:00:00Z");
    await app.runRenewals();
    expect(renewCalls).toBe(before);
  });

  test("renews across token-key overlap and re-encrypts for the new key", async () => {
    const oldKey = Buffer.alloc(32, 1).toString("base64");
    const newKey = Buffer.alloc(32, 2).toString("base64");
    let current = new Date("2026-01-31T12:00:00Z");
    directory = mkdtempSync(join(tmpdir(), "akurai-payments-key-rotation-test-"));
    service = createPaymentService({
      catalog, dbPath: join(directory, "payments.db"), publicUrl: "http://payments.test",
      env: { PAYMENTS_PROVIDER: "mock", PAYMENTS_TOKEN_KEY: oldKey, PAYMENTS_TOKEN_KEY_ID: "old" }, now: () => new Date(current),
    });
    const result = await checkout(service);
    await service.fetch(new Request(`http://payments.test/api/payments/mock/orders/${result.orderId}/approve`, { method: "POST" }));
    service.close();
    service = createPaymentService({
      catalog, dbPath: join(directory, "payments.db"), publicUrl: "http://payments.test",
      env: {
        PAYMENTS_PROVIDER: "mock", PAYMENTS_TOKEN_KEY: newKey, PAYMENTS_TOKEN_KEY_ID: "new",
        PAYMENTS_TOKEN_KEY_PREVIOUS: oldKey, PAYMENTS_TOKEN_KEY_PREVIOUS_ID: "old",
      }, now: () => new Date(current),
    });
    current = new Date("2026-03-01T12:00:00Z");
    await service.runRenewals();
    service.close();
    service = createPaymentService({
      catalog, dbPath: join(directory, "payments.db"), publicUrl: "http://payments.test",
      env: { PAYMENTS_PROVIDER: "mock", PAYMENTS_TOKEN_KEY: newKey, PAYMENTS_TOKEN_KEY_ID: "new" }, now: () => new Date(current),
    });
    current = new Date("2026-04-01T12:00:00Z");
    await service.runRenewals();
    expect((await (await service.fetch(new Request(`http://payments.test/api/payments/subscriptions/${result.manageToken}`))).json()).status).toBe("active");
  });

  test("deduplicates checkout retries and throttles burst creation", async () => {
    const app = createService();
    const body = JSON.stringify({
      productIds: ["mail"], interval: "monthly",
      customer: { name: "Mock Customer", email: "mock@example.test" },
    });
    const request = (key: string) => new Request("http://payments.test/api/payments/checkout", {
      method: "POST", headers: { "content-type": "application/json", "Idempotency-Key": key }, body,
    });
    const first = await app.fetch(request("retry-1"));
    const second = await app.fetch(request("retry-1"));
    expect(first.status).toBe(201);
    expect(second.status).toBe(200);
    expect((await first.json()).orderId).toBe((await second.json()).orderId);
    await app.fetch(request("retry-2"));
    await app.fetch(request("retry-3"));
    await app.fetch(request("retry-4"));
    expect((await app.fetch(request("retry-5"))).status).toBe(429);
  });

  test("records a declined mock payment without creating a subscription", async () => {
    const app = createService();
    const checkoutResult = await checkout(app);
    const decline = await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${checkoutResult.orderId}/decline`, { method: "POST" }));
    expect(decline.status).toBe(303);
    const order = await (await app.fetch(new Request(`http://payments.test/api/payments/orders/${checkoutResult.orderId}`))).json();
    expect(order.status).toBe("failed");
    const managed = await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${checkoutResult.manageToken}`));
    expect(managed.status).toBe(404);
  });

  test("renews a due subscription automatically at its locked price", async () => {
    let current = new Date("2026-01-31T12:00:00Z");
    const app = createService(() => new Date(current));
    const checkoutResult = await checkout(app);
    await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${checkoutResult.orderId}/approve`, { method: "POST" }));

    current = new Date("2026-03-01T12:00:00Z");
    await app.runRenewals();

    const managed = await (await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${checkoutResult.manageToken}`))).json();
    expect(managed.status).toBe("active");
    expect(managed.amountCents).toBe(900);
    expect(managed.nextChargeAt).toBe("2026-03-31T12:00:00.000Z");
  });

  test("rejects client attempts to select unavailable products", async () => {
    const app = createService();
    const response = await app.fetch(new Request("http://payments.test/api/payments/checkout", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        productIds: ["crm"],
        interval: "monthly",
        customer: { name: "Mock Customer", email: "mock@example.test" },
      }),
    }));
    expect(response.status).toBe(400);
    expect(await response.json()).toEqual({ error: "One or more selected apps are unavailable" });
  });

  test("charges a renewal once when executions overlap", async () => {
    let renewCalls = 0;
    const provider: PaymentProvider = {
      name: "mock",
      async begin(order) {
        return { redirectUrl: `http://payments.test/api/payments/mock/checkout/${order.id}` };
      },
      async renew(order) {
        renewCalls += 1;
        const { promise, resolve } = Promise.withResolvers<void>();
        queueMicrotask(resolve);
        await promise;
        return { success: true, transactionRef: `renewed_${order.id}` };
      },
      async recover() {
        return null;
      },
    };
    let current = new Date("2026-01-31T12:00:00Z");
    const app = createService(() => new Date(current), provider);
    const checkoutResult = await checkout(app);
    await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${checkoutResult.orderId}/approve`, { method: "POST" }));
    current = new Date("2026-03-01T12:00:00Z");
    const app2 = createPaymentService({
      catalog,
      dbPath: join(directory!, "payments.db"),
      publicUrl: "http://payments.test",
      env: { PAYMENTS_PROVIDER: "mock", PAYMENTS_IDENTITY_SECRET: identitySecret, PAYMENTS_TRUSTED_PROXY_SECRET: proxySecret },
      paymentProvider: provider,
      now: () => new Date(current),
    });
    try {
      await Promise.all([app.runRenewals(), app2.runRenewals()]);
      expect(renewCalls).toBe(1);
    } finally {
      app2.close();
    }
  });
});
 
describe("disabled payments", () => {
  test("keeps the organization domain available without exposing mock checkout", async () => {
    directory = mkdtempSync(join(tmpdir(), "akurai-payments-disabled-test-"));
    service = createPaymentService({
      catalog,
      dbPath: join(directory, "payments.db"),
      publicUrl: "https://platform.example.test",
      env: { PAYMENTS_PROVIDER: "disabled", PAYMENTS_IDENTITY_SECRET: identitySecret },
    });

    const health = await (await service.fetch(new Request("https://platform.example.test/api/payments/health"))).json();
    expect(health).toMatchObject({ ok: true, provider: "disabled", paymentsEnabled: false });

    const created = await service.fetch(new Request("https://platform.example.test/api/organizations", {
      method: "POST",
      headers: { "content-type": "application/json", authorization: `Bearer ${identityCredential("admin")}` },
      body: JSON.stringify({ name: "Acme" }),
    }));
    expect(created.status).toBe(201);

    const checkoutResponse = await service.fetch(new Request("https://platform.example.test/api/payments/checkout", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        productIds: ["mail"],
        interval: "monthly",
        customer: { name: "Customer", email: "customer@example.test" },
      }),
    }));
    expect(checkoutResponse.status).toBe(503);
    expect(await checkoutResponse.json()).toEqual({ error: "Payments are not configured" });
  });
});

describe("organization domain", () => {
  test("isolates tenants, enforces roles, and keeps idempotent lifecycle state", async () => {
    const app = createService();
    const request = (path: string, init: RequestInit = {}, user = "admin") => new Request(`http://payments.test${path}`, {
      ...init,
      headers: { "content-type": "application/json", authorization: `Bearer ${identityCredential(user)}`, ...(init.headers ?? {}) },
    });
    expect((await app.fetch(new Request("http://payments.test/api/organizations"))).status).toBe(401);
    expect((await app.fetch(new Request("http://payments.test/api/organizations", {
      headers: { "x-akurai-user-id": "admin" },
    }))).status).toBe(401);
    expect((await app.fetch(new Request("http://payments.test/api/organizations", { headers: signedIdentityHeaders("proxy-user") }))).status).toBe(200);
    const created = await (await app.fetch(request("/api/organizations", {
      method: "POST",
      body: JSON.stringify({ name: "Acme", workspaceName: "Acme Managed" }),
    }))).json();
    expect(created.organization.organizationId).toMatch(/^org_/);
    expect(created.workspace.workspaceId).toMatch(/^ws_/);
    expect(created.membership.role).toBe("admin");
    const organizationId = created.organization.organizationId;
    const workspaceId = created.workspace.workspaceId;
    const member = await (await app.fetch(request(`/api/organizations/${organizationId}/members`, {
      method: "POST",
      body: JSON.stringify({ userId: "member", role: "member" }),
    }))).json();
    expect(member.membership.role).toBe("member");
    const denied = await app.fetch(request(`/api/organizations/${organizationId}/workspaces`, {
      method: "POST",
      body: JSON.stringify({ name: "Denied" }),
    }, "member"));
    expect(denied.status).toBe(403);
    const memberRead = await app.fetch(request(`/api/organizations/${organizationId}`, {}, "member"));
    expect(memberRead.status).toBe(200);
    expect((await app.fetch(request(`/api/organizations/${organizationId}/lifecycle`, {}, "member"))).status).toBe(403);
    expect((await app.fetch(request("/api/organizations", {
      method: "POST",
      body: JSON.stringify({ name: "Linked", crmCompanyId: "crm-target" }),
    }))).status).toBe(400);

    const other = await (await app.fetch(request("/api/organizations", {
      method: "POST",
      body: JSON.stringify({ name: "Other" }),
    }, "other"))).json();
    const isolated = await app.fetch(request(`/api/organizations/${other.organization.organizationId}`, {}, "admin"));
    expect(isolated.status).toBe(404);

    const grant = await (await app.fetch(request(`/api/organizations/${organizationId}/entitlements`, {
      method: "POST",
      body: JSON.stringify({ workspaceId, appId: "mail", action: "grant" }),
    }))).json();
    const grantAgain = await (await app.fetch(request(`/api/organizations/${organizationId}/entitlements`, {
      method: "POST",
      body: JSON.stringify({ workspaceId, appId: "mail", action: "grant" }),
    }))).json();
    expect(grant.entitlement.entitlementId).toBe(grantAgain.entitlement.entitlementId);
    const revoked = await (await app.fetch(request(`/api/organizations/${organizationId}/entitlements`, {
      method: "POST",
      body: JSON.stringify({ workspaceId, appId: "mail", action: "revoke" }),
    }))).json();
    expect(revoked.entitlement.status).toBe("revoked");
    const revokedAgain = await (await app.fetch(request(`/api/organizations/${organizationId}/entitlements`, {
      method: "POST",
      body: JSON.stringify({ workspaceId, appId: "mail", action: "revoke" }),
    }))).json();
    expect(revokedAgain.entitlement.entitlementId).toBe(revoked.entitlement.entitlementId);

    const installation = await (await app.fetch(request(`/api/organizations/${organizationId}/installations`, {
      method: "POST",
      body: JSON.stringify({ workspaceId, appId: "mail" }),
    }))).json();
    expect(installation.installation.status).toBe("pending");
    const domain = await (await app.fetch(request(`/api/organizations/${organizationId}/domains`, {
      method: "POST",
      body: JSON.stringify({ workspaceId, hostname: "acme.example.test" }),
    }))).json();
    expect(domain.domain.status).toBe("pending");
    const lifecycle = await (await app.fetch(request(`/api/organizations/${organizationId}/lifecycle`))).json();
    expect(lifecycle.revisions.length).toBeGreaterThanOrEqual(7);
  });

  test("binds paid workspaces to the signed-in owner and verifies export, restore, retention, and deletion", async () => {
    let current = new Date("2026-01-31T12:00:00Z");
    const app = createService(() => new Date(current));
    const payment = await checkout(app, "monthly", ["mail"], "owner");
    await app.fetch(new Request(`http://payments.test/api/payments/mock/orders/${payment.orderId}/approve`, { method: "POST" }));
    const request = (path: string, init: RequestInit = {}) => new Request(`http://payments.test${path}`, {
      ...init,
      headers: { "content-type": "application/json", authorization: `Bearer ${identityCredential("owner")}`, ...(init.headers ?? {}) },
    });
    const organizations = await (await app.fetch(request("/api/organizations"))).json();
    const organizationId = organizations.organizations[0].organizationId;
    const backups = await (await app.fetch(request(`/api/organizations/${organizationId}/backups`))).json();
    expect(backups.backups).toEqual([]);
    const order = await (await app.fetch(new Request(`http://payments.test/api/payments/orders/${payment.orderId}`))).json();
    expect(order.provisioning).toEqual({ status: "entitled", errorCode: null });
    expect(order.ready).toBe(false);
    const exported = await (await app.fetch(request(`/api/organizations/${organizationId}/lifecycle/export`, { method: "POST", body: "{}" }))).json();
    expect(exported.export.integrityHash).toMatch(/^[a-f0-9]{64}$/);
    await app.fetch(new Request(`http://payments.test/api/payments/subscriptions/${payment.manageToken}?action=cancel`, { method: "POST" }));
    expect((await app.fetch(request(`/api/organizations/${organizationId}/lifecycle/restore`, {
      method: "POST", body: JSON.stringify({ exportId: exported.export.id }),
    }))).status).toBe(200);
    current = new Date("2026-05-01T12:00:00Z");
    await app.runRenewals();
    const workspace = await (await app.fetch(request(`/api/organizations/${organizationId}/workspaces`))).json();
    expect(workspace.workspaces[0].status).toBe("deprovisioned");
    const deletion = await (await app.fetch(request(`/api/organizations/${organizationId}/lifecycle/delete-request`, { method: "POST", body: "{}" }))).json();
    expect((await app.fetch(request(`/api/organizations/${organizationId}/lifecycle/delete-confirm`, {
      method: "POST",
      body: JSON.stringify({ deletionId: deletion.deletionId, deleteToken: deletion.deleteToken, confirmation: `DELETE ${organizationId}` }),
    }))).status).toBe(200);
    expect((await app.fetch(request(`/api/organizations/${organizationId}`))).status).toBe(404);
  });

  test("migration preserves payment rows", async () => {
    const app = createService();
    const result = await checkout(app);
    app.close();
    service = createPaymentService({
      catalog,
      dbPath: join(directory!, "payments.db"),
      publicUrl: "http://payments.test",
      env: { PAYMENTS_PROVIDER: "mock" },
      now: () => new Date("2026-07-17T12:00:00Z"),
    });
    const order = await (await service.fetch(new Request(`http://payments.test/api/payments/orders/${result.orderId}`))).json();
    expect(order.id).toBe(result.orderId);
    expect(order.status).toBe("pending");
  });
});