Skip to content

JITSU-200: committed contracts in billing UI (console half) - #1490

Merged
absorbb merged 14 commits into
newjitsufrom
feat/jitsu-200-annual-committed-volume
Sep 3, 2026
Merged

JITSU-200: committed contracts in billing UI (console half)#1490
absorbb merged 14 commits into
newjitsufrom
feat/jitsu-200-annual-committed-volume

Conversation

@absorbb

@absorbb absorbb commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Console half of JITSU-200 — committed contracts (e.g. a 12-month commitment invoiced quarterly) in the billing UI.

The server half is jitsucom/jitsu-cloud-billing#40 (this PR matches its head 3085a2a). Nothing here depends on it for existing plans; the only ordering constraint is the quote token below.

Product decision (2026-09-03)

A commitment is a contract term with a fixed monthly quota — no annual pool, no rollover; overage is only ever tracked per month. So the billing contract stays monthly: destinationEvensPerMonth is the quota, currentPeriod is always one month (the Stripe cycle for a plain monthly price, otherwise the contract month anchored on the subscription start), and only expiresAt reflects the commitment term (the contract anniversary for a commitment billed quarterly). An earlier revision of this PR added an annual quota and period; that is gone.

Changes

  • lib/schema/index.ts — new optional commitmentInterval ("year" or absent; typed as a loose string so a Stripe-metadata typo cannot take the billing page down), the contract term from plan_data; the currentPeriod and expiresAt doc comments describe the actual contract (the former still talked about a month "adjusted" for annual subscriptions).
  • components/Billing/use-events-usage.tsperiodStart added to the react-query key so a period rollover refetches instead of serving the previous month from the 5-minute cache; usagePercentage() (new components/Billing/usage-percentage.ts) so a misconfigured plan with a zero quota reports positive usage as exceeded instead of rendering a NaN progress bar and silently skipping the overage warning.
  • components/Billing/BillingManager.tsx — the "Renews at" date is rendered in UTC like every other period date on the page: for a commitment expiresAt is the anniversary at 00:00 UTC, which the viewer's local zone showed as the previous day west of UTC. On a committed contract the date is tagged with the term ("12-month commitment"), since it is the contract end rather than the end of a monthly cycle.
  • pages/custom-plan.tsx — the negotiated-quote page hardcoded "$X /month … Month-to-month, cancel at any time". It now labels the price by the charge cadence (interval x intervalCount: /quarter, /year, …) and states the term from commitmentInterval, so a quarterly-invoiced 12-month deal reads "$X /quarter … N events per month included. 12-month commitment, billed quarterly." Falls back to the older monthly-only API shape. The "Accept Plan" button also forwards the quote token to billing/upgrade: the token used to guard only discovery of a negotiated plan, and Closes #29 added clickhouse single node integration (with MergeTree() table engine) #40 now requires it to check a custom plan out, so anyone learning a plan id can no longer check out at the negotiated price.
  • components/Billing/charge-cadence.ts + __tests__/unit/charge-cadence.test.ts — the two label helpers, 3 cases.

Deploy note

The billing side must not ship ahead of this console change: once #40 enforces the token, "Accept Plan" on custom quotes fails with "Invalid token" until the console forwards it. The extra parameter is ignored by the current billing API, so the console can go first.

Follow-up

The issue's acceptance criterion "configurable in console/admin (not just Stripe free-text)" is only partly met: the values are structured plan_data keys read by the metering code rather than description free-text, but configuring a deal remains a Stripe dashboard operation. An admin editor is a separate, larger piece of work.

Testing

pnpm typecheck:turbo clean; the console unit project passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

Billing today is monthly everywhere in the console: the quota comes from
`destinationEvensPerMonth`, and every usage/overage string says "month".
An annual contract commits to a volume for the whole contract year and
meters against that single pool, so rendering it as monthly would show a
customer a twelfth of what they bought.

Extend the billing/settings contract with two optional fields —
`billingInterval` ("month" | "year") and `destinationEventsPerPeriod`
(events included in one full period; the negotiated annual commitment,
which is not derivable from the monthly tier) — and read both through a
single `billingPeriod()` helper so no surface reaches for the monthly
field directly. Both are optional and absent on every plan that exists
today, so the console keeps behaving exactly as before until the billing
API starts sending them.

The period window itself needed no change: `currentPeriod` already comes
straight from the Stripe subscription, which is anniversary-based and a
year long for an annual price.

Also make the custom-quote page (/custom-plan) interval-aware, since a
negotiated annual quote is exactly what it renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q
@absorbb
absorbb marked this pull request as ready for review September 2, 2026 05:53

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing settings, event-usage path, custom quote rendering, and the dependent connector-usage report.

  • Connector sync usage is now counted over the annual subscription window without an annual sync quota, which can produce false overage warnings for annual customers.

