Mock

Test billing flows without hitting real payment APIs.

Setup

lib/billing.test.ts
import { createEnhancedClient } from "@fuime/billing-sdk";
import { mock } from "@fuime/billing-sdk/mock";

const mockAdapter = mock();

const billing = createEnhancedClient({
  provider: mockAdapter,
  cache: { ttlMs: 60_000 },
  plans: {
    prod_pro: { features: ["api", "export"] },
    prod_enterprise: { features: "*" },
  },
});

Creating test subscriptions

// Add a subscription for testing
mockAdapter.addSubscription({
  userId: "user_123",
  productId: "prod_pro",
  status: "active",
  currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
});

// Now entitlement checks work
const hasAccess = await billing.hasFeature("user_123", "api");
// true

Simulating webhooks

// Trigger a webhook event
const event = mockAdapter.triggerWebhook({
  type: "subscription.updated",
  userId: "user_123",
  productId: "prod_enterprise",
});

// The cache is automatically invalidated

Test scenarios

Test upgrade flow

test("user can upgrade", async () => {
  const mockAdapter = mock();
  const billing = createEnhancedClient({
    provider: mockAdapter,
    plans: {
      prod_free: { features: ["basic"] },
      prod_pro: { features: ["basic", "api", "export"] },
    },
  });

  // Start on free plan
  mockAdapter.addSubscription({
    userId: "user_123",
    productId: "prod_free",
    status: "active",
  });

  expect(await billing.hasFeature("user_123", "api")).toBe(false);

  // Simulate upgrade
  mockAdapter.updateSubscription("user_123", {
    productId: "prod_pro",
  });

  // Clear cache to see new state
  billing.invalidateCache("user_123");

  expect(await billing.hasFeature("user_123", "api")).toBe(true);
});

Test cancellation

test("cancelled user loses access", async () => {
  mockAdapter.addSubscription({
    userId: "user_123",
    productId: "prod_pro",
    status: "active",
  });

  expect(await billing.hasFeature("user_123", "api")).toBe(true);

  // Cancel subscription
  mockAdapter.cancelSubscription("user_123");
  billing.invalidateCache("user_123");

  expect(await billing.hasFeature("user_123", "api")).toBe(false);
});

Resetting state

beforeEach(() => {
  mockAdapter.reset(); // Clears all subscriptions
});