Skip to main content

SPEC 010 — Payments

FieldValue
StatusDRAFT
PriorityP1 — Core Product
Backendequa-server/modules/billing/
APIequa-server/modules/api/src/endpoints/billing-endpoints.ts
Frontendequa-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

ComponentPath
Billing module rootequa-server/modules/billing/src/
Chargify network layerequa-server/modules/billing/src/network.ts
Billing typesequa-server/modules/billing/src/types.ts
Billing requestsequa-server/modules/billing/src/requests.ts
Billing responsesequa-server/modules/billing/src/responses.ts
Billing readingequa-server/modules/billing/src/reading.ts
Billing writingequa-server/modules/billing/src/writing.ts
Billing utilityequa-server/modules/billing/src/utility.ts
API endpointsequa-server/modules/api/src/endpoints/billing-endpoints.ts

2.2 Frontend

ComponentPath
Payments moduleequa-web/src/modules/payments/
Subscriptions moduleequa-web/src/modules/subscriptions/
Payment service typesequa-web/src/logic/services/payments.ts
Payment service URLsequa-web/src/service/services/payments/urls.ts
Payment service readingequa-web/src/service/services/payments/reading.ts
Payment service writingequa-web/src/service/services/payments/writing.ts
Billing serviceequa-web/src/service/services/billing/
Profile card pagesequa-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):
FieldTypeDescription
apiKeystringChargify API key
subdomainstringChargify account subdomain
ChargifySubscription (billing/types.ts lines 30–38):
FieldTypeDescription
idnumberChargify subscription ID
productChargifyProductProduct reference (id)
activated_atstringActivation timestamp
current_period_ends_atstringnullable, current period end
expires_atstringExpiration timestamp
bank_accountanyBank account details
credit_cardChargifyCreditCardnullable, card details
ChargifyCreditCard (billing/types.ts lines 17–24):
FieldType
idnumber
billing_addressstring
billing_citystring
billing_statestring
billing_zipstring
billing_countrystring

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)

ColumnTypeDescription
useruuidFK → Users
couponvarcharCoupon/promo code
Source: schema.ts (UserCoupons entity)

4. API Endpoints

Product and Pricing

MethodPathAuth GuardDescription
GET/billing/productNoneList available products/plans
GET/billing/member/pricingNoneGet per-member price points

Subscriptions

MethodPathAuth GuardDescription
GET/organization/:organization/subscriptioncanEditOrganizationBillingList org subscriptions
GET/organization/:organization/featurescanViewOrganizationList org features/entitlements
POST/organization/:organization/subscriptioncanEditOrganizationBillingCreate new subscription
POST/organization/:organization/subscription/previewcanEditOrganizationBillingPreview purchase (price calculation without committing)
DELETE/organization/:organization/subscription/:subscriptioncanEditOrganizationBillingCancel subscription

Payment Profiles

Payment profiles are scoped to entities (not organizations), allowing the same entity to manage payment methods across multiple organizations.
MethodPathAuth GuardDescription
POST/entity/:entity/payment/profilecanEditEntityBillingCreate payment profile
GET/entity/:entity/payment/profilecanEditEntityBillingList payment profiles
PUT/entity/:entity/payment/profile/:profilecanEditEntityBillingUpdate payment profile
PUT/entity/:entity/payment/profile/:profile/defaultcanEditEntityBillingSet as default profile
DELETE/entity/:entity/payment/profile/:profilecanEditEntityBillingDelete payment profile

Billing History and Promo

MethodPathAuth GuardDescription
GET/organization/:organization/billing/transactioncanEditOrganizationBillingList billing transactions
POST/promo/validateNoneValidate promotional code
POST/subscriptions/:subscription/adjustmentsNoneCreate billing adjustment
Source: billing-endpoints.ts lines 34–161

5. Frontend Components

Pages

ComponentFilePurpose
BillingPagepayments/pages/billing-page.tsxCurrent subscription, payment profiles, billing history
SelectYourPlanPagepayments/pages/select-your-plan-page.tsxPlan selection with feature comparison
CheckoutPagepayments/pages/check-out-page.tsxPayment execution with profile selection, EquaCash, promo
NewPaymentProfilePagepayments/pages/new-payment-profile.tsxAdd credit card or bank account
EditPaymentProfilePagepayments/pages/edit-payment-profile.tsxUpdate payment method

Components

ComponentFilePurpose
PaymentProfilespayments/components/payment-profiles.tsxPayment method selector (radio list)
CartSummarypayments/components/cart-summary.tsxOrder total with line items (desktop sidebar)
CartSummaryModalpayments/components/cart-summary-modal.tsxOrder total (mobile modal)
FeaturesTablepayments/components/features-and-additional-services.tsxPlan feature comparison matrix
PaymentHistorysubscriptions/components/payment-history.tsxTransaction history table

Routes

RouteComponent
/organization/:organization/billingBillingPage
/organization/:organization/checkoutCheckoutPage
/organization/:organization/select-your-planSelectYourPlanPage
/organization/:organization/payment-profile/newNewPaymentProfilePage
/organization/:organization/payment-profile/:profile/editEditPaymentProfilePage

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):
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

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

RuleDescriptionEnforcement
Entity-scoped payment profilesPayment profiles belong to entities, not organizations. An entity can have profiles used across multiple orgs.API path: /entity/:entity/payment/profile
Default profileOne profile can be set as default per entity via the dedicated endpointBackend sets default, returns updated list
Subscription previewBefore committing, /subscription/preview calculates the exact charge including promo codes and EquaCashBackend Chargify API call
EquaCash offsetIf EquaCash balance covers the full amount (total_in_cents === 0), no payment profile is neededFrontend skips profile selection
Promo code validationCodes are validated against Chargify via POST /promo/validate before applyingBackend validates, frontend stores discount
Terms acceptanceacceptedTerms must be true to create a subscriptionFrontend form validation
Unauthenticated endpointsProduct listing, pricing, promo validation, and adjustments require no authBy design — public pricing info
Permission guardsSubscription management requires canEditOrganizationBilling; payment profiles require canEditEntityBillingAPI endpoint guards
Billing adjustmentAdjustments are posted directly to Chargify by subscription IDNo 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

RiskImpactMitigation
Chargify API downtimeUsers cannot create subscriptions or manage profilesDisplay clear error messages; consider caching product/pricing data
Adjustment endpoint has no auth guardUnauthorized billing adjustmentsAdd canEditOrganizationBilling guard to adjustments endpoint
Credit card data handlingPCI compliance requirementsCard data sent directly to Chargify; Equa server never stores full card numbers
EquaCash balance race conditionTwo concurrent checkouts could double-spend creditsSerialize EquaCash deductions on the server
Subscription state syncLocal state may diverge from Chargify’s actual stateAlways fetch fresh subscription data from Chargify on page load
Expired credit cardRenewal failureChargify handles dunning; surface renewal failure in billing page
Promo code abuseSame code used multiple timesChargify tracks usage limits per code
Entity vs organization confusionUsers may not understand why profiles are entity-scopedUI should explain that payment methods are linked to their account, not the organization

9. Dependencies

DependencyTypeStatus
001-Authentication (user sessions)Required beforeDRAFT
002-Organization ManagementRequired beforeDRAFT
011-Billing and Subscriptions (feature entitlements)Integrates withDRAFT
012-Team Members and Roles (billing permissions)Required beforeDRAFT