Skip to content

feat(billing): alert customers when an invoice is overdue - #6845

Draft
macko911 wants to merge 8 commits into
matej/nan-6590-lift-alert-into-the-design-systemfrom
matej/nan-6232-alert-customers-when-an-invoice-is-overdue
Draft

feat(billing): alert customers when an invoice is overdue#6845
macko911 wants to merge 8 commits into
matej/nan-6590-lift-alert-into-the-design-systemfrom
matej/nan-6232-alert-customers-when-an-invoice-is-overdue

Conversation

@macko911

Copy link
Copy Markdown
Contributor

Problem

When a customer has an overdue/unpaid Orb invoice, nothing surfaces it in-app, so a failed payment can quietly lead to a service interruption. No invoice-status read existed today — only the Orb portal URL was surfaced.

Solution

  • Add GET /api/v1/plans/billing/overdue, backed by a new billing.getOverdueInvoices() (Orb invoices.list, filtered to issued + past-due + still-owed via isOverdueInvoice). Non-paying plans (free / free-uncapped) short-circuit without hitting Orb.
  • Add a sidebar warning card (shown on overdue state, above the usage card) and a warning banner at the top of the Billing & usage page, both linking to the Orb portal to edit the payment method.
  • useApiGetOverdueInvoices hook is gated to paying plans and cached (12h stale time) so the always-rendered sidebar makes no request for free accounts.

Fixes NAN-6232

Testing

  • Unit test for the isOverdueInvoice predicate (issued + past due_date + amount_due > 0); ts-build and lint pass.
  • Verified live in the browser (dev API): the sidebar card and top-of-page banner render per Figma and link out to the portal; free accounts make no /plans/billing/overdue request.

@linear-code

linear-code Bot commented Jul 21, 2026

Copy link
Copy Markdown

NAN-6232

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 14 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/webapp/src/pages/Team/Billing/components/Usage.tsx Outdated
Comment thread packages/server/lib/routes.private.ts
Comment thread packages/webapp/src/hooks/usePlan.tsx
Comment thread packages/billing/lib/clients/orb/client.ts Outdated
Comment thread packages/webapp/src/layout/AppSidebar/OverdueInvoiceCard.tsx Outdated
Comment thread packages/webapp/src/hooks/usePlan.tsx Outdated
Comment thread packages/server/lib/controllers/v1/plans/billing/getOverdueInvoices.ts Outdated
@macko911
macko911 force-pushed the matej/nan-6232-alert-customers-when-an-invoice-is-overdue branch from d5342e7 to e02ad69 Compare August 12, 2026 15:54
@macko911
macko911 deployed to development August 12, 2026 15:54 — with GitHub Actions Active
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Preview Deploy

Status URL Deploy Logs Last Updated
✅ Ready Preview URL Deploy Logs 12 Aug 2026, 15:59 UTC

@macko911
macko911 force-pushed the matej/nan-6232-alert-customers-when-an-invoice-is-overdue branch from e02ad69 to 7f260a8 Compare August 14, 2026 11:04
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Preview Deploys

Preview Status URL Deploy Logs Last Updated
Webapp ✅ Ready Preview URL Deploy Logs 14 Aug 2026, 13:02 UTC
Storybook ✅ Ready Preview URL Deploy Logs 14 Aug 2026, 13:02 UTC

@macko911
macko911 deployed to development August 14, 2026 11:05 — with GitHub Actions Active
@macko911
macko911 deployed to development August 14, 2026 11:05 — with GitHub Actions Active
Surface overdue Orb invoices in-app so a failed payment can't quietly
lead to a service interruption. Adds a sidebar warning card and a banner
at the top of the Billing & usage page, both linking to the Orb portal to
edit the payment method.

A new GET /plans/billing/overdue endpoint polls Orb (invoices.list) for
issued, past-due, still-owed invoices. Non-paying plans (free /
free-uncapped) short-circuit without hitting Orb, and the frontend hook
is gated + cached (12h) so the always-rendered sidebar stays cheap.
Address review feedback:
- Render the overdue banner even when the usage query fails, so a usage
  outage can't hide the payment warning.
