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

# Payments

> Payment profiles, subscriptions, billing transactions, promo codes, and Chargify integration

# SPEC 010 — Payments

| Field    | Value                                                                   |
| -------- | ----------------------------------------------------------------------- |
| Status   | DRAFT                                                                   |
| Priority | P1 — Core Product                                                       |
| Backend  | `equa-server/modules/billing/`                                          |
| API      | `equa-server/modules/api/src/endpoints/billing-endpoints.ts`            |
| Frontend | `equa-web/src/modules/payments/`, `equa-web/src/modules/subscriptions/` |

***

## 1. Feature Purpose

Payments handles the commercial side of the Equa platform: subscription plan selection, checkout, payment profile management (credit cards and bank accounts), billing history, promotional discounts, and EquaCash (in-platform credits). The backend integrates with **Chargify** (not Stripe) for all payment processing, subscription lifecycle, and billing.

***

## 2. Current State (Verified)

### 2.1 Backend

| Component              | Path                                                         |
| ---------------------- | ------------------------------------------------------------ |
| Billing module root    | `equa-server/modules/billing/src/`                           |
| Chargify network layer | `equa-server/modules/billing/src/network.ts`                 |
| Billing types          | `equa-server/modules/billing/src/types.ts`                   |
| Billing requests       | `equa-server/modules/billing/src/requests.ts`                |
| Billing responses      | `equa-server/modules/billing/src/responses.ts`               |
| Billing reading        | `equa-server/modules/billing/src/reading.ts`                 |
| Billing writing        | `equa-server/modules/billing/src/writing.ts`                 |
| Billing utility        | `equa-server/modules/billing/src/utility.ts`                 |
| API endpoints          | `equa-server/modules/api/src/endpoints/billing-endpoints.ts` |

### 2.2 Frontend

| Component               | Path                                                                                               |
| ----------------------- | -------------------------------------------------------------------------------------------------- |
| Payments module         | `equa-web/src/modules/payments/`                                                                   |
| Subscriptions module    | `equa-web/src/modules/subscriptions/`                                                              |
| Payment service types   | `equa-web/src/logic/services/payments.ts`                                                          |
| Payment service URLs    | `equa-web/src/service/services/payments/urls.ts`                                                   |
| Payment service reading | `equa-web/src/service/services/payments/reading.ts`                                                |
| Payment service writing | `equa-web/src/service/services/payments/writing.ts`                                                |
| Billing service         | `equa-web/src/service/services/billing/`                                                           |
| Profile card pages      | `equa-web/src/modules/profile/pages/add-card-page.tsx`, `edit-card-page.tsx`, `view-card-page.tsx` |

***

## 3. Data Model

### 3.1 Chargify Integration (External)

Payments does not store subscription or payment data in the Equa database. All payment state lives in Chargify. The backend acts as a proxy/adapter between the Equa API and Chargify's REST API.

**ChargifyConfig** (`billing/types.ts` lines 2–5):

| Field     | Type   | Description                |
| --------- | ------ | -------------------------- |
| apiKey    | string | Chargify API key           |
| subdomain | string | Chargify account subdomain |

**ChargifySubscription** (`billing/types.ts` lines 30–38):

| Field                     | Type               | Description                  |
| ------------------------- | ------------------ | ---------------------------- |
| id                        | number             | Chargify subscription ID     |
| product                   | ChargifyProduct    | Product reference (id)       |
| activated\_at             | string             | Activation timestamp         |
| current\_period\_ends\_at | string             | nullable, current period end |
| expires\_at               | string             | Expiration timestamp         |
| bank\_account             | any                | Bank account details         |
| credit\_card              | ChargifyCreditCard | nullable, card details       |

**ChargifyCreditCard** (`billing/types.ts` lines 17–24):

| Field            | Type   |
| ---------------- | ------ |
| id               | number |
| billing\_address | string |
| billing\_city    | string |
| billing\_state   | string |
| billing\_zip     | string |
| billing\_country | string |

### 3.2 EquaCash

EquaCash is an in-platform credits/balance system that offsets subscription costs during checkout. When `total_in_cents` is covered by EquaCash, no external payment is required.

### 3.3 UserCoupons (Database)

| Column | Type    | Description       |
| ------ | ------- | ----------------- |
| user   | uuid    | FK → Users        |
| coupon | varchar | Coupon/promo code |

Source: `schema.ts` (UserCoupons entity)

***

## 4. API Endpoints

### Product and Pricing

| Method | Path                      | Auth Guard | Description                   |
| ------ | ------------------------- | ---------- | ----------------------------- |
| GET    | `/billing/product`        | None       | List available products/plans |
| GET    | `/billing/member/pricing` | None       | Get per-member price points   |

### Subscriptions

