Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions docs-audit.md

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@
"platform/billing-access",
"platform/pentests"
]
},
{
"group": "Billing",
"pages": [
"platform/billing/overview",
"platform/billing/trial",
"platform/billing/seats",
"platform/billing/spillover",
"platform/billing/lifecycle",
"platform/billing/faq"
]
}
]
},
Expand Down
14 changes: 14 additions & 0 deletions images/screenshot-placeholder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions platform/billing/IMAGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Billing pages — screenshots to capture

Tracks every placeholder dropped in the new `platform/billing/*.mdx` pages, mapped to the actual frontend component on `hecktron/origin/main`. When you replace a placeholder, delete its row from this checklist.

**Conventions**

- Source: dark theme, ~1600×900 (matches the existing `/images/platform-*-dark.png` set).
- Format: PNG. Name follows `platform-billing-{topic}-dark.png`.
- Replace each `<img src="/images/screenshot-placeholder.svg" />` with `<img src="/images/{name}.png" />` and drop the placeholder caption text from the `<Frame>`.

## Checklist

| Page | Where | Component / route | Suggested filename |
| --- | --- | --- | --- |
| `overview.mdx` | Hero card (already populated) | `/[slug]/billing` | `platform-billing-dark.png` (existing) |
| `trial.mdx` | After "How the trial starts" | `AppBillingStartTrialModal` (`apps/frontend/app/components/app/billing/start-trial-modal.vue`) | `platform-billing-trial-modal-dark.png` |
| `trial.mdx` | Inside the "Owner ends trial early" tab | Billing sidebar with the `skipTrialAvailable` button (`apps/frontend/app/pages/[slug]/billing.vue:230-238`) | `platform-billing-end-trial-cta-dark.png` |
| `seats.mdx` | Before "A worked example" | `/[slug]/people` (or Seats tab) with seat list + cycle-peak metric | `platform-billing-seats-list-dark.png` |
| `spillover.mdx` | Before "How overage PRs are billed" | `AppBillingSpilloverCard` (`apps/frontend/app/components/app/billing/spillover-card.vue`) | `platform-billing-spillover-toggle-dark.png` |
| `spillover.mdx` | After "Developers can ask Owners directly…" | `AppOrganizationPeopleSpilloverBanner` (`apps/frontend/app/components/app/organization/people/spillover-banner.vue`), Owner view | `platform-billing-spillover-banner-dark.png` |
| `lifecycle.mdx` | Before "Reactivate during grace" | Billing sidebar in Grace state — `isCancelling` branch (`apps/frontend/app/pages/[slug]/billing.vue:200-228`) | `platform-billing-grace-period-dark.png` |
| `lifecycle.mdx` | Before "Resubscribe" | `AppBillingTrialEndedBanner` (`apps/frontend/app/components/app/billing/trial-ended-banner.vue`) | `platform-billing-trial-ended-banner-dark.png` |
| `lifecycle.mdx` | Inside "Resubscribe" section | Billing sidebar with the `canStartTrial===false` Resubscribe branch (`apps/frontend/app/pages/[slug]/billing.vue:255-263`) | `platform-billing-resubscribe-dark.png` |

## Optional shots (nice-to-have, not currently placeheld)

- A real GitHub PR with the `CAPACITY_REACHED` CI annotation for the spillover-disabled deny copy — would strengthen `spillover.mdx`. Source: `apps/iva/src/seat/dev/dev-seat-policy.service.ts:190-211` for the exact copy variants.
- The Stripe-hosted "Add payment method" modal (`AppBillingAddPaymentMethodModal`) — relevant on both `trial.mdx` and `lifecycle.mdx` for the resubscribe flow.

## Removing the placeholder

`/images/screenshot-placeholder.svg` exists only to render a visible "Screenshot pending" frame during review. Once every row above is replaced, delete:

- `images/screenshot-placeholder.svg`
- `platform/billing/IMAGES.md` (this file)
113 changes: 113 additions & 0 deletions platform/billing/faq.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: "Billing FAQ"
description: "Common scenarios for Dev-seat billing — added engineers mid-sprint, hit the spillover cap, canceled mid-period."
---

Named scenarios, not generic Q&A. Find your situation in the accordion below.

## Common scenarios

<AccordionGroup>
<Accordion title="We hired 5 engineers mid-month — what changes on our invoice?">
As each new engineer's first PR comes in, a Dev seat auto-assigns. The org's HWM (peak seat count) bumps up. The end-of-month invoice bills for the peak — not the average, not a prorated amount.

