Skip to content

feat: pymthouse plans + subscribe checkout - #8

Open
eliteprox wants to merge 14 commits into
feat/pymthouse-integrationfrom
feat/pymthouse-plans-subscribe
Open

feat: pymthouse plans + subscribe checkout#8
eliteprox wants to merge 14 commits into
feat/pymthouse-integrationfrom
feat/pymthouse-plans-subscribe

Conversation

@eliteprox

@eliteprox eliteprox commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add GET /api/pymthouse/plans and POST /api/pymthouse/subscribe BFF routes via @pymthouse/builder-sdk listBillingProducts / createBillingCheckout
  • Surface a Plans panel on Usage so we can exercise end-user checkout against pymthouse before relying on NaaP
  • Pins @pymthouse/builder-sdk to published 0.6.3

Test plan

  • With pymthouse M2M + PYMTHOUSE_PUBLIC_CLIENT_ID set, open /usage and confirm active plans load
  • Click Subscribe and confirm redirect to Stripe Checkout URL
  • pnpm typecheck / pnpm lint

Stacked on: #3

Wire BFF routes and PlansPanel through builder-sdk createBillingCheckout
so dashboard can exercise end-user checkout against pymthouse before NaaP.
@eliteprox
eliteprox requested a review from adamsoffer as a code owner August 6, 2026 02:30
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard Error Error Aug 11, 2026 3:13am

Request Review

builder-sdk#50 merged and published; drop the git branch pin and the
onlyBuiltDependencies workaround needed for prepare-on-install.
@eliteprox

Copy link
Copy Markdown
Collaborator Author

Pinned to published @pymthouse/builder-sdk@0.6.2 now that pymthouse/builder-sdk#50 is merged.

Resolve dep conflict on the stacked base; keep createBillingCheckout
plus the 0.6.3 Sonar/CI security patch.
@eliteprox

Copy link
Copy Markdown
Collaborator Author

Synced with #3 and bumped pin to @pymthouse/builder-sdk@0.6.3.

Add merchant card controls and invoice links through the billing BFF so users
can manage their payment method and access Connect-hosted invoices.
Wire useBillingAccount into BillingSection: live payment method list
with Set-as-default and Remove (confirm) row actions, live invoice
table with View/PDF links resolved via the Stripe hosted-url endpoint,
and updated copy ("Stripe invoices for this account"). Removes static
blur/WIP overlay and fake billing-details block; plan section stays
static pending subscribe-flow work.
Keep plan actions aligned with the current PymtHouse subscription so active users change plans or resume checkout instead of creating conflicting subscriptions.
useAccountUsage cast every 200 body straight to AccountUsagePayload, so a
malformed response became a value that lied about its type. Consumers then
dereferenced `period.end` and ran BigInt reads over `current`/`balance` and
threw during render — a broken upstream body surfaced as a crashed page
rather than as the error state the hook already has.

Responses are now shape-checked at runtime and malformed bodies route to
that existing error state. The guard covers only the fields consumers
dereference without a fallback; `clientId` and `priorPeriod` are carried by
the type but never read, so they are deliberately unchecked. Error messages
on non-OK responses are likewise read without trusting the body shape.
…oard env

The dashboard kept its own copy of the signer DMZ base in
PYMTHOUSE_SIGNER_URL (plus two aliases) and used it as a fallback whenever
the issuer did not return signer_url. That made the dashboard a second
source of truth for a value the issuer already owns, so a signer move meant
redeploying the dashboard to stay reachable.

The issuer is now the only source: API-key exchange takes signer_url from
the exchange response, and signer sessions resolve it from
GET …/apps/{clientId}/signer/routing, cached for five minutes so the
routing lookup does not add a round trip per session. Locally built
python-gateway --token bundles are dropped in favour of the issuer-built
sdkToken, which already carries the routing. PYMTHOUSE_SIGNER_URL is
removed from .env.example accordingly.

