Menu
Public repository / 17ecb7d0341c5db2f4b548f08a16f8a1f3d26e4e
AkurAI-Platform / payment/service.test.ts
Latest change 54864d769f47e4b584afb75e6ecc5b19890c21b4 - Add modular subscriptions with myPOS checkout by Ólafur Búi Ólafsson
import { afterEach, describe, expect, test } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { createPaymentService, type PaymentService } 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" },
{ id: "passvault", name: "AkurAI PassVault", category: "Security" },
{ id: "crm", name: "AkurAI CRM", category: "Business", 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;
});
function createService(now: () => Date = () => new Date("2026-07-17T12:00:00Z")): PaymentService {
directory = mkdtempSync(join(tmpdir(), "akurai-payments-test-"));
service = createPaymentService({
catalog,
dbPath: join(directory, "payments.db"),
publicUrl: "http://payments.test",
upstreamUrl: "http://127.0.0.1:1",
env: { PAYMENTS_PROVIDER: "mock" },
now,
});
return service;
}
async function checkout(app: PaymentService, interval: "monthly" | "annual" = "monthly", productIds = ["mail"]): 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" },
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");
});
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-28T12: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" });
});
});