If you previously had 5 seats and now have 10, the invoice will reflect 10 seats. No proration. The new seat count carries into the next period as the starting point unless seats are removed.

See [Peak / HWM billing](/platform/billing/seats) for the math.
</Accordion>

<Accordion title="We had a PR spike but no new hires. How does the bill look?">
If your team stayed within the per-seat PR cap, no change — peak seats didn't move.

If individual developers hit the per-seat PR cap and **spillover is enabled** for your org, you'll see a separate **overage** line on the invoice for the per-PR overage events. See [Spillover billing](/platform/billing/spillover).

If spillover is **not enabled**, the PRs past the cap were hard-blocked. Those developers saw a CI annotation explaining they hit the limit. No additional charge — and no review on those PRs until the next period or until you enable spillover.
</Accordion>

<Accordion title="We want to skip the trial and start paid immediately.">
Every paid subscription starts as a trial first — there is no separate "Start paid plan" entry point. To get to paid billing today: the Owner adds a payment method (which auto-starts the trial), then clicks **Skip trial** on the Billing sidebar. The subscription converts to paid immediately and the billing period starts from the conversion.

Caveat: this still consumes the org's one and only trial. Even a same-day Skip flips `has_used_trial` permanently — any future cancel/reactivate cycle will go through **Resubscribe**, not a new trial.