Key creation also no longer reloads the list mid-flight: the caller inserts
the new row and reveals the full key in the same paint, then soft-refreshes,
so a slow list fetch cannot delay or blank the reveal.
The UI trusted any pendingCancel the Builder API returned, but upstream
reports whichever canceled OpenMeter row still occupies the slot — including
one belonging to a plan the user has already switched away from. Billing
therefore showed "Pay as you go" as the current plan while simultaneously
offering to restore a superseded plan, behind a restore button that could
only fail. Cancel/restore affordances now require the pending cancel to
match the live plan, current-plan resolution is single-sourced through
deriveBillingSubscriptionUiState, and a rejected resume reconciles the stale
snapshot instead of stranding an error.

That reconciliation keys on the upstream machine-readable code
`nothing_to_resume`, not on the status class. Treating any 4xx as "nothing
was scheduled" silently swallowed 401/403, unrelated 404s, and the 502
`resume_failed` branch where a resume target existed but the restore threw;
those are real failures and now surface as such.

Cancel and plan change gain explicit timing (immediate, end of period, or a
chosen date), and a scheduled-change conflict prompts before replacing the
existing schedule rather than failing opaquely.

pymthouse-bff gains isUserNotFoundError, accepting both the `code:
"not_found"` envelope and the code-less `{"error":"not_found"}` the
mint-token route returns, so auto-provisioning keeps working across
builder-sdk versions.

Alongside this, the owner wallet surface: prepaid balance, Stripe top-up,
platform invoices, and the auto-debit payment method over Builder M2M. Plan
listing hydrates pay-per-use threshold and behaviour copy from the wallet
summary because the apiVersion=2 product shape omits them, falling back to
generic copy when the wallet API is absent. Allowance copy moves from
"Starter" to "Prepaid" to match. Checkout return URLs prefer a configured
public HTTPS origin, since Stripe rejects most http return URLs.

The settings rail reads its active tab from useSearchParams rather than
window.location, which disagreed between SSR and client and left General
highlighted on a ?tab=billing load until hydration.

Wallet and subscription changes share lib/dashboard/pymthouse-billing-bff.ts
and plan listing calls into the wallet summary, so they ship together rather
than being split by hunk.
Replace soft-limit strip copy with the API-owned posture card (status badge, buffer meter, collection schedule) and align pay-per-use plan wording with progressive invoicing.
- Changed `MockUser.id` to derive from email using SHA-256 for consistency.
- Updated `connect` and `updateUser` methods to return promises for better async handling.
- Enhanced `hydrateMockUser` to support async operations and ensure proper user ID assignment.
- Improved localStorage handling for user sessions and legacy machine IDs.
- Adjusted login page to await connection on email and OAuth submissions.
- Refined comments and documentation for clarity.
- Introduced `formatBillingPlanPrice` to standardize billing plan price formatting, including handling of included usage for Starter plans.
- Updated `isUsagePlan` to account for `isStarterDefault` in plan checks.
- Enhanced `PlansPanel` and `BillingSection` components to support new billing logic, including improved URL handling for plan changes.
- Added `availableRunway` and `overageLimitNote` functions to better represent wallet states and spending limits in the UI.
- Implemented soft error handling for billing list availability in `useBillingAccount`.
- Updated tests to cover new billing functionalities and ensure accurate behavior across components.
…hance billing UI

- Added a new API route for retrieving user subscriptions, ensuring externalUserId is validated.
- Introduced a `TimingChoicePanel` component for selecting subscription change timing options.
- Enhanced `PlansPanel` and `BillingSection` components to support subscription timing changes and conflicts.
- Updated billing state management to include subscription history and error handling for subscription-related actions.
- Refactored existing billing functions to accommodate new subscription logic and improve user experience.
- Changed dependency from local file reference to versioned package for better stability and compatibility.
- Updated pnpm-lock.yaml to reflect the new versioning for @pymthouse/builder-sdk.
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.

1 participant