Skip to content

feat(platform): per-product seat-billing client + shared SeatPaywall#65

Merged
drewstone merged 1 commit into
mainfrom
feat/seat-billing-client
Jun 15, 2026
Merged

feat(platform): per-product seat-billing client + shared SeatPaywall#65
drewstone merged 1 commit into
mainfrom
feat/seat-billing-client

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

The client half of per-product seat billing (per docs/agent-shell-consolidation sibling spec seat-billing-design.md). The platform half is a separate PR in agent-dev-container; both behind SEAT_BILLING_ENABLED (default OFF, fail-open) so nothing changes live until flipped.

What it adds (agent-app, additive, v0.17.0)

  • /platform: getProductEntitlement(http, userApiKey, productId, flag) (GET /v1/billing/product-entitlement, user bearer, unwraps {success,data}, fails OPEN to hasSeat:true when the key/flag/endpoint is absent so consumers don't break pre-rollout); isProductEntitled(ent) = hasSeat || onFreeTier; seatCheckoutUrl(baseUrl, productId); isSeatBillingEnabled(); FREE_TIER_SPEND_CAP_USD=2. Types ProductEntitlement/SeatStatus.
  • /web-react: SeatPaywall component — "Unlock — $100/mo · includes $50/mo of AI usage" + checkout CTA. Allowance shown as a benefit, never the ratio/"margin" (design §6.8).

Contract (matches the platform half)

GET /v1/billing/product-entitlement?product=<id>{ success, data: { seatStatus, currentPeriodEnd, lifetimeSpentUsd, hasSeat, onFreeTier } }, user bearer. hasSeat/onFreeTier computed server-side (free tier = !hasSeat && lifetimeSpent < $2, no earmarking). Checkout at <baseUrl>/app/billing/seat/checkout?product=<id> (single $100 price + metadata.productId).

~2-line product adoption

const ent = await getProductEntitlement(http, userApiKey, PRODUCT_ID, isSeatBillingEnabled())
if (!isProductEntitled(ent)) return <SeatPaywall product='GTM' onCheckout={() => location.assign(http.seatCheckoutUrl(PRODUCT_ID))} />

Verified

Rebased onto current main (v0.16.1 → 0.17.0). pnpm build clean; seat-billing tests 15/15. Merges clean.

Adds the consumer side of per-product seat billing so the five products
(gtm / creative / tax / legal / insurance) each adopt in ~2 lines.

platform/billing.ts:
- getProductEntitlement(http, key, productId, flag) — reads
  GET /v1/billing/product-entitlement?product=<id> (user bearer), same
  transport as readTangleTierState/getBalance. Fails OPEN (hasSeat:true)
  on absent key, flag off, or unreachable endpoint so consumers never
  break pre-rollout.
- isProductEntitled(ent) = hasSeat || onFreeTier — the one gate predicate.
- seatCheckoutUrl(baseUrl, productId) + http.seatCheckoutUrl(productId)
  for the shared $100/mo seat, mirroring billingUrl().
- isSeatBillingEnabled() reads SEAT_BILLING_ENABLED, default OFF.
- ProductEntitlement / SeatStatus types; free tier gates on cumulative
  lifetime spend (computed platform-side).

web-react/seat-paywall.tsx:
- SeatPaywall { product, onCheckout } — "Unlock <Product> — $100/mo,
  includes $50/mo of AI usage". Allowance shown as a benefit; never the
  ratio or the word margin (design §6.8). Tailwind tokens, inline SVG.

Version 0.16.0 -> 0.17.0 (additive). Flag default OFF, fail-open.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved PR — 20094331

Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-15T10:54:12Z

@drewstone drewstone force-pushed the feat/seat-billing-client branch from 2009433 to 4127d28 Compare June 15, 2026 10:54

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved PR — 4127d281

Blanket team auto-approval is enabled for this reviewer service.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: blanket_auto_approve · 2026-06-15T10:54:57Z

@drewstone drewstone merged commit b2b833c into main Jun 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants