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 |
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 |
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. Whentotal_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 |
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 |
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:
- Select payment profile — User picks from existing profiles or adds new one
- Apply EquaCash — Platform credits offset the total
- Enter promo code — Optional coupon discount
- Preview purchase — Calls
/subscription/previewto show final price - Submit — Creates subscription via
POST /organization/:organization/subscription - Modals — Processing spinner, success confirmation, or error notification
selectedProfile: number— Currently selected payment profile IDcouponValue: number— Applied coupon discountremove: boolean— Whether removing a profile
helpers/enum.ts):
total_in_cents !== 0 and no profile is selected, or when total is fully covered by EquaCash.
Request/Response Types
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
canEditOrganizationBillingcan manage subscriptions - Only users with
canEditEntityBillingcan 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 |