Payments & Invoices
Audience: Sales / Finance / Management (engineers for the technical section) · Where in app: Sidebar → Billing → Payments / Invoices / Card Management · Plan availability: All plans
All card payments run through Stripe. Admins manage their saved cards and view invoices under Billing. Knock Knock mirrors each invoice into its own database (InvoiceModel) so the dashboard and Nox can query billing history quickly without hitting Stripe on every request. Finalized invoices are also pushed into Xero for accounting.
What it does
- Stores and charges payment methods (cards) via Stripe.
- Renders the tenant's invoice history with download links.
- Drives dunning (retry + reminder emails) when a charge fails.
- Tracks outstanding balance, renewal date, and failed-payment history for support and Nox.
How it works
Each company is linked to a Stripe customer. Cards are added through Stripe's payment-method UI (Stripe Elements) on the Card Management screen, so raw card data never touches Knock Knock servers. Multiple cards are allowed; one is the default used for subscription charges.
Stripe webhooks drive the lifecycle. On invoice.created the invoice is finalized only if still draft and paid only if not already paid (guards added — the webhook no longer re-finalizes non-draft invoices, and payment is idempotent on retries); on invoice.paid / invoice.payment_succeeded the subscription is set active, AI credits are refilled, the trial flags clear, an InvoiceModel record is created (if missing), and a Xero invoice is created. On invoice.payment_failed the subscription enters dunning. (Webhook receiver: POST /stripe/webhook, backend/app/controllers/StripeController.js.)
Configuration & options
Card management
- Where: Billing → Card Management.
- Add a card (Stripe Elements), set a default, remove a card.
- You cannot remove the only card on file while there is an active paid subscription — the delete endpoint blocks the last card.
- Routes:
GET /cards/all(list),GET /cards/expiring(cards expiring within 2 months — drives proactive reminders),POST /cards/enhanced(add, optionally set default),PUT /cards/default(set default for the customer and all active subscriptions),DELETE /cards/:payment_method_id.
Invoice record (InvoiceModel)
InvoiceModel mirrors each charge for fast querying. The real field names (not the generic Stripe ones):
| Field | Meaning |
|---|---|
user_id, subscription_id, agency_id | Owner, subscription, and agency (agency optional) |
invoice_status | Status string, default pending (e.g. pending / paid) |
amount, currency, quantity | Amount, currency, line quantity |
is_trial | Whether the invoice is for a trial period |
transaction_id | Payment transaction reference |
receipt_url | Hosted receipt link |
payment_method | Method used |
paid_at | Stamped automatically by a pre-save hook when invoice_status === 'paid' |
additional_users, additional_prices[] | Extra-seat and extra line items (name + amount) |
stripe_id, stripe_subscription_details | Stripe linkage |
xero_invoice_id, xero_invoice_number, xero_invoice_link, xero_payment_id | Xero accounting linkage |
Visible in Billing → Invoices (GET /invoice). Each row links to a receipt and, where present, a Xero invoice link (fetched on the fly if xero_invoice_id exists but the link is missing).
paid_at is stamped on all paid invoices (via the pre-save hook above). A past bug left it unset on some paid invoices, which made them invisible to the admin revenue report; that has been fixed and a backfill run. If you find a paid invoice with no paid_at, flag it — it predates the fix.
What gets billed
- The monthly subscription (per the package price).
- Add-ons — charged immediately as one-time line items; they accumulate.
- Manual line items — agency-side, see Agency Overview.
Behaviors & edge cases
Failed payments (dunning) — QA/support
invoice.payment_failedfires →SubscriptionModel.last_failed_payment_atis stamped,expiry_dateis pulled back to now (if it was still in the future), and a payment-failure email goes out with a link to the card-management screen.- Stripe retries on its configured schedule and sends reminder emails.
- If never paid → the subscription degrades (status reflects the failure; on
customer.subscription.deletedit goesinactiveandauto_paymentis turned off).
The admin can retry a failed charge: flag via GET /subscription/payment-retry/:_id, then POST /subscription/stripe-payment-retry/:_id charges the customer's default payment method again.
companies.grace_period_claimed (bool) tracks whether the tenant's one-time grace period has been used. After that there is no further grace at the policy boundary.
Refunds
- Admin-initiated via the Stripe dashboard only. There is no self-serve refund flow in the app.
Tax
- Stripe Tax (when enabled) handles VAT / GST / sales tax. Tenant addresses live on
companies.billing_addresswhen provided.
Agency-managed companies
- Agency-managed companies do not see their invoices here — the agency invoices the client directly, and the agency in turn pays Knock Knock for the underlying platform cost. Sub-account add-ons flow through the agency. See Agency Overview.
Other QA notes
- Invoice numbering is Stripe-controlled and not customizable.
- PDF downloads require the Stripe invoice to be finalized — drafts have no PDF (
invoice_pdfempty).
Plan & limits
- Payments and invoices are available on all plans.
- No per-plan limit on saved cards or invoice history is documented here — verify if asked.
Technical implementation
- Models:
InvoiceModel(mirror of Stripe invoices),companies.stripe_customer_id,companies.grace_period_claimed,companies.billing_address. - Stripe: customer + payment-method management (Stripe Elements), invoice finalization, dunning, Stripe Tax, and webhooks that sync invoice status into
InvoiceModel. Handled by the Stripe service in the backend. - Revenue reporting: the admin revenue screen groups paid invoices by
paid_atand currency — see the note above about thepaid_atbackfill. - Affiliate tracking: FirstPromoter runs alongside Rewardful (
backend/app/configs/firstpromoter.js; hooks inAuthControllersignup andStripeServicecharge paths). - Wallet transactions (telephony top-ups/debits) are a separate ledger — see Wallet.