Files
orca/cloud/apps/billing/test/harness.ts
T
Neil 51ace4d05f feat(billing): one-time Orca Cloud trial (N11) and its desktop surfaces (T1)
Billing Worker:
- trials + trial_claims (migration 0005): 2 sandbox hours per new team, no card, no Stripe.
- POST /v1/billing/trial grants it (idempotent, any member); grantTrial/trialOffer exported for
  the setup session. offers.trial on GET entitlements only when this caller could still claim it.
- Abuse limits: once per team, Orca user, normalized verified email, GitHub user (token claim)
  and GitHub owner (POST /v1/internal/trial/github from the binding; a reused identity revokes),
  disposable-domain blocklist, 3 grants per IP per day. Keys stored as SHA-256 hashes.
- Entitlement trial {includedSeconds, usedSeconds, remainingSeconds, exhausted}; metered from
  usage_events, concurrency 1, never overage; trial_used_up (suspendRunning) / trial_unavailable.
- Any subscription ends the trial; unused trial hours lapse.
- e2e: trial path (grant, no Stripe customer, concurrency, metering to used up, once per user,
  plan ends it) with cleanup of the run's abuse keys.

Desktop: trial_used_up / trial_unavailable classify as 'plan'; copy in billing, phone and CLI;
"Trial" badge, TrialPlanSummary in Plan & billing and the plans page strip, account menu row
"Trial · {{count}} sandbox hours left" (Choose plan for owners/admins); billingClaimTrial IPC
and claimOrcaCloudTrial() for the composer.
2026-11-10 06:20:22 -08:00

104 lines
4.4 KiB
TypeScript

import { SignJWT, exportJWK, generateKeyPair } from 'jose'
import type { Env } from '../src/env'
import { handleRequest } from '../src/index'
import { createStripeClient } from '../src/stripe'
import type { OrgMemberCheck } from '../src/team-plans'
import { createFakeD1 } from './d1-fake'
import { createFakeStripe, signStripePayload } from './stripe-fake'
import type { TrialPort } from '../src/cloud-setup-trial'
export const WEBHOOK_SECRET = 'whsec_test_local_fixture_secret'
export const CONTROL_TOKEN = 'cp_test_0123456789abcdef0123456789abcdef'
export async function createHarness(overrides: Partial<Env> = {}) {
const { privateKey, publicKey } = await generateKeyPair('ES256')
const jwk = { ...(await exportJWK(publicKey)), kid: `k${Math.random()}`, alg: 'ES256', use: 'sig' }
const { db, raw } = createFakeD1()
const stripe = createFakeStripe()
const env: Env = {
BILLING_DB: db,
STRIPE_SECRET_KEY: 'rk_test_fixture',
STRIPE_WEBHOOK_SECRET: WEBHOOK_SECRET,
STRIPE_API_BASE: 'https://stripe.test',
STRIPE_PRICE_TEAM_MONTHLY: 'price_team_month',
STRIPE_PRICE_TEAM_ANNUAL: 'price_team_year',
STRIPE_PRICE_OVERAGE: 'price_overage',
STRIPE_METER_EVENT_NAME: 'orca_sandbox_seconds',
BILLING_RETURN_URL: 'https://onorca.dev/billing/return',
BILLING_JWKS: JSON.stringify({ keys: [jwk] }),
BILLING_JWT_AUDIENCE: 'orca-billing',
BILLING_AUTH_ISSUER: 'https://login.onorca.dev',
CONTROL_PLANE_TOKEN: CONTROL_TOKEN,
...overrides
}
let now = Date.now()
// Live org member counts from auth; null = not configured (the default).
let membership: (orgId: string) => Promise<number | null> = async () => null
// Auth's live org-member check (cloud start B2); null = auth unavailable (the default).
let orgMember: OrgMemberCheck = async () => null
let trial: TrialPort | undefined
let outboundFetch: typeof fetch | undefined
const waits: Promise<unknown>[] = []
const ctx = { waitUntil: (promise: Promise<unknown>) => waits.push(promise), passThroughOnException() {} } as unknown as ExecutionContext
async function call(method: string, path: string, init: { body?: unknown; rawBody?: string; headers?: Record<string, string> } = {}) {
const request = new Request(`https://billing.test${path}`, {
method,
headers: { 'content-type': 'application/json', ...init.headers },
body: init.rawBody ?? (init.body === undefined ? undefined : JSON.stringify(init.body))
})
const response = await handleRequest(request, env, ctx, {
stripe: createStripeClient(env, stripe.fetchImpl),
now: () => now,
membership: (orgId) => membership(orgId),
orgMember: (orgId, billingToken) => orgMember(orgId, billingToken),
...(trial ? { trial } : {}),
...(outboundFetch ? { fetch: outboundFetch } : {})
})
await Promise.all(waits.splice(0))
return { status: response.status, body: (await response.json()) as Record<string, any> }
}
return {
env,
raw,
stripe,
call,
setNow(value: number) {
now = value
},
setMembership(source: (orgId: string) => Promise<number | null>) {
membership = source
},
setOrgMember(check: OrgMemberCheck) {
orgMember = check
},
setTrial(port: TrialPort) {
trial = port
},
setFetch(impl: typeof fetch) {
outboundFetch = impl
},
async desktopToken(claims: { sub: string; org?: string; orgRole?: string; email?: string; orgMembers?: number; emailVerified?: boolean; githubUserId?: string }, options: { audience?: string; purpose?: string; ageSeconds?: number } = {}) {
const iat = Math.floor(now / 1000) - (options.ageSeconds ?? 0)
return new SignJWT({ ...claims, purpose: options.purpose ?? 'billing' })
.setProtectedHeader({ alg: 'ES256', kid: jwk.kid })
.setIssuer('https://login.onorca.dev')
.setAudience(options.audience ?? 'orca-billing')
.setIssuedAt(iat)
.setExpirationTime(iat + 900)
.sign(privateKey)
},
async webhook(event: unknown, secret = WEBHOOK_SECRET) {
const rawBody = JSON.stringify(event)
return call('POST', '/v1/stripe/webhook', {
rawBody,
headers: { 'stripe-signature': signStripePayload(rawBody, secret, Math.floor(now / 1000)) }
})
},
controlPlane(path: string, body: unknown, token = CONTROL_TOKEN) {
return call('POST', path, { body, headers: { authorization: `Bearer ${token}` } })
}
}
}