Subscriptions
Audience: Sales / Management / Agency-success / Support · Where in app: /agency/subscription-management (overview, plans, addons, history, payment-method) and /agency/subscriptions (per-client list/detail) · Plan availability: Agency tier; payment actions gated by agency.subscription_payments
There are two billing relationships in the agency portal, and this page covers both:
- The agency → Knock Knock. The agency subscribes to an agency package (
AgencyPackageModel) which grants a number of sub-account slots. Stripe bills the agency owner. This is the "Subscription Management" unified screen. - The client → the agency. Each client company has its own
SubscriptionModelpointing at an agency-defined client package. The agency controls these; the client can't self-serve. This is the per-client "Subscriptions" list/detail.
What it does
- Subscription Management (unified, tabbed): Overview, Plans, Add-ons, Payment History, Payment Method.
- Subscribe / change the agency package, with immediate upgrades and deferred downgrades.
- Buy agency add-ons (extra sub-accounts), one-time or recurring.
- View payment history / invoices and manage the payment method.
- Per-client subscriptions list with company + status filters, and a detail view.
- Cancel / reactivate the agency subscription and individual client subscriptions.
How it works
Agency subscription (AgencySubscriptionModel)
One agency subscription row per agency holds package_id (current paid plan), optional new_package_id (a scheduled downgrade), stripe_id, status (active / inactive / canceled), expiry_date, is_trial, and canceled_at / cancel_reason. Subscribing or upgrading creates/updates the Stripe subscription immediately and refreshes total_sub_accounts.
Deferred downgrade is the key behavior: when the agency changes to a cheaper plan (changePackage, POST /agencies/change-package), the change is not applied immediately. Instead:
- Stripe schedules the price change at period end (
scheduleSubscriptionPriceChangeAtPeriodEnd). - Locally,
new_package_idis set;package_idstays on the plan the agency already paid for. - The renewal webhook applies the downgrade (swaps
package_id, recalculates sub-account counts) when the cycle rolls over. - The agency keeps current capacity until then, and gets a "Your plan will change to X on
" response + a downgrade email.
Upgrades (more expensive plan) are charged immediately via Stripe updateSubscription (or, during trial, the trial is canceled and a fresh paid subscription starts).
Per-client subscription (SubscriptionModel)
Each sub-account's subscription is created when the agency provisions/assigns a package. Its expiry_date is derived from the agency's own subscription expiry; remaining_ai_credits comes from the client package. The agency cancels/reactivates these via the company endpoints (see Managing Companies).
Source: frontend/src/views/agency/SubscriptionManagement.vue + frontend/src/components/agency/subscription/*, frontend/src/views/agency/subscriptions/{List,Detail}.vue, and AgencyController.
Configuration & options
Subscription Management tabs
| Tab | Route name | Component | Purpose |
|---|---|---|---|
| Overview | agency-subscription-overview | SubscriptionOverview.vue | Current plan, trial banner/progress, capacity |
| Plans | agency-subscription-plans | SubscriptionPlans.vue | Subscribe / change agency package |
| Add-ons | agency-subscription-addons | SubscriptionAddons.vue | Buy agency add-ons |
| Payment History | agency-subscription-history | PaymentHistory.vue | Invoices |
| Payment Method | agency-subscription-payment | PaymentMethodManager.vue | Manage card |
Legacy redirects: /agency/buy-sub-account → Plans, /agency/subscription → Overview, /agency/invoices → Payment History.
Agency subscription actions (repository → endpoint)
| Action | Repository fn | Endpoint | Notes |
|---|---|---|---|
| Subscribe | subscribeAgency({ package_id, trial }) | POST /agencies/subscribe | Wrapped with Rewardful affiliate data |
| First-time price calc | getSubscribeCalculations(packageId) | POST /agencies/subscribe-calculations | Returns { monthly_price, gst, total_due, currency } |
| Change plan | changeAgencyPackage({ package_id }) | POST /agencies/change-package | Immediate upgrade / deferred downgrade |
| Change calc (proration) | getPackageChangeCalculations(packageId) | POST /agencies/package-change-calculations | Prorated amounts |
| Buy agency add-on | buyAgencyAddon(addonId) | POST /agencies/addon | Extra sub-accounts (one-time or recurring) |
| Cancel agency sub | cancelAgencySubscription({ reason }) | POST /agencies/unsubscribe | Stores cancel_reason |
| Reactivate | reactivateAgencySubscription(subId) | POST /agencies/reactivate-subscription | — |
| Retry failed payment | retryAgencyPayment(subId) | POST /subscription/stripe-payment-retry/:id | — |
| Invoices | getInvoices() | GET /agency/invoices | — |
| AI credit usage | getCreditUsage() | GET /agency/credit-usage | Pooled credit history |
All payment/plan actions require agency.subscription_payments (owner always passes); changePackage returns 403 otherwise.
Per-client subscriptions (subscriptions/List.vue, Detail.vue)
| Control | Repository fn | Endpoint |
|---|---|---|
| List (filter by company + status) | getSubscriptions({ companyId, status, page, pageSize }) | GET /agency/subscriptions |
| Detail | getSubscriptionById(id) | GET /agency/subscriptions/:id |
| Active sub by user | getActiveSubscriptionByUserId(userId) | GET /subscription/active/:userId |
| Cancel client sub | cancelCompanySubscription(companyId) | POST /agencies/companies/:id/cancel-subscription |
| Reactivate client sub | reactivateCompanySubscription(companyId) | POST /agencies/companies/:id/reactivate-subscription |
Status filter values: active, cancelled, expired (UI badge colors map active→green, cancelled→red, expired→gray).
Agency package model fields (AgencyPackageModel)
| Field | Notes |
|---|---|
name, description | Plan identity |
amount, currency (default usd) | Price the agency pays |
sub_accounts | Slot capacity granted |
trial | Whether the plan offers a trial |
stripe_product_id, stripe_price_id | Stripe linkage |
status (active/inactive), package_type (private/public), sort_order | Catalog control |
Agency add-on model fields (AgencyAddonModel)
| Field | Notes |
|---|---|
amount, currency, type (sub_account) | What the add-on grants/costs |
billing_type (one_time / recurring, default recurring) | One-time charge vs. rebills each cycle |
visibility (private/public), status, sort_order | Catalog control |
Behaviors & edge cases
- Deferred downgrade keeps capacity. Until the cycle ends the agency stays on the paid plan;
new_package_idflags the pending change. A downgrade is blocked during trial and blocked if it can't hold the in-use sub-accounts (returns a message telling the agency to remove sub-accounts first). - Upgrades are immediate; trial upgrades cancel the trial and start a fresh paid subscription.
- Billing responsibility split. Stripe bills the agency for the agency package + add-ons. Clients are billed by the agency off-platform — client
SubscriptionModelrows track entitlement/expiry, not a client Stripe charge. - Trial banner. Overview shows an amber "Trial Active" (with progress bar) or red "Trial Expired" state driven by
subscription.is_trial+expiry_date. - Payment issues.
agency.payment_issueflag andcompanyPaymentIssue/dismissCompanyPaymentIssueendpoints surface dunning state; cancel reasons persist on the subscription. - Calculations are tax-inclusive.
subscribe-calculationsreturns GST so the displayed total matches the eventual invoice.
Plan & limits
- Sub-account capacity = the agency package's
sub_accounts+ any sub-account add-ons (verify exact slot counts and prices per plan — none are hard-coded in this cluster). - Trial availability/duration depends on the package's
trialflag and the agency'strial_duration(verify).
Technical implementation
- Views:
frontend/src/views/agency/SubscriptionManagement.vue,frontend/src/components/agency/subscription/*,frontend/src/views/agency/subscriptions/{List,Detail}.vue - Routes:
frontend/src/router/agency.js(subscription-management children +agency-subscriptions,agency-subscription-detail) - Repository:
frontend/src/repositories/agency/index.js - Controller:
backend/app/controllers/AgencyController.js(subscribe,changePackage,unsubscribe,reactivateSubscription,buyAddon,subscribeCalculations,packageChangeCalculations) — see backend service - Models:
AgencySubscriptionModel(incl.new_package_idfor deferred downgrade),AgencyPackageModel,AgencyAddonModel,SubscriptionModel(per-client)
Related
- Agency Portal — Overview
- Packages — client packages vs. agency packages
- Managing Companies — per-client cancel/reactivate
- Dashboard