feat(cloud): ENTITLEMENTS-B2 metadata.orca_plan on Stripe prices is the catalog join key

Neil's Q1 answer: a plan id on the Stripe Price/Product maps to the repo catalog's entitlements.
seatPriceMatch reads price.metadata.orca_plan (after env ids and price history, before lookup key
and product); the catalog sync logs a lookup-key/tag mismatch; stripe-test-setup tags every price.
This commit is contained in:
Neil
2026-11-12 01:44:10 -08:00
parent fe2db1b352
commit c856b945f1
5 changed files with 41 additions and 6 deletions
@@ -1,4 +1,5 @@
// One-time TEST MODE catalog setup: the Team product with monthly + annual seat prices, the
// One-time TEST MODE catalog setup (every Product and Price carries metadata.orca_plan, the join key
// to catalog/catalog.json): the Team product with monthly + annual seat prices, the
// sandbox-seconds meter with its metered overage price, and a Customer Portal configuration
// (monthly <-> annual switch, cancel; seats follow membership). Refuses any key that is not
// sk_test_/rk_test_ and stops if Stripe ever reports a live-mode object. Prints the Worker vars.
@@ -53,7 +54,9 @@ const monthly = await post(
unit_amount: prices.teamMonthly,
recurring: { interval: 'month' },
lookup_key: 'orca_team_seat_monthly',
transfer_lookup_key: true
transfer_lookup_key: true,
// The join key to the catalog (catalog/catalog.json plan id).
metadata: { orca_plan: 'team' }
},
`price:team:month:${suffix}`
)
@@ -65,7 +68,8 @@ const annual = await post(
unit_amount: prices.teamAnnual,
recurring: { interval: 'year' },
lookup_key: 'orca_team_seat_annual',
transfer_lookup_key: true
transfer_lookup_key: true,
metadata: { orca_plan: 'team' }
},
`price:team:year:${suffix}`
)
@@ -95,6 +99,7 @@ const overage = await post(
transform_quantity: { divide_by: OVERAGE_SECONDS_PER_BILLED_UNIT, round: 'up' },
lookup_key: 'orca_sandbox_overage_per_minute',
transfer_lookup_key: true,
metadata: { orca_plan: 'team', orca_role: 'overage' },
nickname: 'Sandbox overage, billed by the minute'
},
`price:overage:minute:${suffix}`
+16 -1
View File
@@ -4,7 +4,8 @@
// - records every seat price a subscription is seen on (price history), so a subscriber on an
// older price keeps mapping to its plan after the lookup key moves to a new price;
// - maps a subscription item's price to a plan + interval: the configured env ids, any price we
// have seen for the plan, a price holding the plan's lookup key, or one on the plan's product.
// have seen for the plan, the price's metadata.orca_plan (the join key every Orca price carries),
// a price holding the plan's lookup key, or one on the plan's product.
import { invalidateCatalog, loadCatalog, type Catalog, type PlanPriceRow } from './catalog'
import type { Env } from './env'
import { BILLING_INTERVALS, PLAN_IDS, planForPrice, type BillingInterval, type PlanId } from './plans'
@@ -64,6 +65,15 @@ export function seatPriceMatch(env: Env, catalog: Catalog | null, price: StripeP
if (!interval) {
return null
}
// The join key: metadata.orca_plan on the Price (Stripe metadata is ours; customers can't edit it).
const tagged = price.metadata?.orca_plan
if (tagged) {
const plan = catalog.plans.get(tagged)
const id = plan?.kind === 'self_serve' ? asPlan(plan.id) : null
if (id) {
return { plan: id, interval }
}
}
for (const plan of catalog.plans.values()) {
const id = asPlan(plan.id)
if (!id || plan.kind !== 'self_serve') {
@@ -193,6 +203,11 @@ export async function syncCatalogPrices(
missing.push(entry.key)
continue
}
if (price.metadata?.orca_plan && price.metadata.orca_plan !== entry.planId) {
// The lookup key says one plan, the price's own tag another: a Stripe setup mistake. The
// lookup key wins for Checkout; say so loudly.
console.error(JSON.stringify({ error: 'catalog_price_plan_mismatch', lookupKey: entry.key, plan: entry.planId, tagged: price.metadata.orca_plan }))
}
statements.push(
priceStatement(db, { price, planId: entry.planId, role: entry.role, interval: entry.interval, checkout: true, setCheckout: true, now }),
// The key moved: the previous holder stays as history only.
+2
View File
@@ -249,6 +249,8 @@ export type StripePrice = {
unit_amount?: number | null
unit_amount_decimal?: string | null
lookup_key?: string | null
/** `orca_plan` names the catalog plan this price sells (the join key, set on every price we create). */
metadata?: Record<string, string> | null
product?: string | { id: string } | null
recurring?: { interval?: string; usage_type?: string } | null
}
+12
View File
@@ -68,6 +68,18 @@ describe('a price change never orphans existing subscribers (planForPrice)', ()
expect(h.raw.prepare("SELECT plan_id, role, interval FROM plan_prices WHERE stripe_price_id = 'price_team_month_v3'").get()).toEqual({ plan_id: 'team', role: 'seat', interval: 'month' })
})
it('maps a price by its metadata.orca_plan join key (no lookup key, unknown product)', async () => {
const h = await createHarness({ ENTITLEMENTS_MODE: 'on' })
h.stripe.prices.set('price_team_tagged', { id: 'price_team_tagged', active: true, currency: 'usd', unit_amount: 4000, product: 'prod_team_promo', metadata: { orca_plan: 'team' }, recurring: { interval: 'year', usage_type: 'licensed' } })
const token = await h.desktopToken({ sub: 'u1', org: 'o1', orgRole: 'owner', email: 'a@example.com' })
const auth = { authorization: `Bearer ${token}` }
await h.call('POST', '/v1/billing/checkout', { body: { plan: 'team', seats: 1 }, headers: auth })
const customer = [...h.stripe.customers.keys()][0]!
const subscription = h.stripe.putSubscription({ customer, accountId: 'org:o1', basePrice: 'price_team_tagged', quantity: 1, periodStart: PERIOD_START, periodEnd: PERIOD_END })
expect((await h.webhook(checkoutCompleted({ customer, subscription: subscription.id, accountId: 'org:o1' }))).body).toEqual({ received: true })
expect(await entitlement(h, auth)).toMatchObject({ plan: 'team', interval: 'year', canStartSandbox: true })
})
it('still applies status for a known subscriber on an unmappable price', async () => {
const { h, auth, subscription } = await subscribed('on')
subscription.items.data[0]!.price = { id: 'price_mystery', recurring: { usage_type: 'licensed' } }
+3 -2
View File
@@ -23,6 +23,7 @@ type FakePrice = {
unit_amount?: number | null
unit_amount_decimal?: string | null
lookup_key?: string | null
metadata?: Record<string, string>
product?: string
recurring: { interval?: string; usage_type: 'licensed' | 'metered' }
}
@@ -38,8 +39,8 @@ type FakeItem = {
/** The test-mode catalog the harness env names (STRIPE_PRICE_*), like stripe-test-setup.ts makes. */
export function defaultFakePrices(): FakePrice[] {
return [
{ id: 'price_team_month', active: true, currency: 'usd', unit_amount: 5000, unit_amount_decimal: '5000', lookup_key: 'orca_team_seat_monthly', product: 'prod_team', recurring: { interval: 'month', usage_type: 'licensed' } },
{ id: 'price_team_year', active: true, currency: 'usd', unit_amount: 48000, unit_amount_decimal: '48000', lookup_key: 'orca_team_seat_annual', product: 'prod_team', recurring: { interval: 'year', usage_type: 'licensed' } },
{ id: 'price_team_month', active: true, currency: 'usd', unit_amount: 5000, unit_amount_decimal: '5000', lookup_key: 'orca_team_seat_monthly', metadata: { orca_plan: 'team' }, product: 'prod_team', recurring: { interval: 'month', usage_type: 'licensed' } },
{ id: 'price_team_year', active: true, currency: 'usd', unit_amount: 48000, unit_amount_decimal: '48000', lookup_key: 'orca_team_seat_annual', metadata: { orca_plan: 'team' }, product: 'prod_team', recurring: { interval: 'year', usage_type: 'licensed' } },
{ id: 'price_overage', active: true, currency: 'usd', unit_amount: null, unit_amount_decimal: '1.666666666667', lookup_key: 'orca_sandbox_overage_per_minute', product: 'prod_overage', recurring: { interval: 'month', usage_type: 'metered' } }
]
}