Billing Plans and Roles Spec
This spec defines the canonical behavior for AIQLick plans, subscriptions, credits, and billing roles.
Canonical Plan Data
Production RDS is the canonical source for plan business fields:
| Plan | Active | Billing Mode | Price | Trial | maxCompanies | maxUsersPerCompany | Credits |
|---|---|---|---|---|---|---|---|
| Basic | Yes | PREPAID | EUR 49 monthly | 14 days | 1 | 1 | 0 |
| Pro | Yes | PREPAID | EUR 99 monthly | 0 days | 1 | 3 | 0 |
| Enterprise | Yes | POSTPAID | EUR 0 monthly | 0 days | Unlimited | Unlimited | 0 |
| Test Plan | No | PREPAID | SEK 10 monthly | 30 days | 3 | Unlimited | 30 |
Development RDS must match production for plan names, active flags, prices, currencies, billing modes, trials, features, and all plan-limit fields. Stripe price and product IDs are environment-specific and must not be copied from production to development.
Dev-only legacy plans may remain in development only when inactive and retained for historical references. They must not be shown for checkout or public pricing.
Plan Limits
Plan-limit values use the same convention everywhere:
| Value | Meaning |
|---|---|
-1 | Unlimited |
0 | Disabled |
>0 | Specific cap |
Inputs must reject limits below -1.
Company-scoped limits are enforced by backend PlanLimitGuard or background BillingGate. maxCompanies is user-scoped and counts companies where the user is Company.billingOwnerId. Users with no active subscription are on the free tier and can own exactly one company.
Billing Ownership
Company billing resolution is:
All billing checks must use the same resolution order:
Company.billingOwnerId- Earliest user with the built-in
Company Adminrole
This fallback exists only for legacy or admin-created companies missing billingOwnerId. New company creation must set billingOwnerId to the creator.
Role Rules
Company subscription and credit-pack management is allowed only for:
| Actor | Allowed |
|---|---|
| Super admin | Yes |
| Company billing owner | Yes |
Built-in Company Admin | Yes |
Custom role with tier = "admin" | Yes |
| Member or viewer roles | No |
Economy, External Sales, and Auditor/Viewer are viewer-tier roles. They may read subscription state when the UI allows it, but they cannot mutate company billing.
Subscription Creation
Self-service paid subscription creation must go through Stripe Checkout. The public/authenticated createSubscription mutation is not a self-service billing path and is restricted to super admins for controlled admin assignments and tests.
Direct local subscription creation must not allocate credits unless the subscription is active or trialing.
Inactive plans cannot be selected for checkout, enterprise requests, admin assignment, or approval.
Credits
PREPAID subscriptions allocate creditsPerMonth only when:
- The subscription is
ACTIVEorTRIALING - The subscription is
isActive = true - The plan is PREPAID
creditsPerMonth > 0
Allocation order:
- If the billing owner has company billing configs, distribute by percentages and put the remainder in the user's personal balance.
- Else if the subscription has
companyId, allocate all credits to that company balance. - Else allocate all credits to the user's personal balance.
POSTPAID plans do not allocate credits. They log usage and are invoiced monthly.
Zero-cost gated operations, such as ADD_CANDIDATE, still run subscription and enterprise-block checks, but they do not require a positive credit balance.
Stripe Webhooks
Stripe webhook idempotency must distinguish processed events from retryable events:
| Local StripeEvent state | Behavior |
|---|---|
| No row | Create processed=false, process handler, then mark processed=true |
processed=true | Return 200 without reprocessing |
processed=false | Reprocess handler and mark processed=true on success |
| Concurrent create conflict | Return 500 so Stripe retries |
Failed handlers must not become permanent no-ops on Stripe retry.
Dev Sync Procedure
When development plan, system role, or permission assignment data drifts, sync directly against RDS with the guarded backend utility. This is not a seed path and must not run Prisma migrations.
- Open network access to prod and dev RDS, usually through AWS SSM port-forwarding.
- Set
PROD_DATABASE_URLandDEV_DATABASE_URL. - Review the diff:
npm run rds:sync-system-billing - Apply the diff:
npm run rds:sync-system-billing -- --apply - Re-run the review command and confirm no drift remains.
Never copy production Stripe IDs into development.
The utility performs these direct RDS changes:
- Removes
Economy -> Subscriptions_WRITEfrom prod and dev. - Mirrors prod global system role permission assignments into dev.
- Syncs dev plan business fields from prod without copying Stripe price or product IDs.
- Deactivates active dev-only plans instead of deleting them.