Comment thread webapps/console/components/Billing/BillingManager.tsx Outdated
dailyActiveSyncs is a monthly limit with no annual terms, so on an annual plan the sync usage section now windows over the contract month containing now (anchored on the period start, same as a monthly subscription would bill) instead of the whole contract year. Monthly and free plans are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 2, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing-period changes across quota resolution, usage reporting, and the custom-plan quote display. I found no additional actionable issues beyond the existing unresolved sync-usage thread.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 2, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing settings contract, event quota/usage calculation, custom-plan copy, and the monthly sync usage window for annual subscriptions.

No actionable correctness, security, or user-visible regression findings in this change set.

…, events by metering interval

The billing API now describes a negotiated quote with two independent cadences: what one charge covers (price interval x interval_count, e.g. quarterly installments) and the period the included events are metered over (billingInterval, the contract year). The page read only the price interval, so a quarterly-invoiced annual commitment rendered the installment as per month and the committed annual volume as events per month. chargeCadence() labels the charge; the events line follows the metering interval.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual committed-volume billing changes, including usage metering, quota display, sync usage windows, and custom-plan copy.

Finding:

  • Custom-billed annual workspaces still query one monthly currentPeriod while comparing it with the new annual quota.

Comment thread webapps/console/components/Billing/use-events-usage.ts Outdated
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 2, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing quota, event-usage, sync-window, and quote-cadence changes. No additional actionable issues found.

The existing unresolved inline finding remains applicable: custom-billed annual workspaces can still query only one month of events against the annual quota.

expiresAt is a UTC instant; on an annual plan it is the anniversary at 00:00 UTC, so formatting it in the viewer's zone showed the previous day west of UTC. Every other period date on the billing page is already rendered in UTC.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q
…om billing included

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing-period, event-usage, sync-quota, renewal-date, and custom-quote changes. I also checked the existing review threads; both are resolved and are not re-raised. No new actionable correctness, security, or user-visible regression found.

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing-period, usage-query, sync-quota, and custom-quote changes, including the resolved review threads.

Findings: none.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 2, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing-period contract, usage/overage calculations, monthly sync window behavior, quote cadence rendering, and UTC renewal-date display. I also checked the existing review threads; the two earlier findings are resolved and addressed by the current diff. No new actionable issues found.

The token only guarded discovery of a negotiated plan; the billing service (jitsu-cloud-billing#40) now also requires it to check out a custom plan, so anyone learning a plan id can no longer check out at the negotiated price. The quote page already holds the token and forwards it with the upgrade request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual committed-volume changes across billing settings parsing, event and sync reporting windows, custom-plan quote/checkout handling, and UTC renewal-date rendering.

No additional actionable bugs, security issues, or user-visible regressions found. The existing resolved review threads were also checked and are not re-raised.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 2, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual quota, usage-window, and custom-quote changes.

Finding:

  • Scheduled annual subscriptions can query the month before their contract begins for sync usage.

Comment thread webapps/console/components/Billing/billing-period.ts Outdated
The billing API reports no period before it starts, but the helper should not depend on that: clamp the contract-month index at 0 so a not-yet-started period yields its first month rather than the month before the contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing-period, usage, sync-quota, custom-quote, and follow-up boundary handling changes. No new actionable findings; the existing resolved review threads were considered and are not repeated.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 2, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the annual billing-period, usage reporting, sync quota window, and custom-quote changes. I found no new actionable correctness, security, or user-visible regression issues in the final diff. Existing resolved threads were considered and are not re-raised.

… pool (JITSU-200)

Product decision (2026-09-03): a committed contract is a term with a fixed monthly quota; overage is only ever tracked per month. The billing API drops billingInterval and destinationEventsPerPeriod, keeps currentPeriod at one month (the contract month anchored on the subscription start for installment-billed commitments) and moves the commitment end into expiresAt. Removes the annual quota/period helper and copy; keeps the usage cache key per period, the zero-quota guard, the UTC renewal date, the quote token on checkout, and the quote page's charge-cadence labels, now with the commitment term from commitmentInterval.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q
@absorbb absorbb changed the title JITSU-200: annual committed event volume in billing UI (console half) JITSU-200: committed contracts in billing UI (console half) Sep 3, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the latest monthly-quota commitment contract, per-period usage cache key, quote charge/commitment labels, checkout-token forwarding, and UTC renewal-date rendering.

Findings: no actionable issues found.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 3, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the billing-settings, usage-cache, renewal-date, custom-quote, and checkout-token changes.

No new actionable findings. The resolved review threads were checked and are not repeated here.

commitmentInterval now reaches the console via plan_data; on a committed contract expiresAt is the contract end, so label it "12-month commitment" instead of letting it read as a monthly cycle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the committed-contract billing UI changes, including quote checkout/labels, period caching, and UTC renewal display.

Finding:

  • A zero event quota with usage is rendered as within quota and suppresses the overage warning.

Comment thread webapps/console/components/Billing/use-events-usage.ts Outdated
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 3, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the billing-settings contract updates, commitment/charge-cadence rendering, custom-quote checkout token forwarding, and usage-cache key change. No additional actionable findings beyond the existing unresolved zero-quota usage discussion.\n\nValidation: inspected the PR diff and surrounding call paths; targeted unit test command could not run because pnpm is not installed in this environment.

…view)

