Finance module

The Finance module covers invoicing, payment collection, expenses, and revenue reporting. It integrates with Stripe for customer-facing payment links and emits webhook events on every state change so external systems stay in sync.

Entities

EntityWhat it represents
InvoiceIssued bill to a customer. Has invoice_number, status (draft/sent/overdue/paid), due_date, line items, currency, total.
Recurring invoiceA template that the daily cron clones into new invoices on a frequency (weekly/biweekly/monthly/quarterly/annually).
PaymentA received payment. Has amount, method, reference, optional stripe_session_id linking back to a Checkout Session.
ExpenseAn outgoing cost. Has vendor, category, amount, receipt URL, billable flag.
Payment linkA Stripe Checkout Session for an invoice; can be regenerated, expires after 24h.

Schema lives under the finance schema with backward-compatible views in public (e.g. public.invoicesfinance.invoices).

Invoice lifecycle

Invoices move through five states:

draft → sent → overdue → paid
              ↘ paid

State transitions emit finance.invoice.* events. The most useful ones for integrations:

EventFires when
finance.invoice.sentInvoice emailed to the customer.
finance.invoice.paidStripe webhook confirms payment OR a payment is manually recorded that brings the total to >= invoice.total.
finance.invoice.overdueDaily reminder cron flips sent → overdue for invoices past due.

Payment links (Stripe)

Customer-facing payment via Stripe Checkout. The flow:

  1. Operator clicks Payment link on an invoice in the dashboard.
  2. We create a Stripe Checkout Session in payment mode tagged with ccd_invoice=true + the tenant + invoice id.
  3. Operator copies the URL and shares with the customer.
  4. Customer pays; Stripe fires checkout.session.completed.
  5. Our webhook handler writes a payments row keyed on the session id (UNIQUE — duplicate delivery is a no-op) and marks the invoice paid + emits finance.invoice.paid.

Sessions are idempotent: a second click on Payment link while the previous session is still open returns the existing URL.

Recurring invoices

Define a recurring template, then the daily recurring-invoices cron generates fresh invoices on schedule. Each generation:

  • Clones the template's line items + customer + currency.
  • Stamps a unique invoice number INV-YYYYMM-NNNN.
  • Sets issue_date=today, due_date=today+30.
  • If auto_send=true, emails the customer + flips status to sent (in a single atomic step that's safe to retry).

Deactivates when end_date passes.

Invoice reminders

Daily cron walks invoices status IN ('sent','overdue') with due_date < today and emails a polite nudge to the contact. A per-invoice 7-day cooldown prevents nagging. Flips sent → overdue on the first reminder.

Expense tracking

Lightweight: expense rows have a vendor, category, amount, and optional billable flag. Billable expenses flagged against a project surface as draft invoice line items the same way billable time entries do.

Receipt upload goes to the content-assets storage bucket scoped to the tenant; the file URL is stored on the row.

Cross-module insights

  • Finance × CRM: deal + linked-contact's invoice history on the contact detail page.
  • Finance × Projects: billable time → draft invoice generator.
  • Finance × Analytics: scheduled reports for revenue by month, invoice aging, and pipeline-to-revenue attribution.

Permissions

finance.invoices.read|create|update|delete, finance.payments.read|create|update|delete, etc. Operator-only endpoints (payment-link creation, recurring template edits) gate on finance.invoices.update.

Read next

  • Finance API — invoice/payment-link endpoints + Stripe webhook reference.
  • Analytics module — where scheduled revenue reports live.