| Method | Path                                                     | Auth Guard                 | Description                                             |
| ------ | -------------------------------------------------------- | -------------------------- | ------------------------------------------------------- |
| GET    | `/organization/:organization/subscription`               | canEditOrganizationBilling | List org subscriptions                                  |
| GET    | `/organization/:organization/features`                   | canViewOrganization        | List org features/entitlements                          |
| POST   | `/organization/:organization/subscription`               | canEditOrganizationBilling | Create new subscription                                 |
| POST   | `/organization/:organization/subscription/preview`       | canEditOrganizationBilling | Preview purchase (price calculation without committing) |
| DELETE | `/organization/:organization/subscription/:subscription` | canEditOrganizationBilling | Cancel subscription                                     |

### Payment Profiles

Payment profiles are scoped to **entities** (not organizations), allowing the same entity to manage payment methods across multiple organizations.

| Method | Path                                               | Auth Guard           | Description            |
| ------ | -------------------------------------------------- | -------------------- | ---------------------- |
| POST   | `/entity/:entity/payment/profile`                  | canEditEntityBilling | Create payment profile |
| GET    | `/entity/:entity/payment/profile`                  | canEditEntityBilling | List payment profiles  |
| PUT    | `/entity/:entity/payment/profile/:profile`         | canEditEntityBilling | Update payment profile |
| PUT    | `/entity/:entity/payment/profile/:profile/default` | canEditEntityBilling | Set as default profile |
| DELETE | `/entity/:entity/payment/profile/:profile`         | canEditEntityBilling | Delete payment profile |

### Billing History and Promo

| Method | Path                                              | Auth Guard                 | Description               |
| ------ | ------------------------------------------------- | -------------------------- | ------------------------- |
| GET    | `/organization/:organization/billing/transaction` | canEditOrganizationBilling | List billing transactions |
| POST   | `/promo/validate`                                 | None                       | Validate promotional code |
| POST   | `/subscriptions/:subscription/adjustments`        | None                       | Create billing adjustment |

Source: `billing-endpoints.ts` lines 34–161

***

## 5. Frontend Components

### Pages

| Component              | File                                       | Purpose                                                   |
| ---------------------- | ------------------------------------------ | --------------------------------------------------------- |
| BillingPage            | `payments/pages/billing-page.tsx`          | Current subscription, payment profiles, billing history   |
| SelectYourPlanPage     | `payments/pages/select-your-plan-page.tsx` | Plan selection with feature comparison                    |
| CheckoutPage           | `payments/pages/check-out-page.tsx`        | Payment execution with profile selection, EquaCash, promo |
| NewPaymentProfilePage  | `payments/pages/new-payment-profile.tsx`   | Add credit card or bank account                           |
| EditPaymentProfilePage | `payments/pages/edit-payment-profile.tsx`  | Update payment method                                     |

### Components

| Component        | File                                                       | Purpose                                       |
| ---------------- | ---------------------------------------------------------- | --------------------------------------------- |
| PaymentProfiles  | `payments/components/payment-profiles.tsx`                 | Payment method selector (radio list)          |
| CartSummary      | `payments/components/cart-summary.tsx`                     | Order total with line items (desktop sidebar) |
| CartSummaryModal | `payments/components/cart-summary-modal.tsx`               | Order total (mobile modal)                    |
| FeaturesTable    | `payments/components/features-and-additional-services.tsx` | Plan feature comparison matrix                |
| PaymentHistory   | `subscriptions/components/payment-history.tsx`             | Transaction history table                     |

### Routes

| Route                                                       | Component              |
| ----------------------------------------------------------- | ---------------------- |
| `/organization/:organization/billing`                       | BillingPage            |
| `/organization/:organization/checkout`                      | CheckoutPage           |
| `/organization/:organization/select-your-plan`              | SelectYourPlanPage     |
| `/organization/:organization/payment-profile/new`           | NewPaymentProfilePage  |
| `/organization/:organization/payment-profile/:profile/edit` | EditPaymentProfilePage |

### Checkout Flow

The checkout page (`check-out-page.tsx`) follows a multi-step process:

1. **Select payment profile** — User picks from existing profiles or adds new one
2. **Apply EquaCash** — Platform credits offset the total
3. **Enter promo code** — Optional coupon discount
4. **Preview purchase** — Calls `/subscription/preview` to show final price
5. **Submit** — Creates subscription via `POST /organization/:organization/subscription`
6. **Modals** — Processing spinner, success confirmation, or error notification

**State:**

* `selectedProfile: number` — Currently selected payment profile ID
* `couponValue: number` — Applied coupon discount
* `remove: boolean` — Whether removing a profile

**Checkout enums** (`helpers/enum.ts`):

```typescript theme={null}
enum Steps { checkout = 0, confirm = 1, thanks = 2 }
enum PaymentSteps { selectPlan = 0, checkout = 1 }
```

**Submit validation:** Disabled when `total_in_cents !== 0` and no profile is selected, or when total is fully covered by EquaCash.

### Request/Response Types