A quota of 0 is a misconfigured plan; reporting the ratio as 0% hid the red bar and the overage warning. usagePercentage() reports positive usage as a finite ratio above 1 and zero usage as 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the committed-contract billing UI changes, custom-quote checkout token forwarding, UTC renewal-date rendering, and billing-usage cache key update. I also checked the existing review threads; all are resolved. No new actionable findings in the supplied range.

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the committed-contract billing UI changes, including quote checkout token forwarding, cadence/term copy, UTC renewal rendering, monthly usage cache rollover, and zero-quota handling. I also inspected the existing review threads; all are resolved and were not re-raised. No new actionable findings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 3, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the committed-contract billing UI changes, including quote checkout/cadence copy, UTC renewal rendering, and usage-period caching.

Finding:

  • The new cache key does not roll an already-open billing page into the next period because no render is scheduled at the boundary.

Comment thread webapps/console/components/Billing/use-events-usage.ts

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the committed-contract billing UI changes: quote checkout token forwarding, charge/commitment labels, UTC renewal display, and monthly usage metering safeguards.

No additional actionable findings in this pass. The existing open thread on refreshing usage after a period boundary remains applicable.

jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 3, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the negotiated-contract billing UI changes, including quote checkout token forwarding, cadence/commitment copy, billing date rendering, and usage handling.

Finding:

  • The zero-quota guard may regress plans represented by the existing negative Unlimited quota sentinel.

Comment thread webapps/console/components/Billing/usage-percentage.ts
The billing service now accepts `destinationEventsPerMonth` next to the
misspelt `destinationEvensPerMonth` (jitsu-cloud-billing#40), emitting
both wherever it normalizes plan data. Make the console robust to either
spelling on its own: BillingSettings accepts the correct key and copies
it over the legacy one on parse (correct spelling wins), and the quote
page reads the raw plan_data through the same helper instead of the
legacy key only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 3, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the billing cadence, quota normalization, custom-plan checkout token handling, and focused tests. No additional findings beyond the existing open discussion about the negative unlimited-quota sentinel.

…rcentage

A negative destinationEvensPerMonth is the unlimited sentinel; the
zero-quota special case must not turn it into "exceeded".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q
jitsu-code-review[bot]
jitsu-code-review Bot previously approved these changes Sep 3, 2026

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the committed-contract UI, quote checkout token forwarding, quota normalization, and usage guards.

Finding noted inline: malformed mixed quota keys can bypass the intended correct-key precedence.

Comment thread webapps/console/lib/schema/index.ts Outdated
A junk value in the correct-spelled key must be skipped in favour of the
legacy one, not treated as "no quota" — that is what the billing service
does, verified against its own resolution. The zod schema and the quote
page now share that single rule, so the plans table, the usage bar and a
quote can never disagree about a plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARxaC6w4JJ8Xw4b8Pr6b5Q

@jitsu-code-review jitsu-code-review Bot 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.

Reviewed the console changes for committed-contract billing: cadence and commitment copy, token forwarding, UTC renewal rendering, quota normalization, and usage/cache behavior. I found no new actionable correctness, security, or user-visible regression issues. Existing resolved review threads were inspected and not re-raised. Targeted unit tests were not run here because pnpm is unavailable in this runner.

@absorbb absorbb added the deploy:console Auto-deploy console to beta when this PR merges (JITSU-68) label Sep 3, 2026
@absorbb
absorbb merged commit 2ab27dc into newjitsu Sep 3, 2026
12 checks passed
@absorbb
absorbb deleted the feat/jitsu-200-annual-committed-volume branch September 3, 2026 14:13
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🚀 deploy:console label detected — triggered a beta console deployment to jitsu-cloud-infra. Track it in the deploy runs (newest at top): https://github.com/jitsucom/jitsu-cloud-infra/actions/workflows/deploy.yaml?query=event%3Aworkflow_dispatch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy:console Auto-deploy console to beta when this PR merges (JITSU-68)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant