Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesSubscriptions PlansSubscriptions & Plans — Code Map

Subscriptions & Plans — Code Map

API

apps/api/src/plans/

  • plans.module.ts
  • plans.controller.ts@Controller('organizations/:orgId/plans'). RequiresFeature('membership_plans') on mutating routes.
  • plans.service.tscreate | findAll | findById | update | remove | purchase | resumeCheckout. Internal requireFeature helper for service-layer tier checks. resumeCheckout is a thin front door onto purchase (resumeSubscriptionId pins the reuse branch to one row) so the checkout mechanics exist once.
  • plans.service.unit.spec.ts — unit tests.
  • dto/
    • create-plan.dto.ts
    • update-plan.dto.ts
    • purchase-plan.dto.ts{ successUrl, cancelUrl, replacesSubscriptionId }.

apps/api/src/subscriptions/

  • subscriptions.module.ts
  • subscriptions.controller.ts — both member-scoped (/subscriptions/my/*) and admin-scoped (/subscriptions/:id/*) routes.
  • subscriptions.service.ts — lifecycle. Includes sweepDueCancellations (plus its repairStuckScheduledCancellations pass) invoked by the cron, memberCancelPending, memberWithdrawScheduled / findWithdrawBlocker / applyScheduledWithdrawal, staffVoidPendingSubscription and recordCheckoutReturn. The module-level isMemberRolledBackCheckout helper is what hides a member’s own rolled-back checkout from getMySubscriptions.
  • member-action.tsresolveMemberAction (legacy singular, one action) and resolveMemberActions (authoritative array). Both resolve from the same predicates the endpoints enforce, so a listed action is always one the API accepts.
  • subscription-display-status.ts — the terminal-status split, plus PRESALE_WITHDRAWN_REASON, isPresaleWithdrawal and resolveCheckoutReleasedBy (which of the three writers released an abandoned checkout). Deliberately dependency-free (no Nest service imports) so label logic stays unit-testable.
  • presale-withdraw.int.spec.ts — withdraw happy path, seat release, re-purchase, the three refusals, the use_withdraw redirect, and the cron repair pass.
  • checkout-decision.int.spec.ts — regret rollback + member-list hiding, the rows that must stay visible (swept, staff-dismissed), transaction metadata, staff void-pending, checkout-return telemetry.
  • cancellation-cron.service.ts@Cron('30 2 * * *') flips cancel_at_period_end rows to cancelled.
  • cancellation-requests.controller.ts — request submission + owner approve/reject.
  • cancellation-requests.service.ts — owns the cancellation_requests table lifecycle.
  • cancellation-flow.int.spec.ts — integration test for the full request → approve → refund handshake.
  • plan-change.controller.ts (FIT-254 §4) — preview, org change, cancel-scheduled, member self-serve change. One controller, no class-level route prefix (member vs. staff routes have different shapes).
  • plan-change.service.tsPlanChangeService: previewChangePlan, orgChangePlan, memberChangePlan, cancelScheduledChange, plus the three immediate-variant private methods (applyImmediateCharge, applyImmediateComp, scheduleChange). Depends on PlansService via forwardRef (member checkout delegates to purchase).
  • plan-change-math.ts — pure proration math, zero framework dependency: computeDueNow, classifyDirection, PLAN_CHANGE_INTERVAL_DAYS.
  • plan-change-eligibility.tsenforcePlanChangeEligibility, the standalone §4.1 eligibility matrix shared by every plan-change entry point (including PlansService.purchase’s replacesSubscriptionId path) without a circular constructor dependency.
  • plan-change.service.driver.ts, plan-change.service.unit.spec.ts, plan-change-math.unit.spec.ts — tests.
  • dto/
    • enroll-member.dto.ts
    • change-plan.dto.ts — org-initiated { newPlanId, timing, billing? }.
    • member-change-plan.dto.ts — member self-serve { newPlanId, successUrl?, cancelUrl? }.
    • resume-checkout.dto.ts{ successUrl?, cancelUrl?, failedUrl? }; same shape and same loose validation as purchase-plan.dto.ts.
    • cancel-pending.dto.ts — optional { intent?, source? }; an empty body is today’s behavior.
    • checkout-return.dto.ts{ outcome, client } for the telemetry endpoint.

Web (apps/web/src/)

  • app/[lang]/(protected)/dashboard/plans/ — plan CRUD UI.
  • app/[lang]/(protected)/dashboard/payments/ — owner-side payments console (includes a Cancellations tab driven by components/payments/cancellation-requests-list.tsx).
  • app/[lang]/(protected)/dashboard/members/ — admin sub view, freeze/cancel actions.
  • components/payments/cancellation-requests-list.tsx — owner approve/reject UI.
  • components/payments/cancellations-widget.tsx — dashboard counts (pending requests + scheduled cancels).
  • components/member/plan-card.tsx — member-facing subscription summary.
  • components/member/change-plan-sheet.tsx (+ .driver.tsx, .int.spec.tsx) — member self-serve “Change plan” sheet (FIT-254 §4.5): preview → upgrade redirect or downgrade confirm.
  • components/member/scheduled-plan-change-banner.tsx (+ .driver.tsx, .int.spec.tsx) — member-facing scheduled-change banner; shows currentPeriodEnd (see behavior.md’s known W4 inconsistency vs. the staff badge).
  • components/member/switch-plan-picker-dialog.tsx — shop “Switch to this plan” entry point when the member holds several subscription-type subs.
  • components/overview/members/member-memberships-tab.tsx — staff member-detail Memberships tab: subscription list, scheduled-change badge (shows planChangeScheduledAt), freeze/cancel/change-plan actions.
  • components/overview/members/payment-methods-card.tsx — staff-facing card-on-file card (FIT-254 §3.1).
  • components/members/manual-charge-dialog.tsx, components/members/collect-debt-dialog.tsx — staff desk-charge and debt-collection dialogs (FIT-254 §3.3/§3.4).
  • app/[lang]/(protected)/(member)/profile/payments/page.tsx — member profile payments page; hosts the change-plan entry point and scheduled-change banner.

Booking-entitlement sweep (FIT-254 §4.6/§5)

  • apps/api/src/bookings/booking-entitlement-sweep.service.tsBookingEntitlementSweepService: plan/planForTargetPlan (zero-write preview), apply/applyInPlace (writes). Flag key booking-entitlement-sweep.
  • apps/api/src/bookings/booking-entitlement-sweep.module.ts — module wiring (kept separate from BookingsModule to avoid the DI cycle documented in the service’s doc-comment).
  • apps/api/src/bookings/booking-entitlement-sweep.service.driver.ts, .unit.spec.ts — tests.
  • apps/api/src/bookings/booking-enforcement.util.ts — shared low-level primitives (dayBoundsUtc, weekBoundsUtc, sessionsOverlap, deductCreditAtomic, refundCreditAtomic, assertCreditAvailable, tryDeductCreditAtomic, promoteFromWaitlist, withSerializableRetry) extracted out of BookingsService so the sweep engine can reuse them without a DI cycle back through it.
  • C3 (booking-horizon guard) lives inline in apps/api/src/bookings/bookings.service.ts book() — no separate file.
  • C4 call sites: apps/api/src/subscriptions/subscriptions.service.ts (sweepOnEnd, immediate cancel + period-end cron), apps/api/src/payments/services/recurring-charge.service.ts (sweepOnDebt, 3rd renewal failure), apps/api/src/payments/services/webhook-processing.service.ts (sweepOnDebt, webhook-driven failure).

Shared

  • libs/shared/src/lib/schemas/plan.schema.ts — Zod schemas for PlanResponse + the plan-change request/response types (ChangePlanRequest, MemberChangePlanRequest, MemberChangePlanResponse, ChangePlanPreviewResponse, PreviewBooking, direction/timing/billing enums).
  • libs/shared/src/lib/schemas/membership.schema.tsSubscriptionResponse, SubscriptionWithPlan.
  • libs/shared/src/lib/constants/payment-error-codes.tsPaymentErrorCodes registry (FIT-254): NO_ACTIVE_PAYMENT_METHOD, MEMBERSHIP_NOT_ACTIVE, PROVIDER_CHARGE_UNVERIFIED, PAYMENT_METHOD_IN_USE, OUTSTANDING_BALANCE, PENDING_ACTION_CONFLICT, RESUME_FIRST, BOOKING_BEYOND_SUBSCRIPTION_END, RENEWAL_CHARGE_FAILED, PLAN_CHANGE_CHARGE_FAILED.
  • libs/shared/src/lib/constants/feature-flags.tsFeatureFlags.ADMIN_CARD_ON_FILE / .ADMIN_PLAN_CHANGE / .MEMBER_PLAN_CHANGE (the booking-entitlement-sweep key is defined directly on BookingEntitlementSweepService.FLAG_KEY, not duplicated here).

DB

  • libs/db/src/lib/schema/payments.tsplans, subscriptions (+ FIT-254’s scheduled_plan_id/plan_change_scheduled_at/changed_from_subscription_id), cancellation_requests.
  • libs/db/src/lib/schema/enums.tsplanType, planInterval, subscriptionStatus, cancellationRequestStatus, membershipPaymentStatus.
  • libs/db/src/lib/schema/memberships.tsmemberships.paymentStatus is mutated by the renewal cron + webhook processor.
  • libs/db/drizzle/0090_naive_roughhouse.sql — migration adding the three plan-change columns + their FKs. Not yet applied to any real environment (human to-do — see data-model.md).

Notifications

  • apps/api/src/notifications/cancellation-notifications.service.tscancellationScheduled, cancellationRequestSubmitted, cancellationRequestApproved, cancellationRequestRejected, newCancellationRequestForOwner, refundCompleted.
  • apps/api/src/notifications/templates/payment-failed.ts, debt-warning.ts, payment-receipt.ts.
  • apps/api/src/notifications/sweep-notifications.service.ts (FIT-254) — SweepNotificationsService: sendPlanChangeSwept, sendSubscriptionEndedSwept, sendPlanChangeScheduled, sendPlanChangeScheduleCancelled. Resolves recipient locale (device → org → en) and org branding.
  • apps/api/src/notifications/templates/entitlement-sweep.tsplanChangeSweepEmail, subscriptionEndedSweepEmail. Localized en/he/ru via dictionaries[locale].entitlementSweepEmail.copy.
  • apps/api/src/notifications/templates/plan-change-scheduled.tsplanChangeScheduledEmail, planChangeScheduleCancelledEmail. Localized en/he/ru.

Cross-references

  • apps/api/src/payments/services/webhook-processing.service.ts — mutates subscriptions on payment events; hosts the plan-change checkout activation hook (§4.3).
  • apps/api/src/payments/services/recurring-charge.service.ts — mutates subscriptions and memberships.paymentStatus daily; hosts the scheduled-change boundary swap (§4.4, PD-B5a).
  • apps/api/src/payments/services/manual-charge.service.tsCHARGE_VERIFIED_PROVIDERS, imported by plan-change.service.ts for the org saved-card charge variant.
  • apps/api/src/automations/automation-scheduler.service.tssweepEnded’s plan_change / replacement-sub exclusions (§4.8, PD-B9).
  • apps/api/src/bookings/ — calls subscriptionsService.deductCredit / refundCredit; bookings.service.ts hosts C3 inline.
  • apps/api/src/courses/course-checkout.service.ts — uses plan.type='course' rows, bypasses plans.purchase.