diff --git a/docs-audit.md b/docs-audit.md new file mode 100644 index 0000000..389751f --- /dev/null +++ b/docs-audit.md @@ -0,0 +1,273 @@ +# Hacktron Docs Audit — drift vs `iva/origin/main` and `hecktron/origin/main` + +**Date:** 2026-05-16 +**Audited by:** Automated cross-reference against code on `origin/main` of `iva` and `hecktron`. +**Scope:** Every page in the docs site except the new `platform/billing/*` pages added in this branch. +**Status:** This audit identifies drift. Fixes are NOT applied on this branch (per scope decision — billing additions only). Track fixes in a follow-up PR. + +All `file:line` citations are against `origin/main` of either repo at the time of writing. Hyperlinks below use repo-relative paths so they keep working after merge. + +--- + +## Top-10 prioritized fixes + +Address these first — they are correctness bugs that will mislead API consumers, billing admins, or new users today. + +1. **Remove `impact`, `root_cause`, `remediation` from every Findings page.** These fields do not exist on the `Finding` entity (`apps/iva/src/findings/finding.entity.ts:40-80`) and are not returned by `RestExportService.toFindingResponse` (`apps/iva/src/rest/services/rest-export.service.ts:176-192`). SDK consumers will see missing fields. Pages: `api-reference/findings/list-findings.mdx`, `api-reference/findings/get-finding.mdx`. +2. **Fix `github_installation_id` required/optional mismatch** in `api-reference/cost-estimations/create-cost-estimation.mdx`. Docs say "No"; DTO requires it for `connected` repos with no `@IsOptional()` (`apps/iva/src/cost-estimation/cost-estimation.dto.ts:51-54`). +3. **Correct "200 PRs per seat" trial claim** in `platform/billing-access.mdx`. The per-seat PR cap defaults to **50**, configurable (`apps/iva/src/seat/dev/dev-seat-policy.service.ts:44` `DEFAULT_SEAT_LIMIT = 50`). The number 200 is the file-change cap per PR (`apps/iva/src/seat/dev/dev-seat-pr-usage.service.ts:7`), an unrelated knob. +4. **Correct pentest starter privilege**. Both `platform/billing-access.mdx` and `platform/pentests.mdx` say "owner-only". The UI gates on `isAdmin = Owner OR Admin` (`hecktron/apps/frontend/app/components/app/pentest/run-button.vue:32` and `composables/organization.ts:51-54`); backend allows any `MEMBER` (`apps/iva/src/scan/scan.controller.ts:222`). Only **credit purchase** is owner-only (`apps/iva/src/pentest-credit/pentest-credit.controller.ts:124,146`). Either tighten the controller guard or fix the docs. +5. **Reduce the Roles table from 5 to 4 rows** in `platform/billing-access.mdx`. The `OrgRole` enum has exactly four values: `OWNER`, `ADMIN`, `MEMBER`, `VIEWER` (`apps/iva/src/organization/organization-member.entity.ts:8-13`). "Unassigned" is a People-page UI label for discovered developers, not a role. +6. **Fix the Swagger UI URL.** `api-reference/introduction.mdx` and `api-reference/authentication.mdx` reference `/docs`; the public path is `/api-docs` (`apps/iva/src/main.ts:153`). `/docs` is basic-auth gated internal (`main.ts:103-114`). +7. **Document all three throttlers in `api-reference/rate-limits.mdx`.** Code runs `short` (10 req/s), `medium` (20 req/10s), and `long` (100 req/min) concurrently (`apps/iva/src/config/throttler.config.ts:10-19`). Docs mention only the 100/min number. Also add the per-route caps: `POST /v1/scans` (2/s, 5/10s, 20/min) and `POST /v1/cost-estimations` long: 5/10min, plus the org-level "max 3 active estimations" 429 (`cost-estimation.service.ts:1211-1216`). +8. **Document GitLab and Bitbucket as first-class SCM providers** in `platform/code-reviews.mdx`. iva ships full integrations (`apps/iva/src/gitlab/gitlab.controller.ts`, `apps/iva/src/bitbucket/bitbucket.controller.ts`) and the frontend has SCM connection components for both. +9. **Update onboarding labels and drop CLI references** in `platform/quickstart.mdx`. The wizard label is "In a team" not "With my team" (`hecktron/apps/frontend/i18n/locales/en.json` `onboarding.team-or-solo.team.title`). The CLI and IDE extensions are deprecated (`index.mdx` already says so). Remove the `hacktron login` mention. Also fix the "vulnerabiltiies" typo in `index.mdx:7`. +10. **Fix billing permissions section** in `platform/billing-access.mdx`. Promotion/coupon codes are **Owner-only**, not Admin+ (`apps/iva/src/payments/promotion-code.controller.ts:46,66,95,119`). Add a "Spillover billing" subsection covering the toggle + LD flag + ACTIVE-sub triple gate. + +--- + +## Per-page audit + +### `index.mdx` + +**Status:** ✅ Accurate, minor polish. + +- **Typo:** "vulnerabiltiies" on line 7 → "vulnerabilities". +- **Missing:** No card grid linking to Privacy / Security / Trust Center. Add a card group at the bottom. +- **Style:** "Hacktron Workbench (Deprecated)" section has no actionable destination. Consider removing. + +--- + +### `platform/quickstart.mdx` + +**Status:** ❌ Outdated. + +| Claim | Reality | Fix | +| --- | --- | --- | +| "By myself or With my team" | UI labels are "By myself" / "In a team" (`hecktron/apps/frontend/i18n/locales/en.json` `onboarding.team-or-solo.team.title` = "In a team") | Match UI exactly. | +| "Choose By myself if you want the individual path for the CLI and VS Code extension" | Solo path drops users into GitHub OAuth (`hecktron/apps/frontend/app/components/app/onboarding/steps/connect-github.vue:1-35`). No CLI/VS Code path exists; index.mdx already says they are deprecated. | Rewrite: "By myself" sets up a single-developer GitHub OAuth flow on personal repos. | +| "If you arrived from `hacktron login`…" | CLI is deprecated. | Remove bullet. | +| Step 4: "if an owner needs to add payment information, manage seats, or buy credits" | Owners do payment + credits; **Admins** manage seats (`apps/iva/src/seat/seat.controller.ts:135-184` — `assignSeat` is `@RequireOrgRole(OrgRole.ADMIN)`). | Split: owners do payment/credits; admins manage seats. | + +**Missing:** +- Slack integration onboarding step (`hecktron/apps/frontend/app/components/app/onboarding/steps/slack-connect.vue`). +- "Start trial" modal that owners see after onboarding (`apps/frontend/app/components/app/billing/start-trial-modal.vue`). +- Link forward to the Billing page glossary for "Dev seat". + +**Style:** The two "Pages overview" lists (lines 47–55 and 38–46) are largely duplicative — consolidate. + +--- + +### `platform/code-reviews.mdx` + +**Status:** ⚠️ Partially outdated. + +| Claim | Reality | Fix | +| --- | --- | --- | +| Only GitHub is mentioned. | iva supports **GitHub, GitLab, and Bitbucket** (`apps/iva/src/gitlab/gitlab.controller.ts`, `apps/iva/src/bitbucket/bitbucket.controller.ts`; frontend SCM components present for both). | Document all three SCM providers. | +| Implicit: PR-coverage config is just on/off per branch. | Repos also have `issue_creation_toggle` (Linear/Jira per repo) and PR-scan branches selection (`apps/frontend/app/components/app/repositories/{issue-creation-toggle,pr-scan-branches-select,pr-scans-toggle}.vue`). | Mention per-repo branch selection and issue-creation toggle. | +| No mention of PR file-count cap. | A PR with > 200 changed files is rejected with `PR_TOO_LARGE` (`apps/iva/src/seat/dev/dev-seat-pr-usage.service.ts:7` `FILE_CHANGE_CAP = 200`; user message at `dev-seat-policy.service.ts:185`). | Add "PRs larger than 200 changed files are skipped — split the PR" to "If a PR is not being reviewed". | + +**Missing:** +- Supported notification integrations (Slack, Jira, Linear) that surface review results. +- "What's next" link forward to **Findings** page. + +--- + +### `platform/billing-access.mdx` + +**Status:** ❌ Outdated — multiple factual errors. + +| Claim | Reality | Fix | +| --- | --- | --- | +| 5 roles including "Unassigned". | `OrgRole` has 4: `OWNER`, `ADMIN`, `MEMBER`, `VIEWER` (`apps/iva/src/organization/organization-member.entity.ts:8-13`). "Unassigned" is a People-page UI fallback for developers discovered via PR activity (`hecktron/apps/frontend/app/components/app/organization/people/members-table.vue:171,206,283,299`). | Reduce to 4 rows. Add a paragraph: "Unassigned" is a People-page label, not a role. | +| Owner = "...pentest starts" | UI gate: Owner+Admin (`apps/frontend/app/components/app/pentest/run-button.vue:32`). Backend gate: any Member (`apps/iva/src/scan/scan.controller.ts:222`). Only **credit purchases** are owner-only (`apps/iva/src/pentest-credit/pentest-credit.controller.ts:124,146`). | Move pentest start off Owner row. Add "Pentest credit purchases" under Owner. | +| Trial: "200 PRs per seat" | Per-seat PR limit defaults to **50** (`apps/iva/src/seat/dev/dev-seat-policy.service.ts:44` `DEFAULT_SEAT_LIMIT = 50`). The 200 is the per-PR file-change cap, an unrelated knob (`dev-seat-pr-usage.service.ts:7`). | Change to "50 PRs per seat per period (configurable)." Remove 200 from this context. | +| Trial: "lasts 14 days" | Default 14 days (`apps/iva/src/payments/stripe.service.ts:186`), but `trial_days_override` on the org overrides (`payments/trial.service.ts:103`). | Add: "Default 14 days; some orgs have a different negotiated duration." | +| "Admin+ can apply coupon codes" | Promotion codes are **Owner-only** (`apps/iva/src/payments/promotion-code.controller.ts:46,66,95,119`). | Fix to Owner-only. | +| Spillover billing not mentioned. | Owner-only toggle (`apps/iva/src/seat/seat.controller.ts:100`, `@RequireOrgRole(OrgRole.OWNER)`); gated by `spillover_billing_enabled` + LD `prOverageBillingEnabled` + ACTIVE sub (`apps/iva/src/seat/dev/dev-seat-policy.service.ts:856-861`). | Add a "Spillover billing" subsection. (Now covered in detail by the new `platform/billing/spillover.mdx` page on this branch.) | +| Trial: "begins when any organization owner adds payment information" | Trial endpoint is owner-only (`payments/subscription.controller.ts:74-77`); `has_used_trial` is permanent (`payments/trial.service.ts:141-145`). | Add: "A trial can only be started once per organization — after expiry/cancel, the org converts via Resubscribe." | +| `dev_seat_limit` not documented. | Default 10 during trial (`trial.service.ts:155-156`), 50 otherwise (`dev-seat-policy.service.ts:235`). | Document the seat cap and how to raise it. | + +**Missing:** +- Pentest credit pricing/packaging (price-preview endpoint exists at `apps/iva/src/pentest-credit/pentest-credit.controller.ts:97-122`). +- Coupon/promotion code flow (dedicated endpoints exist, undocumented). +- Seat audit trail (`dev_seat_audit_log` entity surfaces in the History tab). + +**Style:** +- Order roles least → most privilege. +- Collapse the two repetitive "Example scenarios" tables into a shared template. + +--- + +### `platform/pentests.mdx` + +**Status:** ⚠️ Partially outdated. + +| Claim | Reality | Fix | +| --- | --- | --- | +| "Only organization owners can start one right now" | UI: Owner+Admin (`hecktron/apps/frontend/app/components/app/pentest/run-button.vue:32`). Backend: any Member (`apps/iva/src/scan/scan.controller.ts:222`). Credit purchase only is owner-only. | Rewrite: "Admins and Owners can start pentests. Only Owners can purchase additional credits." | +| Pentest statuses list 5 values | `ScanStatus` enum (`packages/shared-utils/src/status-mapper.ts:2-15`) has: `pending, running, completed, failed, stopped, cancelled, skipped, pending_verification, pending_triage, draft, estimating, estimated`. | Add missing: `Pending`, `Estimating`, `Estimated`, `Pending verification`, `Pending triage`, `Stopped`, `Skipped`. | +| Wizard: "repo → target URLs → access instructions → coverage plan → review → checkout" | Real steps (`apps/frontend/app/components/app/pentest/wizard/steps.ts`): repository, target, access, context-documents, cost-estimation, review, checkout. | Replace "coverage plan" with "context documents (optional)" + "cost estimation". | + +**Missing:** +- Context documents (`apps/iva/src/context-documents/`). +- Cost estimation step inside the wizard. +- Pentest retry mode (`container.vue:5,16,38-43`). +- Disclosed pentests endpoint (`GET /scans/disclosed/:task_id`, public, no auth). + +--- + +### `api-reference/introduction.mdx` + +**Status:** ⚠️ Partially outdated. + +| Claim | Reality | Fix | +| --- | --- | --- | +| Swagger UI at `/docs` | Public path is `/api-docs` (`apps/iva/src/main.ts:153` — `SwaggerModule.setup('api-docs', ...)`). `/docs` is basic-auth gated internal (`main.ts:103-114`). | Change link to `/api-docs`. | +| Base URL `https://api.hacktron.ai/v1` | Confirmed (`apps/iva/src/main.ts:121-148`). ✅ | Keep. | + +**Missing:** API keys carry `effective_role: viewer` (`apps/iva/src/api-keys/api-key.entity.ts:50`). State this up-front so consumers don't expect Admin/Owner-gated routes to work. + +--- + +### `api-reference/authentication.mdx` + +**Status:** ⚠️ Partially outdated. + +- **Key prefix "first 12 characters" stored non-secret.** Confirmed (`api-key.service.ts:51`). ✅ +- **"Max 10 active API keys."** Confirmed (`api-key.service.ts:13`). ✅ +- **Key example length.** Format is `hacktron_` + 40 chars = 49 total (`api-key.service.ts:15`). Example is open-ended; show the exact length. +- **Auth via `X-Api-Key` header.** Confirmed (`api-key-throttler.guard.ts:34`; `main.ts:131`). ✅ +- **Missing:** State that `effective_role: viewer` means the key cannot escalate to Admin/Owner endpoints. +- **Missing:** Note `last_used_at` updates are fire-and-forget (`api-key.service.ts:123-127`); dashboard view can be a few seconds stale. + +--- + +### `api-reference/rate-limits.mdx` + +**Status:** ❌ Outdated. + +| Claim | Reality | Fix | +| --- | --- | --- | +| "100 requests per 60 seconds" only | Three named throttlers run concurrently: `short` = 10/s, `medium` = 20/10s, `long` = 100/min (`apps/iva/src/config/throttler.config.ts:10-19`). | Document all three. | +| "Dashboard sessions do not count" | They use a separate tracker but the same numeric limits (`api-key-throttler.guard.ts:22-30`). | Rewrite: "API key requests are tracked by SHA-256 of the key; dashboard requests use a separate per-user/IP tracker but the same numeric limits." | +| Per-route limits not mentioned | `POST /v1/scans`: 2/s, 5/10s, 20/min (`throttler.config.ts:22-26`). `POST /v1/cost-estimations`: 5/10min (`throttler.config.ts:32-36`). Plus org-level "max 3 active estimations" → 429 (`cost-estimation.service.ts:1211-1216`). | Add a "Tighter limits" subsection. | + +**Missing:** Mention `Retry-After` header behavior (verify whether NestJS throttler emits one). + +--- + +### `api-reference/pagination-filtering.mdx` + +**Status:** ⚠️ Mostly accurate. + +- Default `limit = 15`, max `100`. Confirmed (`apps/iva/src/dto/pagination.dto.ts:14-24`). ✅ +- Cost estimations: default `50`, max `100`. Confirmed (`rest-cost-estimation.controller.ts:73-79`). ✅ +- Sort defaults to `DESC`. Confirmed. ✅ +- **Add:** Severity sort is inverted relative to the DB enum order — `ASC` orders least → most severe (`rest-finding.controller.ts:97-110`). +- **Add:** Findings `state` filter (already exists; listed by name but missing from the "Enum filters" examples). +- **Add:** `scan_type` filter on `GET /scans` (`pr` or `full`). + +--- + +### `api-reference/errors.mdx` + +**Status:** ⚠️ Mostly accurate. + +- `402` for insufficient pentest credits. Confirmed (`rest-scan.controller.ts:108-126`). ✅ +- `409` for max API keys. Confirmed (`api-key.service.ts:38-42`). ✅ +- Scope guard messages. Confirmed exact strings (`api-key-scope.guard.ts:59-83`). ✅ +- **Add:** Custom 429 body shape for cost-estimation org cap: `"Too many active estimations (${stillActive}). Wait for current ones to finish."` (`cost-estimation.service.ts:1212-1215`). +- **Add:** Example validation-error body for malformed UUID path param (`ParseUUIDPipe` shape). + +--- + +### `api-reference/cost-estimations/*.mdx` + +**Status:** ❌ Outdated — schema mismatch on `github_installation_id`. + +**create-cost-estimation.mdx:** +| Claim | Reality | Fix | +| --- | --- | --- | +| `github_installation_id` "No" (optional) for `connected` repos | REQUIRED — DTO at `apps/iva/src/cost-estimation/cost-estimation.dto.ts:51-54` has `@ApiProperty`, `@IsInt`, no `@IsOptional()`. | Mark Required. Provide an example value. | +| `repo_url` "Max 500 chars" | Confirmed (`cost-estimation.dto.ts:43`). ✅ | Keep. | +| `1–20 repositories` | Confirmed (`cost-estimation.dto.ts:27`). ✅ | Keep. | +| Response example missing fields | `RepoInputResponseDto` also includes `source` and `archive_id`. | Add to example or note "internal fields omitted". | + +**list-cost-estimations.mdx** and **get-cost-estimation.mdx**: ✅ accurate per spot-check. + +--- + +### `api-reference/scans/*.mdx` + +**Status:** ⚠️ Partially outdated. + +**create-scan.mdx:** Mostly accurate. Add: each entry in `target_urls` must be a valid URL (`@IsUrl({}, { each: true })` at `rest-scan.dto.ts:164-167`). + +**list-scans.mdx:** Add missing statuses (`estimating`, `estimated`, `pending`, `stopped`, `skipped`, `pending_verification`, `pending_triage`, `draft`). Split `stopped/cancelled` into two rows. + +**export-scan-findings.mdx:** Enumerate CSV columns (`apps/iva/src/rest/services/rest-export.service.ts:63-77`). Add SARIF `affected_file` format note: can be `path`, `path:line`, or `path:start-end`. + +--- + +### `api-reference/findings/*.mdx` + +**Status:** ❌ Outdated — invented fields. + +**list-findings.mdx and get-finding.mdx:** Remove `impact`, `root_cause`, `remediation` from the response shape entirely. They do not exist on the `Finding` entity (`apps/iva/src/findings/finding.entity.ts:40-80`), are not returned by `RestExportService.toFindingResponse` (`apps/iva/src/rest/services/rest-export.service.ts:176-192`), and are not in `RestFindingResponseDto` (`rest-finding.dto.ts:42-86`). + +**get-finding.mdx:** Triage entry `username` is `@ApiPropertyOptional` (`rest-finding.dto.ts:101-102`); mark optional in docs or make DTO required if guaranteed. + +**add-finding-comment.mdx:** ✅ accurate. + +--- + +### `security.mdx`, `privacy.mdx` + +**Status:** ⚠️ Light review. + +- `privacy.mdx` line 3: stray opening quote in frontmatter description. Fix. +- "SOC 2 Type 1" — verify with security team whether org has moved to Type 2. +- Two different security contact emails across pages — pick one canonical (`hello@hacktron.ai` vs `founders@hacktron.ai`). +- Both are short; consider adding "Last reviewed: YYYY-MM-DD". + +--- + +### `docs.json` + +**Status:** ✅ Accurate. + +- Every referenced page exists on disk. +- `.worktrees/` is a worktree artifact; consider gitignoring `.worktrees/` so Mintlify never crawls it. +- Navigation order is logical (estimate → scan → findings). +- The new Billing group added on this branch slots under the Platform tab (see this branch's `docs.json` diff). + +--- + +## Confidence notes + +- **Owner-only pentest starts (#4):** If product intent is owner-only, the **code is missing a guard at `scan.controller.ts:222`** — the docs reflect intent, the code doesn't. Flag for product + eng alignment before fixing the docs. +- **SOC 2 Type 2 status:** Unverified from code alone; needs SME confirmation. +- **Hall of Fame / VDP status:** Unverified from code alone. + +## Followup PR sketch + +A separate PR fixing all of the above would touch: + +- `index.mdx` (typo) +- `platform/quickstart.mdx` (full rewrite of onboarding flow) +- `platform/code-reviews.mdx` (add GitLab/Bitbucket sections) +- `platform/billing-access.mdx` (roles table, trial cap, pentest privilege, coupons, add spillover link) +- `platform/pentests.mdx` (wizard steps, statuses, retries, context docs) +- `api-reference/introduction.mdx` and `authentication.mdx` (Swagger URL, effective_role) +- `api-reference/rate-limits.mdx` (three throttlers + per-route + org cap) +- `api-reference/errors.mdx` (custom 429 shapes) +- `api-reference/pagination-filtering.mdx` (severity sort, scan_type) +- `api-reference/cost-estimations/create-cost-estimation.mdx` (github_installation_id required) +- `api-reference/scans/list-scans.mdx`, `export-scan-findings.mdx` (missing statuses, CSV columns, SARIF format) +- `api-reference/findings/list-findings.mdx`, `get-finding.mdx` (remove invented fields) +- `privacy.mdx` (frontmatter typo) +- `.gitignore` (add `.worktrees/`) + +Estimated effort: 1–2 dev-days. None of these are large rewrites; they are surgical text edits with citations. diff --git a/docs.json b/docs.json index eca33f8..6ad987f 100644 --- a/docs.json +++ b/docs.json @@ -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" + ] } ] }, diff --git a/images/screenshot-placeholder.svg b/images/screenshot-placeholder.svg new file mode 100644 index 0000000..5dc7930 --- /dev/null +++ b/images/screenshot-placeholder.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + Screenshot placeholder + Replace with the screenshot described in the page's alt text. + + diff --git a/platform/billing/IMAGES.md b/platform/billing/IMAGES.md new file mode 100644 index 0000000..4eb025a --- /dev/null +++ b/platform/billing/IMAGES.md @@ -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 `` with `` and drop the placeholder caption text from the ``. + +## 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) diff --git a/platform/billing/faq.mdx b/platform/billing/faq.mdx new file mode 100644 index 0000000..a258dbd --- /dev/null +++ b/platform/billing/faq.mdx @@ -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 + + + + 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. + + + + 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. + + + + 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). + + + + 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). + + + + 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. + + + + 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). + + + + 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). + + + + 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. + + + + 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. + + + + 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). + + + +## 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 + + + + Who can do what across Billing — roles, seats, and the permission matrix. + + + The map of every billing topic. + + diff --git a/platform/billing/lifecycle.mdx b/platform/billing/lifecycle.mdx new file mode 100644 index 0000000..bffab73 --- /dev/null +++ b/platform/billing/lifecycle.mdx @@ -0,0 +1,125 @@ +--- +title: "Subscription lifecycle" +description: "Cancel, grace period, reactivate, resubscribe, and payment-failure recovery." +--- + + + **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. + + +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. + + + Billing sidebar in Grace period state: 'Your subscription cancels on {date}' message above the 'Reactivate subscription' button. + + +### 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. + + + **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. + + + + 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. + + +## 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. + + + Billing sidebar in Canceled state: 'Your subscription is canceled' message above the 'Resubscribe' button. + + +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 + + + + 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. + + + 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. + + + 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. + + + 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. + + + +## What's next + + + + Common scenarios across the lifecycle. + + + For orgs in the No subscription state, the path to start. + + diff --git a/platform/billing/overview.mdx b/platform/billing/overview.mdx new file mode 100644 index 0000000..5af4e96 --- /dev/null +++ b/platform/billing/overview.mdx @@ -0,0 +1,83 @@ +--- +title: "Billing overview" +description: "What Hacktron bills for, who can change it, and where each topic lives." +--- + +Hacktron bills for two things: **Dev seats** (code review on pull requests) and **pentest credits** (pooled, consumed by pentest scans). Everything else — roles, integrations, dashboards — is free. + +This page is the map. Follow the links to the topic you need. + + + Use **Billing** to manage payment methods, seats, spillover, and pentest credits for your organization. + + +## What you pay for + +| Product | What you pay for | Charged how | Who buys | +| --- | --- | --- | --- | +| **Dev seats** | Each developer whose PRs are reviewed | Per peak seat per billing period (HWM) | Owner | +| **Pentest credits** | One credit per pentest run, sized by scope | Up-front bundles, consumed per scan | Owner | + +Dev seats and pentest credits are **independent**. You can buy pentest credits without an active Dev-seat subscription, and vice versa. + +## Where each topic lives + + + + 14-day Dev-seat trial, payment-method requirement, and the Skip-trial button for ending early. + + + How peak (HWM) billing works, with a worked example. + + + Letting PRs continue past the per-seat cap, with billing per overage PR. + + + Cancel, grace period, reactivate, resubscribe, and payment-failure recovery. + + + Common scenarios — adding engineers mid-sprint, hitting the spillover cap, removing seats. + + + Platform roles, seat assignments, and who can do what. + + + +## How billing relates to roles + +Roles gate **actions in the Platform**. Seats gate **paid product access**. They are two separate things — see [Billing & Access](/platform/billing-access) for the full breakdown. + +| Action | Role required | +| --- | --- | +| View subscription and credit balances | Viewer or higher | +| Manage seat assignments | Admin or higher | +| Add a payment method, start the trial, buy pentest credits | Owner | +| Toggle spillover billing | Owner | + +The Platform has four roles, from least to most privilege: **Viewer**, **Member**, **Admin**, **Owner**. Each row above is satisfied by the named role and any higher role. + + + Payment methods, trial activation, and credit purchases are **Owner-only**. If you are an Admin and need a payment change, ask your org Owner. + + +## Glossary + +These terms recur across the Billing pages. The full glossary is at [Glossary](/platform/billing/faq#glossary) on the FAQ page. + +- **Dev seat** — a paid slot covering one developer's pull request reviews. +- **Peak (HWM)** — the highest number of Dev seats assigned at any point in a billing period. +- **Spillover** — billing per overage PR after the per-seat cap is hit, when enabled. +- **Trial** — a 14-day window with seat scanning enabled and no charge. Each org gets at most one trial, ever. +- **Skip trial** — the sidebar button that ends an in-progress trial immediately and converts to paid. Not a separate entry point from No-subscription state. +- **Grace period** — the time between canceling and the period's end, during which access continues. + +## What's next + + + + How to start a 14-day Dev-seat trial, and the one situation where you'd skip it. + + + The math behind "we charged you for 8 seats when you only have 5 today". + + diff --git a/platform/billing/seats.mdx b/platform/billing/seats.mdx new file mode 100644 index 0000000..8021f9f --- /dev/null +++ b/platform/billing/seats.mdx @@ -0,0 +1,90 @@ +--- +title: "Seats & peak billing" +description: "How peak seat counts (HWM) translate into the Dev-seat invoice, with a worked example." +--- + +Hacktron bills Dev seats on **peak usage** — the highest number of seats assigned at any point in the billing period. This page explains the mechanic and walks through a concrete scenario. + +## The rule + +> **You are billed for the maximum number of seats assigned at any one point during the billing period.** + +Adding seats mid-period raises the peak immediately. Removing seats mid-period **does not lower** the peak — the org is still billed for the high-water mark (HWM) until the next period starts. + +This is sometimes called **HWM billing** or **peak billing**. The trade-off: you can experiment with seat counts without worrying about partial-period proration in either direction. You pay for what you used at the busiest moment. + + + People / Seats page showing the developer list, the assigned seat count, and the cycle-peak metric for the current billing period. + + +## A worked example + +A team starts a billing period with 5 Dev seats. They hire two engineers in week 2 and a contractor in week 3. The contractor leaves at the end of week 3. Here is how the invoice resolves. + +| Day | Event | Seats active | HWM before | HWM after | Stripe quantity | Invoice impact | +| --- | --- | --- | --- | --- | --- | --- | +| 1 | Period starts | 5 | 5 | 5 | 5 | — | +| 8 | Engineer A joins → seat auto-assigned | 6 | 5 | 6 | 6 | Stripe `quantity` bumped to 6 | +| 9 | Engineer B joins → seat auto-assigned | 7 | 6 | 7 | 7 | Bumped to 7 | +| 14 | Contractor joins → seat auto-assigned | 8 | 7 | 8 | 8 | Bumped to 8 | +| 21 | Contractor leaves → seat unassigned | 7 | 8 | **8** | 8 | No change — HWM holds | +| 28 | Engineer A leaves → seat unassigned | 6 | 8 | **8** | 8 | No change — HWM holds | +| 30 | Period closes | 6 | — | — | 8 | **Invoice: 8 seats** | + +The team finishes the period with 6 active seats but is invoiced for 8 — the peak from day 14. + + + At period close, the next period's starting Stripe quantity is **reset to the active seat count**, not the prior period's HWM. In the example above, the next period starts at 6 seats. If no new seats are added that period, the invoice will be for 6. + + +## How HWM is tracked + +The peak is tracked in the `dev_seat_period_usage` record for the organization's current period: + +- **`max_assigned_count`** — the HWM. Monotonically non-decreasing within a billing period; reset on period rollover. +- **`billable_dev_seats`** — equals `max_assigned_count`. This is what Stripe's `quantity` is synced to. +- **`pending_reduction_next_period`** — the difference between the HWM and the current active count. Informational; reflects how many seats will "drop off" at the next period start. + +Each seat assignment: + +1. Acquires an advisory lock per organization. +2. Recomputes `activeNow = active dev seat count`. +3. Sets `max_assigned_count := max(max_assigned_count, activeNow)`. +4. If the peak increased, calls Stripe `subscriptionItems.update` with the new quantity. `proration_behavior` is `none` — no mid-period proration credit is issued for upgrades. + +## Trial → paid: peak resets + +When an organization converts from trial to paid (either by natural expiry or by ending the trial early), the peak is **reset to the current active seat count**. This prevents trial peaks — which often spike during initial exploration — from carrying into the first paid invoice. + +For example: a trial that peaks at 10 seats but has 3 active when the trial ends starts the first paid period with HWM = 3, not 10. + +## Removing seats + +Removing a seat: + +- Unassigns the seat immediately. The developer's PRs are no longer reviewed. +- Frees the slot to be reassigned to another developer within the current period at no additional charge. +- Does **not** reduce the HWM. The org is still billed for the peak. + +This is intentional. It means an admin can confidently rotate which developers hold seats without worrying about per-change billing impacts. You only pay more when you exceed your prior peak. + +## Downgrading to zero seats + +If the org reduces seat count to zero mid-period (for example, after a layoff), the subscription stays `ACTIVE` and the invoice still reflects the period's peak. To stop future billing entirely, the Owner must **cancel** the subscription — see [Lifecycle](/platform/billing/lifecycle). + +## Why peak instead of average? + +- **Predictability.** The number on the invoice equals a real moment in the period — not a calculated average that depends on day-counts and proration windows. +- **Bursting.** Teams can scale up for a sprint, ship the work, and scale down — and the bill matches what they actually used at peak. Most teams prefer this to per-day proration. +- **Operational simplicity.** No proration credits to chase. No mid-period downgrades creating refund accounting. + +## What's next + + + + What happens when a seat hits its per-seat PR cap — and how to opt in to overage. + + + Cancel, grace period, reactivate, payment-failure recovery. + + diff --git a/platform/billing/spillover.mdx b/platform/billing/spillover.mdx new file mode 100644 index 0000000..585a7ec --- /dev/null +++ b/platform/billing/spillover.mdx @@ -0,0 +1,138 @@ +--- +title: "Spillover (PR overage) billing" +description: "Let PRs continue past the per-seat PR cap, billed per overage PR." +--- + + + **Owner-only toggle.** Only an organization Owner can enable or disable spillover billing. Admins and Members can see the setting but cannot change it. + + +Spillover billing lets pull-request reviews continue past the per-seat PR cap, with each overage PR billed individually. It is **off by default**. The toggle appears only on paid plans; turning it on enables overage billing for future PRs, and turning it off is always allowed. + +This page covers when spillover applies, exactly how it's billed, and what happens when you toggle it mid-period. + +## When spillover applies + +A PR is processed as a spillover (overage) event when **all three** of these are true: + + + + Not trialing, not past-due, not canceled. Trials and past-due hard-block past the cap rather than spilling over — there is no spillover during a trial. + + + The Owner has enabled **Spillover billing** on the Billing page. The setting persists across billing periods and across Resubscribe. + + + OSS-exempt PRs use a fixed lower cap (30 PRs per period) and never spill over. + + + +If any one of those is false, the PR is **denied** when it hits the per-seat cap, with a CI annotation explaining why (see the [Deny reasons](#deny-reasons) table). + + + Some organizations have an internal-unlimited entitlement from Hacktron — these orgs bypass the per-seat cap entirely and never see the spillover toggle. + + + + Billing sidebar Spillover card: title 'Spillover billing' with info icon, green dot + 'On' state indicator, and toggle switch in the on position. + + +## How overage PRs are billed + +Each overage PR is a single Stripe meter event. The pricing is one fixed amount per overage PR — your invoice line will show a count and a unit price. + +Mechanically: + +1. PR arrives, seat policy check fires. +2. If the PR is past the per-seat cap AND all three conditions above hold, a `dev_seat_pr_usage` row is created with `overage_at_insert = true`. +3. A BullMQ job enqueues with `jobId = prUsageId`. The job calls Stripe's meter event API with `identifier = prUsageId`. +4. On success, `billed_at` is stamped on the row. Subsequent calls for the same row return silently. +5. Each hour, a reconciliation job sweeps any rows where `overage_at_insert = true AND billed_at IS NULL AND period_end > NOW()`. This catches jobs lost to Redis flushes or deploy races. + +The system has three layers of idempotency — the BullMQ jobId, the DB `billed_at` set-once, and the Stripe `identifier` dedup. Double-billing is structurally prevented. + +### Lazy meter-item attachment + +The Stripe subscription item for overage billing is attached **on the first overage event**, not at subscription creation. This means: + +- If you never use spillover, your subscription has no overage line item and no $0 lines. +- The first overage in your org's history attaches the meter item once and re-uses it from then on. +- Disabling spillover later leaves the meter item attached. Stripe shows $0 for the line in periods with no events — this is expected and harmless. + +## Toggling spillover mid-period + + + + Effect is **forward-only**: + + - PRs that were previously hard-blocked stay blocked. They are **not** retroactively scanned. The developer must retrigger the PR (push, comment, or close-and-reopen). + - PRs arriving after the toggle is enabled flow through the overage path and are billed. + + There is no retroactive credit, no auto-retry of denied PRs, and no notification to developers whose PRs were previously denied. + + + Effect is **forward-only**: + + - PRs already recorded with `overage_at_insert = true` will still bill via the hourly reconciliation job, even after the toggle is off. Disabling stops **future** overage, not in-flight rows. + - New PRs past the per-seat cap will be hard-blocked with a CAPACITY_REACHED deny code. + + If you toggle off because of an unexpected charge, expect a small tail of overage events to land on your next invoice — these were already in flight at the moment you toggled. + + + +## Deny reasons + +When a PR is denied at the per-seat cap, developers see a CI annotation. The copy varies based on context: + +| Situation | Reason shown | +| --- | --- | +| Org is on a trial | "PR review limit reached for this trial period. Inform your org owner." | +| Paid org (ACTIVE), Owner has **not** enabled the toggle | "PR review limit reached for this billing period. Spillover billing is available for this org but is not enabled. Ask your org owner to enable spillover billing on the billing page, or wait for the next billing cycle." | +| Subscription is not ACTIVE (past-due, canceled), or the PR is OSS-exempt | "PR review limit reached for this billing period. New PRs will resume at the start of the next cycle." | + +Developers can ask Owners directly via a link in the CI annotation. There is no in-product approval queue — toggling on the Billing page is the resolution. + + + People page spillover banner (yellow UAlert) in the Owner view: warning icon, title 'Spillover billing is available', body copy mentioning the PR limit, and 'Enable spillover' action button linking to Billing. + + +## Recommended use + +Spillover is most useful when: + +- Your team consistently hits the per-seat cap but the cost of adding seats outweighs the cost of paying per overage PR. +- You have temporary PR spikes (e.g., dependency upgrade sweeps) where you want reviews to continue without permanently raising your seat count. +- You want a soft ceiling rather than a hard one — and your finance team can absorb a variable line item. + +It is **not** useful when: + +- The org wants strict cost predictability — peak seat billing alone is more predictable. +- You'd rather scale seat counts up at the start of a sprint than pay per overage PR. The cost trade-off is per-org. + +## Troubleshooting + + + + The toggle is shown only when the subscription is `ACTIVE` (not trialing, past-due, or canceled) and the org is not on an internal-unlimited entitlement. If you're trialing, finish or skip the trial first. If you're past-due, update the payment method. Internal-unlimited orgs don't need spillover at all. + + + Expected. Disabling stops future overage, not rows already inserted with `overage_at_insert = true`. The hourly reconciliation job bills those rows on its next sweep. The tail rarely exceeds a few hours of PRs. + + + Check the CI annotation. The reason text tells you which condition failed: subscription status, OSS exemption, or the toggle being off. If the reason mentions the trial, the org is still on a trial — spillover only applies to ACTIVE paid subscriptions. + + + There is no in-product hard cap today. If you need a cap, monitor your overage line on the Billing page and disable the toggle when the spend hits your target. Future work may add a self-serve cap. + + + +## What's next + + + + The seat-count billing your spillover line sits on top of. + + + Common scenarios — what to do if you hit the spillover cap unexpectedly. + + diff --git a/platform/billing/trial.mdx b/platform/billing/trial.mdx new file mode 100644 index 0000000..a849bbc --- /dev/null +++ b/platform/billing/trial.mdx @@ -0,0 +1,130 @@ +--- +title: "Trial & activation" +description: "How the 14-day Dev-seat trial works, what it includes, and when to skip it." +--- + + + **Owner-only.** Only an organization Owner can start the trial, add the first payment method, or activate paid billing. Admins and Members can view billing details but cannot change them. + + +Hacktron offers a **14-day Dev-seat trial** for organizations that have never trialed before. The trial is one-time per organization and starts the moment an Owner adds a payment method on the **Billing** page. + +This page covers how the trial behaves, the three ways it can end, and how to end it early when you're ready to start paid billing now. + +## At a glance + +| Property | Trial behavior | +| --- | --- | +| **Duration** | 14 days from activation (some orgs have a different negotiated duration) | +| **Payment method** | Required to start. No charge during the trial. | +| **Seat assignment** | Auto-approval — Dev seats assign as PRs arrive | +| **Default seat cap** | 10 seats | +| **Per-seat PR cap** | Default 50 PRs per billing period (configurable) | +| **Spillover** | Not available during trial | +| **One-time** | Each organization gets at most **one** trial, ever | + +## How the trial starts + + + + GitHub, GitLab, or Bitbucket. See [Quickstart](/platform/quickstart) for the connection flow. + + + The Owner navigates to **Billing** in the Platform. + + + Card details go straight to Stripe — Hacktron never sees raw card data. Adding a payment method on a trial-eligible org auto-starts the trial as soon as the modal closes. + + + The organization enters the 14-day window immediately. `has_used_trial` is set permanently at this moment — see the Warning below. + + + +During the trial, Dev seats auto-assign as PRs come in. There is no hard cap on PRs at the organization level — each seat is metered against the per-seat PR cap (50 PRs per billing period by default). + + + Post-install Start-trial confirmation modal: title 'Start your 14-day trial', body copy, and 'Go to Billing' CTA. + + +## What happens when the trial ends + +The trial ends one of three ways: + + + + On day 14, the trial transitions to a paid plan automatically. Stripe issues the first invoice; the **peak seat count** during the trial is **reset**, so the first paid invoice bills only for seats currently active — not the trial peak. + + [See Peak billing](/platform/billing/seats) for the HWM mechanics. + + + The Owner can end the trial at any time from the Billing page. This converts the organization to a paid plan immediately, with the same HWM reset behavior as natural expiry. + + Use this if the team is ready to commit before day 14 and wants the period boundary to start now. + + + Billing sidebar showing the 'End trial early' button next to the Cancel button, with the trial period end date above. + + + + Canceling during the trial sets `cancel_at_period_end = true`. The trial continues to run; when day 14 arrives, the subscription is canceled instead of converted. No charges are issued. + + The Owner can reactivate any time before day 14 with one click. + + + +## Skipping the trial + + + Use this when the team is ready to start paid billing now and doesn't want to wait out the remaining trial days. + + +There is no separate "Start paid plan" entry point from the No-subscription state — every paid subscription starts as a trial. To get to paid billing immediately, start the trial and end it the same day. Mechanically: + + + + Add a payment method on the Billing page. The trial begins as soon as the modal closes; `has_used_trial` is set permanently at this moment. + + + The button appears whenever the subscription is `trialing` and not already pending cancellation. + + + Stripe's `trial_end` is set to "now" and `cancel_at_period_end` is reset to `false`. The first paid invoice issues at the current period close; the period boundary is the moment you skipped. + + + + + **The trial is one-time per organization.** Once an Owner adds a payment method on a trial-eligible org, `has_used_trial` flips to true permanently — even if you skip the trial on day 1 or cancel before day 14. Future cancel→reactivate cycles go through **Resubscribe**, not a new trial. There is no admin override. + + +## What's not available during the trial + +- **Spillover billing.** Spillover requires an `ACTIVE` paid subscription — see [Spillover](/platform/billing/spillover). PRs past the per-seat cap during a trial are hard-blocked, not billed as overage. +- **Pentest credit purchases unrelated to seats.** These are independent — you can buy pentest credits without an active subscription, but seat-level features stay gated by trial/paid status. + +## Troubleshooting + + + + Check that the org has not already used its trial — `has_used_trial` is permanent. If `has_used_trial` is true, adding a payment method only attaches the card; it does not start a new trial. The Billing sidebar will show **Resubscribe** for that case. + + + No. Each organization is permitted one trial only. If the org has used its trial, the Billing page will show **Resubscribe** instead of **Start trial**. There is no admin override. + + + They stay assigned. The peak seat count is reset on conversion so the first paid invoice bills only for currently-active seats, not the trial peak. See [Peak billing](/platform/billing/seats). + + + Trials default to 14 days. If your organization has a negotiated different duration, the Billing page will reflect it. Contact your Hacktron point of contact to negotiate; the default cannot be self-served. + + + +## What's next + + + + How peak seat counts during a period translate into the invoice. + + + Cancel, grace period, reactivate — the full lifecycle past the trial. + +