feat(billing): companion monthly usage subscription for yearly plans (flag off) - #415
Conversation
… flag off by default A second free-base monthly subscription carries the metered prices and monthly meter credits beside a yearly plan, so overage is invoiced monthly. Behind NUXT_POLAR_COMPANION_USAGE (default off).
|
qa review: CHANGES @ 23cc81b (test merge d7725a12 = origin/main 1b39285 + head) The flag is off, so prod is not at risk, but two items in the PR's own spec fail. Blocker (before the flag is enabled)
Major Minor
Before enabling: run the yearly bundle once in the sandbox with a test card. It needs to cover:
Evidence
|
…lace on product switch One conditional claim per workspace decides who opens the companion, so created, updated and reconciler cannot open two. Workspace delete and the plan-cancel webhook now fail when the companion cannot be cancelled. The overage toggle write touches only its own key. A plan moved to another product replaces its companion; an ended one is forgotten on the row.
|
qa re-review: CHANGES @ 3dace87 (test merge a7b158b1 = origin/main 1b39285 + head) Fixed: workspace delete (2), plan-cancel webhook (3), the Blocker A: a companion opened by the reconciler is cancelled and re-created on the next plan webhook.
Blocker B: the created/updated race from finding 1 is still open.
Medium: a companion
Low
Tests: the 4 touched files pass 80/80. The payment-accounts contract test passes 6/6 on a fresh postgres:16. CI: |
…mp is learned The plan created upsert now preserves the companion keys, claim included, so a racing open keeps its claim. A claim stamped without a product by the reconciler is learned on the next plan event instead of cancelling a healthy companion. An ending companion event with none recorded is ignored.
…ed for The provider returns the plan product it read from the plan subscription, so a reconciler-opened companion is stamped with its real product and a switch as the first later plan event is still replaced.
|
ONAY @ ceebcbb Three review rounds; every finding is fixed at this head. Evidence (qa, local): head ceebcbb already contains origin/main 1b39285 (fast-forward, no merge needed).
|
What
Yearly plans bill usage once a year in Polar. This adds a companion subscription: a second, monthly, $0-base subscription that carries the metered prices and monthly meter credits beside the yearly plan sub. Same limits as the monthly tier, usage tracked and reset monthly, overage invoiced monthly (founder rule 2026-10-04).
Flag off by default. With it off nothing changes:
ensureCompanionSubscriptionreturns null, no companion is created, the reconciler is a no-op.Config (env names only)
NUXT_POLAR_COMPANION_USAGE(default false)NUXT_POLAR_STARTER_COMPANION_PRODUCT_ID,NUXT_POLAR_PRO_COMPANION_PRODUCT_IDpolar:syncnow has a step 4/4 that creates the companion products. Dry-run only; not applied to any sandbox/prod from this PR.Design
subscription.created/updatedfor yearly plans (and Migrate bundle year-1 → yearly) when none is recorded. Taggedmetadata.contentrain_companion = 'true'.payment_accounts.plugin_metadata(companion_subscription_id,companion_billable_meters) via single-key writes; plan-subscription upserts preserve both keys (preserveMetadataKeys).payment_accountsplan fields;applyCompanionEventonly updates the metadata keys.yearly_planreason only when nothing is priced.server/plugins/companion-reconciler.ts, 6-hourly) opens missing companions and covers accounts the webhook missed.Tests
Unit: overage-lock (+6), companion plugin, companion util, migrate-revoke (+2). Integration (mocked Polar): new
billing-webhook-companion(10 cases); existing webhook test updated for the extra preserved keys. Typecheck and eslint clean.pnpm test:cigreen with a raised unit timeout; at the default 5s a few unrelated import-heavy files (agent/brain-query/conversation-engine) time out under load and pass alone.Open questions (not proven in the sandbox — no card, cannot advance a billing cycle)
A. Is the companion's overage actually invoiced at cycle end?
B. Is it charged to the card saved at the bundle checkout?
C. Companion beside a paid yearly sub / checkout interplay — only two free subs were tested;
allow_multiple_subscriptionswas not touched.Also unverified: alignment of the yearly and companion billing cycles, and whether the companion invoice uses the bundle card.
Don't merge before qa ONAY. Not to be enabled anywhere until A/B are checked with a test-card yearly-bundle checkout.