86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
import { buildInvoice, Invoice } from '../src/invoicing';
|
|
import { Customer } from '../src/auth';
|
|
import { TierConfig } from '../src/pricing';
|
|
import { UsageEntry } from '../src/usage';
|
|
|
|
const pro: TierConfig = { id: 'pro', name: 'Pro', multiplier: 0.5, monthlyCreditCents: 1000 };
|
|
|
|
const customer: Customer = {
|
|
id: 'cust_2',
|
|
name: 'Grace',
|
|
tierId: 'pro',
|
|
apiKey: 'key-grace',
|
|
stripeCustomerId: 'cus_123',
|
|
};
|
|
|
|
const entry = (endpointId: string, cents: number): UsageEntry => ({
|
|
customerId: customer.id,
|
|
endpointId,
|
|
cents,
|
|
metadataBytes: 0,
|
|
attachmentBytes: 0,
|
|
timestamp: new Date('2026-07-10T12:00:00Z'),
|
|
});
|
|
|
|
describe('buildInvoice', () => {
|
|
it('groups usage entries into lines per endpoint, sorted', () => {
|
|
const inv = buildInvoice({
|
|
customer,
|
|
period: '2026-07',
|
|
sequence: 1,
|
|
tier: pro,
|
|
entries: [entry('transform', 3), entry('storage', 6), entry('transform', 3)],
|
|
});
|
|
expect(inv.lines).toEqual([
|
|
{ endpointId: 'storage', calls: 1, cents: 6 },
|
|
{ endpointId: 'transform', calls: 2, cents: 6 },
|
|
]);
|
|
expect(inv.totalCents).toBe(12);
|
|
});
|
|
|
|
it('applies a partial monthly credit', () => {
|
|
const inv = buildInvoice({
|
|
customer,
|
|
period: '2026-07',
|
|
sequence: 1,
|
|
tier: pro,
|
|
entries: [entry('storage', 1500)],
|
|
});
|
|
expect(inv.totalCents).toBe(1500);
|
|
expect(inv.creditCents).toBe(1000);
|
|
expect(inv.billableCents).toBe(500);
|
|
});
|
|
|
|
it('floors billable at zero when credit exceeds usage', () => {
|
|
const inv = buildInvoice({
|
|
customer,
|
|
period: '2026-07',
|
|
sequence: 1,
|
|
tier: pro,
|
|
entries: [entry('storage', 400)],
|
|
});
|
|
expect(inv.creditCents).toBe(400);
|
|
expect(inv.billableCents).toBe(0);
|
|
});
|
|
|
|
it('formats the id as INV-<period>-<padded sequence> and starts as draft', () => {
|
|
const inv = buildInvoice({ customer, period: '2026-07', sequence: 7, tier: pro, entries: [] });
|
|
expect(inv.id).toBe('INV-2026-07-0007');
|
|
expect(inv.status).toBe('draft');
|
|
expect(inv.billingType).toBe('stripe');
|
|
});
|
|
|
|
it('carries purchase-order billing fields', () => {
|
|
const poCustomer: Customer = { ...customer, billingType: 'purchase_order' };
|
|
const inv = buildInvoice({
|
|
customer: poCustomer,
|
|
period: '2026-07',
|
|
sequence: 2,
|
|
tier: pro,
|
|
entries: [entry('storage', 2000)],
|
|
poNumber: 'PO-9982',
|
|
});
|
|
expect(inv.billingType).toBe('purchase_order');
|
|
expect(inv.poNumber).toBe('PO-9982');
|
|
});
|
|
});
|