SPEC 011: Billing and Subscriptions
Status: DRAFT Priority: P0 Created: 2026-02-21 Approved: pending Repo(s): equa-web, equa-server
1. Feature Purpose
The Billing and Subscriptions module handles the commercial side of the Equa platform: subscription tier management, payment profile collection, checkout flows, coupon redemption, and waitlist positioning. It integrates with Chargify as the external payment processor and subscription lifecycle manager. The module enforces tier-based limits (e.g. member caps) across the platform and provides self-service upgrade/downgrade flows for organization admins.2. Current State (Verified)
Frontend modules:3. Data Model
Entities
| Entity | Description | File |
|---|---|---|
| MemberLimits | Enforces subscription-tier member caps per organization | equa-server/modules/persistence/src/entity/MemberLimits.ts |
| UserCoupons | Coupon codes redeemed by users | equa-server/modules/persistence/src/entity/UserCoupons.ts |
| Waitlists | Pre-launch or tier-gated waitlist positions | equa-server/modules/persistence/src/entity/Waitlists.ts |
Key Fields — MemberLimits
| Field | Type | Nullable | Description |
|---|---|---|---|
organization | uuid | No | Organization this limit applies to |
memberLimit | integer | No | Maximum team members allowed by current subscription tier |
Key Fields — UserCoupons
| Field | Type | Nullable | Description |
|---|---|---|---|
user | uuid | No | User who redeemed the coupon |
code | string | No | Coupon code |
Key Fields — Waitlists
| Field | Type | Nullable | Description |
|---|---|---|---|
id | uuid | No | Primary key |
user | uuid | No | User on the waitlist |
position | integer | No | Queue position |
Relationships
External Integration: Chargify
Chargify manages the canonical subscription state. The Equa backend syncs via:- API calls to Chargify for creating/updating subscriptions, applying coupons, and managing payment profiles.
- Webhooks from Chargify for subscription lifecycle events (activation, renewal, cancellation, payment failure, dunning).
MemberLimits entity is updated when subscription tier changes are confirmed by Chargify.
4. API Endpoints
Served byequa-server/modules/api/src/endpoints/billing-endpoints.ts.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/billing/subscriptions | Session | Get current user’s subscriptions |
| POST | /v1/billing/subscriptions | Session | Create a new subscription (triggers Chargify) |
| PUT | /v1/billing/subscriptions/:id | Session | Update subscription (upgrade/downgrade) |
| DELETE | /v1/billing/subscriptions/:id | Session | Cancel a subscription |
| GET | /v1/billing/payment-profiles | Session | List payment profiles on file |
| POST | /v1/billing/payment-profiles | Session | Add a payment profile |
| PUT | /v1/billing/payment-profiles/:id | Session | Update a payment profile |
| DELETE | /v1/billing/payment-profiles/:id | Session | Remove a payment profile |
| POST | /v1/billing/coupons/redeem | Session | Redeem a coupon code |
| GET | /v1/billing/coupons/validate/:code | Session | Validate a coupon code without redeeming |
| GET | /v1/billing/plans | Public | List available subscription plans and pricing |
| POST | /v1/billing/checkout | Session | Initiate checkout flow |
| GET | /v1/billing/invoices | Session | List billing invoices |
| POST | /v1/billing/webhooks/chargify | Webhook | Chargify webhook receiver |
| GET | /v1/waitlist/status | Session | Get user’s waitlist position |
| POST | /v1/waitlist/join | Session | Join the waitlist |
5. Frontend Components
Payments Module
| Component | File | Purpose |
|---|---|---|
| BillingDashboard | equa-web/src/modules/payments/components/BillingDashboard.tsx | Overview of billing status, invoices, payment method |
| PaymentProfileForm | equa-web/src/modules/payments/components/PaymentProfileForm.tsx | Add/edit credit card or bank account |
| CheckoutFlow | equa-web/src/modules/payments/components/CheckoutFlow.tsx | Multi-step checkout with plan selection and payment |
| InvoiceList | equa-web/src/modules/payments/components/InvoiceList.tsx | View past invoices |
| CouponInput | equa-web/src/modules/payments/components/CouponInput.tsx | Coupon code entry and validation |
Subscriptions Module
| Component | File | Purpose |
|---|---|---|
| SubscriptionManager | equa-web/src/modules/subscriptions/components/SubscriptionManager.tsx | Current plan details, upgrade/downgrade |
| PlanSelector | equa-web/src/modules/subscriptions/components/PlanSelector.tsx | Compare and select subscription tiers |
| UsageMeter | equa-web/src/modules/subscriptions/components/UsageMeter.tsx | Show usage vs. tier limits (e.g. members) |
Routes
| Route | Component | Description |
|---|---|---|
/settings/billing | BillingDashboard | Billing overview and payment methods |
/settings/billing/checkout | CheckoutFlow | New subscription checkout |
/settings/subscription | SubscriptionManager | Current subscription management |
/pricing | PlanSelector | Public pricing / plan comparison |
State Management
Payment and subscription state is split across two module stores. Thepayments store manages payment profiles, invoices, and checkout state. The subscriptions store manages the active subscription, tier details, and usage. Both sync with Chargify-backed API endpoints.
6. Business Rules and Validation
| Rule | Description | Enforcement |
|---|---|---|
| BIL-001 | An organization cannot add members beyond its MemberLimits.memberLimit | Backend |
| BIL-002 | A coupon code can only be redeemed once per user | Backend |
| BIL-003 | Subscription downgrades take effect at the end of the current billing period | Backend (Chargify) |
| BIL-004 | Subscription upgrades take effect immediately with prorated billing | Backend (Chargify) |
| BIL-005 | A valid payment profile is required before creating a subscription | Both |
| BIL-006 | Webhook payloads from Chargify must be signature-verified before processing | Backend |
| BIL-007 | Failed payments trigger dunning workflow managed by Chargify; after final failure, subscription is suspended | Backend (Chargify) |
| BIL-008 | Waitlist position is immutable once assigned; users cannot change their position | Backend |
| BIL-009 | Cancelled subscriptions retain read-only access until the end of the paid period | Backend |
7. Acceptance Criteria
- AC-1: User can view available subscription plans with pricing on the public pricing page
- AC-2: User can add a payment profile (credit card) and complete checkout
- AC-3: Subscription is created in Chargify and reflected in the Equa billing dashboard
- AC-4: Organization member limit is enforced based on subscription tier
- AC-5: User can upgrade subscription; change takes effect immediately with proration
- AC-6: User can downgrade subscription; change takes effect at next billing cycle
- AC-7: User can cancel subscription; access remains until period end
- AC-8: Coupon codes apply correct discounts and prevent duplicate redemption
- AC-9: Chargify webhooks are received, verified, and correctly update local subscription state
- AC-10: Failed payments trigger appropriate user notifications and dunning flow
- AC-11: Waitlist users can check their position and are notified when promoted
- AC-12: Invoice history is accessible from the billing dashboard
8. Risks and Edge Cases
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Chargify API downtime blocks subscription changes | Low | High | Queue failed requests for retry; show user-friendly error with retry option |
| Webhook delivery failure causes state drift | Med | High | Periodic reconciliation job syncs Chargify state; idempotent webhook handler |
| Race condition: member added while downgrade pending | Low | Med | Check limit at member-add time against current (not pending) tier |
| Coupon abuse via code sharing | Med | Low | Rate-limit redemptions; tie coupons to specific campaigns |
| Payment profile PCI compliance | Low | Critical | Chargify handles card storage; Equa never stores raw card numbers |
| Waitlist position disputes | Low | Low | Position assigned server-side via auto-increment; no manual override |
9. Dependencies
| Dependency | Type | Status |
|---|---|---|
| SPEC 001: Authentication | Required before | DRAFT |
| SPEC 002: Organization Management | Required before | DRAFT |
| SPEC 012: Team Members and Roles | Integrates with | DRAFT |
| Chargify (external) | External integration | Active |