Files
hanzo.ai/e2e/unit/pay.spec.ts
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

99 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test'
import { checkoutUrl, payPage } from '@hanzo/app/lib/pay'
import { checkout } from '../../lib/checkout'
/**
* THE PAY ADDRESS, AND THE ORGANIZATION IT IS NAMED.
*
* pay bills and reads the ledger a request names, and it can only offer the
* organizations a token lists. Which of them this workspace is in is a fact
* this site holds in the browser, so the workspace's billing links carry it as
* `?org=`. The checkout links a marketing page draws carry none: they are
* written while the export prerenders, where there is no browser to ask.
*/
/** A bearer the way IAM signs one; only the middle segment is read. */
function bearer(claims: Record<string, unknown>): string {
const body = Buffer.from(JSON.stringify(claims)).toString('base64url')
return `header.${body}.signature`
}
/** A browser on hanzo.ai holding these entries, for the length of one call. */
function browser(entries: Record<string, string>): () => void {
const global = globalThis as { window?: unknown }
const had = 'window' in global
const was = global.window
const store = new Map(Object.entries(entries))
global.window = { location: { hostname: 'hanzo.ai' }, localStorage: { getItem: (k: string) => store.get(k) ?? null } }
return () => {
if (had) global.window = was
else delete global.window
}
}
const ACCESS = 'hanzo_iam_access_token'
const CHOSEN = 'hanzo_iam_current_org'
test('a billing page carries the one organization the token names', () => {
const done = browser({ [ACCESS]: bearer({ owner: 'hanzo', orgs: [{ org: 'acme', role: 'owner' }] }) })
try {
expect(payPage()).toBe('https://hanzo.ai/pay/?org=acme')
expect(payPage('/usage')).toBe('https://hanzo.ai/pay/usage?org=acme')
} finally {
done()
}
})
test('a billing page carries the organization chosen among several', () => {
const done = browser({
[ACCESS]: bearer({ owner: 'hanzo', orgs: [{ org: 'acme' }, { org: 'beta' }] }),
[CHOSEN]: 'beta',
})
try {
expect(payPage('/invoices')).toBe('https://hanzo.ai/pay/invoices?org=beta')
} finally {
done()
}
})
test('a billing page names no organization it cannot vouch for', () => {
const done = browser({ [ACCESS]: bearer({ owner: 'hanzo' }) })
try {
expect(payPage()).toBe('https://hanzo.ai/pay')
expect(payPage('/usage')).toBe('https://hanzo.ai/pay/usage')
} finally {
done()
}
})
// A plan opens on pay's cart, where a plan and its term are resolved. The root
// forwards a named plan there too, with its term; the cart is one hop fewer.
test('a checkout link opens the cart on the plan', () => {
expect(checkoutUrl()).toBe('https://hanzo.ai/pay')
expect(checkoutUrl('dev')).toBe('https://hanzo.ai/pay/cart?plan=dev')
expect(checkoutUrl('dev', 'https://hanzo.ai/home')).toBe(
'https://hanzo.ai/pay/cart?plan=dev&returnUrl=https%3A%2F%2Fhanzo.ai%2Fhome',
)
expect(checkoutUrl(undefined, 'https://hanzo.ai/home')).toBe(
'https://hanzo.ai/pay/?returnUrl=https%3A%2F%2Fhanzo.ai%2Fhome',
)
})
// A plan is bought on pay's card step (lib/checkout): the plan, a year where one
// is asked for, the seats past one, the coupon, the organization billed, and the
// address pay returns to. A month and one seat are pay's defaults.
test('a plan is bought on pay’s card step, carrying its term, seats, coupon and organization', () => {
const back = 'https://hanzo.ai/chat'
expect(checkout({ plan: 'dev', returnUrl: back })).toBe('/pay/confirm/card?plan=dev&returnUrl=https%3A%2F%2Fhanzo.ai%2Fchat')
expect(checkout({ plan: 'dev', interval: 'month', quantity: 1, returnUrl: back })).toBe(checkout({ plan: 'dev', returnUrl: back }))
expect(checkout({ plan: 'max-5x', interval: 'year', returnUrl: back })).toBe(
'/pay/confirm/card?plan=max-5x&interval=year&returnUrl=https%3A%2F%2Fhanzo.ai%2Fchat',
)
expect(checkout({ plan: 'dev', interval: 'month', coupon: '50OFF', returnUrl: back })).toBe(
'/pay/confirm/card?plan=dev&coupon=50OFF&returnUrl=https%3A%2F%2Fhanzo.ai%2Fchat',
)
expect(checkout({ plan: 'team', interval: 'month', quantity: 3, org: 'acme', returnUrl: back })).toBe(
'/pay/confirm/card?plan=team&quantity=3&org=acme&returnUrl=https%3A%2F%2Fhanzo.ai%2Fchat',
)
})