See [Trial & activation § Skipping the trial](/platform/billing/trial#skipping-the-trial).
</Accordion>

<Accordion title="We hit our spillover cap and need to stop the bleeding.">
There is no in-product hard cap on spillover spend today. To stop further overage charges:

1. The Owner navigates to the Billing page.
2. Disables the **Spillover billing** toggle.
3. New PRs past the per-seat cap will be hard-blocked going forward.

Expect a small tail of overage events to land on the next invoice — rows already inserted with `overage_at_insert = true` continue to bill via the hourly reconciliation job. The tail rarely exceeds a few hours of PR activity.

See [Spillover § Toggling spillover mid-period](/platform/billing/spillover#toggling-spillover-mid-period).
</Accordion>

<Accordion title="We want to remove seats after a sprint.">
The admin unassigns seats from the Seats page. Each unassignment is immediate — the developer's PRs stop being reviewed.

But: removing seats does **not** lower the period's HWM. The invoice will still reflect the peak. The next period starts at the current active seat count.

Said differently: removing seats today saves money next period, not this period.
</Accordion>

<Accordion title="We canceled. Why is there still an invoice?">
Cancellation enters a **Grace period** — `cancel_at_period_end = true`. The current period's invoice still issues because access continued through it. At period close, the subscription transitions to **Canceled** and no further invoices issue.

If you canceled the day before period close, expect one final invoice; if you canceled in week 1, expect the full period invoice. The amount is the peak seat count for the period.

See [Lifecycle § Cancel](/platform/billing/lifecycle#cancel).
</Accordion>

<Accordion title="Payment failed and our developers can't scan. What now?">
The fastest path: the Owner updates the payment method on the Billing page. Stripe retries against the new card immediately; most orgs return to **Paid active** within minutes.

PRs that were denied during the Past-due window will not auto-rescan. Affected developers should push or re-trigger CI on the affected PRs to get a fresh review.

See [Lifecycle § Payment failure](/platform/billing/lifecycle#payment-failure).
</Accordion>

<Accordion title="A developer left. Do we get a credit?">
No — Hacktron bills on peak, not on day-of-departure. The seat can be reassigned to another developer immediately at no additional charge. If no one takes the seat, the next period will start at one fewer seat.

The trade-off is intentional: it makes seat rotation low-friction. You pay for capacity at peak; you don't pay extra to shuffle who holds the slot.
</Accordion>

<Accordion title="We're on a trial and we love it. Can we extend?">
Trials default to 14 days. If your org has a negotiated different duration, the Billing page will reflect it. The default cannot be self-served; contact your Hacktron point of contact to discuss.

Most teams find 14 days sufficient. If you want to start the billing period sooner, you can **End trial early** from the Billing page.
</Accordion>

<Accordion title="We trialed last year, canceled, and want to come back.">
The Billing page will show **Resubscribe** (not Start trial). Resubscribing creates a new paid subscription immediately — no second trial. Previously-assigned seats are restored; the spillover toggle and other settings are preserved.

See [Lifecycle § Resubscribe](/platform/billing/lifecycle#resubscribe).
</Accordion>
</AccordionGroup>

## Glossary

These terms appear across the Billing pages. Linked from each page's introduction.

- **Dev seat** — a paid slot covering one developer's pull request reviews. Auto-assigned on first PR; can be reassigned freely within a period.
- **Peak / HWM (high-water mark)** — the highest number of Dev seats assigned at any point in a billing period. Determines the invoice.
- **Per-seat PR cap** — the maximum number of PRs a single seat can review in a period. Default 50, configurable per org.
- **Spillover (PR overage)** — billing per overage PR after a seat hits its PR cap. Off by default; Owner-only toggle.
- **Trial** — a 14-day window with seat scanning enabled and no charge. Each org gets at most one trial, ever.
- **Skip trial** — the Billing-sidebar button that ends an in-progress trial immediately and converts to paid. Available only while trialing; not a separate entry point from the No-subscription state.
- **Grace period** — the time between **Cancel** and the period's end, during which access continues but `cancel_at_period_end` is set.
- **Past-due** — Stripe could not collect on the invoice. Scanning is blocked; Stripe retries automatically.
- **Resubscribe** — creating a new paid subscription after a previous one was canceled. Available indefinitely; never re-enables the trial.

## What's next

<CardGroup cols={2}>
<Card title="Roles & access" icon="users" href="/platform/billing-access">
Who can do what across Billing — roles, seats, and the permission matrix.
</Card>
<Card title="Billing overview" icon="map" href="/platform/billing/overview">
The map of every billing topic.
</Card>
</CardGroup>
125 changes: 125 additions & 0 deletions platform/billing/lifecycle.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
title: "Subscription lifecycle"
description: "Cancel, grace period, reactivate, resubscribe, and payment-failure recovery."
---

<Info>
**Owner-only.** All actions on this page — cancel, reactivate, resubscribe, update payment method — require the Owner role. Admins and Members can view subscription state but cannot change it.
</Info>

A Dev-seat subscription moves through a small set of states over its life. This page covers every state, what triggers each transition, and what changes for developers in each state.

## States

| State | What it means | Developers can scan? | Recoverable? |
| --- | --- | --- | --- |
| **No subscription** | Org has never had a paid plan or trial | No | Yes — add a payment method to start the trial |
| **Trial active** | 14-day trial window in progress | Yes | — (this is the goal state) |
| **Paid active** | Paid plan with current invoices | Yes | — |
| **Grace period** | Owner canceled; period not yet ended | Yes (until period close) | Yes — reactivate before period closes |
| **Past-due** | Invoice payment failed | **No** | Yes — Stripe retries; Owner can update payment method |
| **Canceled** | Subscription fully canceled | No | Yes — resubscribe |
| **Org deleted** | Organization itself removed | No | No |

Each transition runs through a single Stripe-backed subscription record. The Owner-facing actions below are the only way to move between states.

## Cancel

To cancel a subscription, the Owner clicks **Cancel subscription** on the Billing page. This sets `cancel_at_period_end = true` on the Stripe subscription. Two things follow:

1. **Access continues until the period closes.** The org is still in the **Grace period** state; developers can still scan, seats stay assigned, and the period's invoice will still issue.
2. **At the period close, the subscription transitions to Canceled.** All Dev seats unassign. Scanning stops.

The grace period exists so that:

- Teams can cancel proactively without losing access mid-period.
- The invoice for the current period resolves cleanly — you paid for it, you keep it.

<Frame caption="Placeholder — replace with the Billing sidebar in the Grace period state. Should show the 'Cancels on {date}' message and the 'Reactivate subscription' button.">
<img src="/images/screenshot-placeholder.svg" alt="Billing sidebar in Grace period state: 'Your subscription cancels on {date}' message above the 'Reactivate subscription' button." />
</Frame>

### Reactivate during grace

Until the period closes, the Owner can **Reactivate** with one click. This sets `cancel_at_period_end = false`. Nothing else changes — seats stay assigned, scans continue uninterrupted.

After the period closes, reactivation is no longer available. The path back to active is **Resubscribe**.

## Payment failure

Stripe attempts each invoice payment. On failure, the subscription flips to **Past-due**:

- Developers immediately lose scanning access. CI annotations explain that the org's subscription is past-due.
- Stripe retries the invoice over the next several days (Stripe's smart retry schedule).
- On success, the subscription flips back to **Paid active** and access resumes.
- On final failure (after Stripe's retries are exhausted), the subscription is auto-canceled.

Owners receive Stripe's standard payment-failure emails. The fastest fix is usually to update the payment method on the Billing page; Stripe will retry against the new card immediately.

<Warning>
**Past-due cuts off access mid-period.** Unlike the grace period (which preserves access), Past-due immediately blocks Dev-seat scanning. This is to prevent unbilled work from accumulating on a card Stripe can't charge.
</Warning>

<Frame caption="Placeholder — replace with the org-wide trial-ended banner (AppBillingTrialEndedBanner): red error bar across the top of the app with the message and 'Reactivate billing' link to /[slug]/billing.">
<img src="/images/screenshot-placeholder.svg" alt="Trial-ended banner across the top of the app: red bar with alert-triangle icon, body copy explaining the trial has ended, and 'Reactivate billing' link to the Billing page." />
</Frame>

## Resubscribe

If a subscription has been **Canceled** (whether by Owner action, payment failure, or trial cancellation), the org can resubscribe. The Billing page shows a **Resubscribe** button when no active subscription exists and a prior one is on file.

<Frame caption="Placeholder — replace with the Billing sidebar in the Canceled state, showing the 'Resubscribe' CTA (or 'Resubscribe — add payment method' when no PM on file).">
<img src="/images/screenshot-placeholder.svg" alt="Billing sidebar in Canceled state: 'Your subscription is canceled' message above the 'Resubscribe' button." />
</Frame>

Resubscribing creates a brand-new Stripe subscription and a fresh DB row. Important details:

- **A new billing period starts.** Previously-assigned developers are re-assigned to the new subscription automatically.
- **The trial cannot be repeated.** Each org has at most one trial in its lifetime. The org goes straight to a paid plan.
- **Spillover toggle state is preserved** across resubscribe — if you had it on before, it's on again on the new subscription.

## Org deletion

Deleting the organization (a separate, irreversible action) cascade-deletes the subscription, all seat records, all PR usage data, and audit logs. There is no recovery path. Deletion is rarely the right action — for "we want to stop billing" the right path is **Cancel**.

## Seeing your current state

The Billing page surfaces the current state in the subscription card at the top. The page's available actions vary by state:

| State | Visible actions |
| --- | --- |
| No subscription | Add payment method (auto-starts the trial) |
| Trial active | Skip trial, Cancel, Change payment method |
| Paid active | Cancel, Change payment method, Manage seats |
| Grace period (cancel pending) | Reactivate, Change payment method (Cancel is hidden) |
| Past-due | Update payment method (highlighted) |
| Canceled, trial never used | Start trial (CTA card), View past invoices |
| Canceled, trial already used | Resubscribe, View past invoices |

## Troubleshooting

<AccordionGroup>
<Accordion title="We canceled but still got an invoice — why?">
Cancellation enters the **Grace period**. The current period's invoice still issues because access continued through it. The subscription transitions to **Canceled** at the period close — no further invoices will issue.
</Accordion>
<Accordion title="Stripe charged a card we removed.">
Removing a card from Stripe doesn't retro-cancel attempts already in flight. Update the payment method first; then any retries will hit the new card. If the old card was incorrectly charged, contact us — we'll work with you and Stripe to resolve.
</Accordion>
<Accordion title="Our subscription went Past-due over a weekend and developers lost access. Can we backfill?">
Updating the payment method usually clears Past-due within minutes (Stripe retries against the new card). PRs that were denied during the Past-due window will not auto-rescan; affected developers should push or re-trigger CI on the affected PRs to get a fresh review.
</Accordion>
<Accordion title="We want to delete our organization and start fresh.">
Org deletion is irreversible and removes all history — findings, scans, audit logs. If you only need to stop billing, Cancel is the right path; if you need to restructure, contact Hacktron support before deleting so we can help preserve data.
</Accordion>
</AccordionGroup>

## What's next

<CardGroup cols={2}>
<Card title="Billing FAQ" icon="circle-question" href="/platform/billing/faq">
Common scenarios across the lifecycle.
</Card>
<Card title="Trial & activation" icon="rocket" href="/platform/billing/trial">
For orgs in the No subscription state, the path to start.
</Card>
</CardGroup>
Loading