```typescript theme={null}
interface NewAddressRequest {
  street1: string; street2?: string; street3?: string;
  country: string; city: string; postalCode: string; province: string;
}

interface CreditCardRequest {
  firstName?: string; lastName?: string;
  fullNumber?: string; expirationMonth?: number;
  expirationYear?: number; cvv?: string;
}

interface NewPaymentProfileRequest extends OrganizationRequest, PaymentRequest {
  billingAddress: NewAddressRequest;
  creditCard?: CreditCardRequest;
}

interface NewSubscriptionRequest extends PurchaseRequest, PaymentRequest {
  billingAddress?: NewAddressRequest;
  savePaymentInfo?: boolean;
  creditCard?: CreditCardRequest;
  acceptedTerms?: boolean;
  paymentProfile?: number;  // existing profile ID
}

interface SubscriptionResponse {
  product: string | number;
  productPriceInCents: number;
  nextPriceInCents: number;
  lineItems: BillingLineItem[];
  startDate: string;
  renewDate: string;
  bankAccount?: BankAccountResponse;
  creditCard?: CreditCardResponse;
  billingAddress?: NewAddressRequest;
}
```

***

## 6. Business Rules and Validation

| Rule                           | Description                                                                                                    | Enforcement                                 |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| Entity-scoped payment profiles | Payment profiles belong to entities, not organizations. An entity can have profiles used across multiple orgs. | API path: `/entity/:entity/payment/profile` |
| Default profile                | One profile can be set as default per entity via the dedicated endpoint                                        | Backend sets default, returns updated list  |
| Subscription preview           | Before committing, `/subscription/preview` calculates the exact charge including promo codes and EquaCash      | Backend Chargify API call                   |
| EquaCash offset                | If EquaCash balance covers the full amount (`total_in_cents === 0`), no payment profile is needed              | Frontend skips profile selection            |
| Promo code validation          | Codes are validated against Chargify via `POST /promo/validate` before applying                                | Backend validates, frontend stores discount |
| Terms acceptance               | `acceptedTerms` must be true to create a subscription                                                          | Frontend form validation                    |
| Unauthenticated endpoints      | Product listing, pricing, promo validation, and adjustments require no auth                                    | By design — public pricing info             |
| Permission guards              | Subscription management requires `canEditOrganizationBilling`; payment profiles require `canEditEntityBilling` | API endpoint guards                         |
| Billing adjustment             | Adjustments are posted directly to Chargify by subscription ID                                                 | No org-level auth check (potential gap)     |

***

## 7. Acceptance Criteria

* [ ] Users can view available plans with feature comparison on SelectYourPlanPage
* [ ] Credit card payment profiles can be created with billing address and card details
* [ ] One payment profile can be set as default per entity
* [ ] Checkout page shows cart summary with line items and total
* [ ] Promo codes can be validated and applied to reduce the total
* [ ] EquaCash balance is applied to reduce or fully cover the subscription cost
* [ ] When EquaCash covers the total, checkout proceeds without a payment profile
* [ ] Subscription preview shows the exact charge before committing
* [ ] Subscription creation succeeds and shows confirmation
* [ ] Billing history page displays past transactions
* [ ] Only users with `canEditOrganizationBilling` can manage subscriptions
* [ ] Only users with `canEditEntityBilling` can manage payment profiles
* [ ] Product listing and pricing endpoints work without authentication
* [ ] Payment profiles are scoped to entities, not organizations
* [ ] Subscription cancellation works and is confirmed

***

## 8. Risks and Edge Cases

| Risk                                  | Impact                                                  | Mitigation                                                                               |
| ------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Chargify API downtime                 | Users cannot create subscriptions or manage profiles    | Display clear error messages; consider caching product/pricing data                      |
| Adjustment endpoint has no auth guard | Unauthorized billing adjustments                        | Add `canEditOrganizationBilling` guard to adjustments endpoint                           |
| Credit card data handling             | PCI compliance requirements                             | Card data sent directly to Chargify; Equa server never stores full card numbers          |
| EquaCash balance race condition       | Two concurrent checkouts could double-spend credits     | Serialize EquaCash deductions on the server                                              |
| Subscription state sync               | Local state may diverge from Chargify's actual state    | Always fetch fresh subscription data from Chargify on page load                          |
| Expired credit card                   | Renewal failure                                         | Chargify handles dunning; surface renewal failure in billing page                        |
| Promo code abuse                      | Same code used multiple times                           | Chargify tracks usage limits per code                                                    |
| Entity vs organization confusion      | Users may not understand why profiles are entity-scoped | UI should explain that payment methods are linked to their account, not the organization |

***

## 9. Dependencies

| Dependency                                           | Type            | Status |
| ---------------------------------------------------- | --------------- | ------ |
| 001-Authentication (user sessions)                   | Required before | DRAFT  |
| 002-Organization Management                          | Required before | DRAFT  |
| 011-Billing and Subscriptions (feature entitlements) | Integrates with | DRAFT  |
| 012-Team Members and Roles (billing permissions)     | Required before | DRAFT  |