- Report Orb errors before returning 500 so failures are traceable.
- Fetch the customer (portal URL) only when there's an overdue invoice.
- Paginate all past-due issued invoices so a page of fully-credited ones
  can't hide a still-owed one.
- Include the plan name in the query key so switching to a free plan
  in-session drops the stale overdue cache.
Verifying the overdue-invoice alerts previously meant flipping the
account's plan in the database and patching the no-op billing client,
since local dev has no Orb key and the alerts are gated on a paying
plan. Adds a select to the existing Plan Override dev tool instead.

The two variants differ only by whether a portal URL is returned, which
is what decides if the "Edit payment method" CTA renders. The override
also bypasses the paying-plan gate so the alerts can be previewed on a
Free account, and it is part of the query key so toggling applies
immediately rather than waiting out the 12h stale window.
The `ui/Alert` look has drifted from the design system's, so neither the
sidebar card nor the Billing page banner matched the intended design.
Hand-roll both to match `UsageLimitAlert`, the existing precedent for a
sidebar alert, until `Alert` itself moves into the design system and the
two can be folded back together.

Both places now share one `OverdueInvoiceAlert` in components/patterns,
replacing the sidebar-only `OverdueInvoiceCard` and the inline `Alert` on
the Billing page, so the copy and styling live in one spot. Also drops
the `StyledLink` warning variant, which was added for the old card and no
longer has any users.
The design moved this alert to the danger status, and the typography it
specifies (13px title over 12px body) never existed in the webapp's own
scale, so both lines rendered at 12px.

Bind to the danger tokens and to `type-text-regular-sm` / `-xs`, the
composite classes generated from the Figma text styles the design
actually uses — the sidebar's nav and dropdowns already use them, so
`text-body-small-regular` (12px/16px) was the odd one out. Also swaps
TriangleAlert for CircleAlert, matching how `UsageLimitAlert` already
distinguishes its danger state from its warning one, and drops the
underline the design doesn't have.
UsageLimitAlert carried the same drift the overdue alert just had: 12px
for both lines where the design specifies a 13px title over a 12px body,
plus an underline the design doesn't have. Move it onto the generated
Figma text styles so the two alerts match when they stack in the sidebar
footer, which they do on a Free plan with an unpaid invoice.

Also records why the overdue alert has no dismiss button, since the
design does show one.
Now that Alert lives in the design system, the overdue and usage-limit
alerts drop their hand-rolled markup for it. The DS component already
encodes the spec they were matching by hand: AlertTitle is Figma's
text/regular/sm, AlertDescription text/regular/xs, and AlertButton the
borderless pill with no underline, coloured from the alert's own status
via --alert-link. Leaving off onDismiss keeps both non-dismissible.

The sidebar keeps size="compact", matching the Figma sidebar alerts
(Size=Compact). The Billing page banner takes size="wide" so its CTA sits
inline on the right rather than wrapping under the text, which is what
the extra width is for.
@macko911
macko911 force-pushed the matej/nan-6232-alert-customers-when-an-invoice-is-overdue branch from 7f260a8 to 5a03226 Compare August 14, 2026 12:27
@macko911
macko911 changed the base branch from master to matej/nan-6590-lift-alert-into-the-design-system August 14, 2026 12:27
@macko911
macko911 deployed to development August 14, 2026 12:27 — with GitHub Actions Active
@macko911
macko911 deployed to development August 14, 2026 12:27 — with GitHub Actions Active
"View usage" routes to /team/billing#usage, an in-app route, but carried
an ExternalLink icon implying it leaves the app. Use ArrowUpRight, as the
"View log" CTA in AuthTab already does; alert CTAs that really do go
off-site keep ExternalLink.
@macko911
macko911 deployed to development August 14, 2026 12:59 — with GitHub Actions Active
@macko911
macko911 deployed to development August 14, 2026 12:59 — with GitHub Actions Active
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