> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC 011: Billing and Subscriptions

> Subscription management, payment processing, and billing via Chargify integration

# 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:**

```
equa-web/src/modules/payments/
├── components/   # Billing dashboard, payment profiles, checkout
├── services/     # API client for billing operations
├── store/        # Payment and subscription state
└── index.tsx

equa-web/src/modules/subscriptions/
├── components/   # Subscription management, plan selection, upgrade/downgrade
├── services/     # API client for subscription operations
├── store/        # Subscription state
└── index.tsx
```

**Frontend service layer:**

```
equa-web/src/service/services/payments   # Shared payment service utilities
```

**Backend module:**

```
equa-server/modules/billing/
├── src/
│   ├── chargify/      # Chargify API client and webhook handlers
│   ├── services/      # Billing business logic
│   └── entity/        # Billing-related entities
```

**Backend endpoints:**

```
equa-server/modules/api/src/endpoints/billing-endpoints.ts
```

## 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

```mermaid theme={null}
erDiagram
    ORGANIZATIONS ||--o| MEMBER_LIMITS : "constrained by"
    USERS ||--o{ USER_COUPONS : "redeems"
    USERS ||--o| WAITLISTS : "queued in"
```

### 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).

The local `MemberLimits` entity is updated when subscription tier changes are confirmed by Chargify.

## 4. API Endpoints

Served by `equa-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. The `payments` 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 |
