Features

Invoices

Full invoice lifecycle management — from draft to paid. Generate Stripe payment links, sync to Google Sheets, and record payments with a single click.

Invoice Lifecycle

Every invoice moves through four statuses. The system automatically marks invoices as overdue when the due date passes.

Draft

The invoice is being prepared. Line items can be added, edited, or removed. Not visible to the client yet.

Sent

Delivered to the client via email or payment link. The due date countdown begins.

Paid

Payment received and recorded. The paid date is stored and the lead status can advance to Paid.

Overdue

The due date has passed without payment. Triggers a notification alert on the dashboard.

Draft → Sent → Paid

Sent invoices automatically become Overdue when past the due date

Stripe Payment Links

When you mark an invoice as "Sent", the app can generate a Stripe Checkout session so your client can pay online. The payment link is stored on the invoice record and can be shared via email.

  • *Requires STRIPE_SECRET_KEY in your environment
  • *Checkout sessions use the invoice amount and number as the product name
  • *Success URL redirects back to the invoice page with a ?paid=true parameter
  • *Webhook support for automatic payment confirmation is optional
src/lib/stripe.ts
// Generate a Stripe payment link for an invoice
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function generatePaymentLink(invoice: Invoice) {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [{
      price_data: {
        currency: "usd",
        product_data: { name: `Invoice #${invoice.number}` },
        unit_amount: Math.round(invoice.amount * 100),
      },
      quantity: 1,
    }],
    success_url: `${process.env.AUTH_URL}/invoices/${invoice.id}?paid=true`,
    cancel_url: `${process.env.AUTH_URL}/invoices/${invoice.id}`,
  });

  return session.url;
}

Google Sheets Integration

For freelancers who track finances in spreadsheets, the CRM can sync invoice data to a Google Sheet. Each invoice creates or updates a row with the invoice number, client, amount, status, and dates.

  • *Configure GOOGLE_SHEETS_ID in your environment
  • *Rows are appended when invoices are created, updated when status changes
  • *Uses the Google Sheets API v4 — requires a service account
src/lib/sheets.ts
// Sync invoice to Google Sheets
export async function syncToSheets(invoice: Invoice) {
  // Appends a row to your configured Google Sheet
  // Columns: Invoice #, Client, Amount, Status, Due Date, Paid Date
  await sheets.spreadsheets.values.append({
    spreadsheetId: process.env.GOOGLE_SHEETS_ID,
    range: "Invoices!A:F",
    valueInputOption: "USER_ENTERED",
    requestBody: {
      values: [[
        invoice.number,
        invoice.client.name,
        invoice.amount,
        invoice.status,
        invoice.dueDate,
        invoice.paidAt ?? "",
      ]],
    },
  });
}

Recording Payments

Payments can be recorded in two ways:

  • 1.Manual— Click "Mark as Paid" on the invoice detail page. Optionally enter the payment date and reference number.
  • 2.Automatic via Stripe — When a client completes the Stripe checkout, the success redirect triggers an automatic status update to Paid.

Data Model

prisma/schema.prisma
// Invoice model and status
enum InvoiceStatus {
  DRAFT    // Being prepared, not yet sent
  SENT     // Delivered to client
  PAID     // Payment received
  OVERDUE  // Past due date, not paid
}

model Invoice {
  id             String        @id @default(cuid())
  number         String        @unique
  projectId      String
  project        Project       @relation(fields: [projectId])
  clientId       String
  client         Client        @relation(fields: [clientId])
  amount         Float
  status         InvoiceStatus @default(DRAFT)
  dueDate        DateTime
  paidAt         DateTime?
  stripeLink     String?       // Stripe payment link URL
  sheetsRowId    String?       // Google Sheets row reference
  lineItems      LineItem[]
  createdAt      DateTime      @default(now())
}

Related pages

  • Projects — Invoices are tied to projects
  • Dashboard — Revenue metrics from paid invoices
  • Clients — Invoice history per client