# Invoice generation and retrieval is implemented

- **Pattern:** `ab-002257` (`saas-billing.subscription-mgmt.invoice-generation`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002257
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002257)

## Why it matters

Users who cannot retrieve their own invoices open support tickets for every tax filing, expense report, and accounting reconciliation, burning engineering and support cycles on a solved problem. For B2B customers, missing invoice access blocks procurement workflows — finance teams will not pay invoices they cannot download as PDFs with proper tax metadata. This maps directly to user-experience taxons and creates churn risk when customers cannot close their books at month-end.

## Severity rationale

Medium because it causes sustained support load and B2B procurement friction without directly exposing data or blocking sign-ups.

## Remediation

Add a Stripe Customer Portal session endpoint and link it from the account settings page — the portal handles invoice listing, PDF downloads, and receipts with zero custom UI. Create `app/api/billing/portal/route.ts` that calls `stripe.billingPortal.sessions.create({ customer, return_url })` and redirect the user to `portalSession.url`.

## Detection

- **ID:** `invoice-generation`
- **Severity:** `medium`
- **What to look for:** Determine whether users can access their billing history and download invoices. Look for a billing history page, a customer portal integration, or an API endpoint that retrieves invoices from Stripe. Check for Stripe Customer Portal integration (`stripe.billingPortal.sessions.create()`), or a custom invoice listing that calls `stripe.invoices.list({ customer: customerId })`. Also look for whether invoices are stored locally or fetched from Stripe on demand.
- **Pass criteria:** Count every invoice access mechanism. Users can access their invoice history through at least 1 of: Stripe Customer Portal (which handles this automatically), a custom billing page that fetches invoices from Stripe's API, or a webhook-triggered local store of invoice data. Invoices must be accessible to the user without manual intervention.
- **Fail criteria:** No invoice access mechanism exists — users have no way to see or download their billing history. Invoice data is stored locally but never populated (no webhook handler for `invoice.paid`).
- **Skip (N/A) when:** One-time purchases only (no subscription billing where ongoing invoices are generated). Signal: subscription-related dependencies and schema fields are absent.
- **Detail on fail:** `"No billing history page or Stripe Customer Portal integration — users cannot access invoices"` or `"Invoice page exists but fetches from local DB that is never populated — no invoice.paid webhook handler"`
- **Remediation:** The simplest approach is Stripe Customer Portal, which handles invoices, payment method updates, and cancellation without custom code:

  ```ts
  // app/api/billing/portal/route.ts
  export async function POST(req: Request) {
    const session = await getServerSession()
    const user = await db.user.findUnique({ where: { id: session.userId } })

    const portalSession = await stripe.billingPortal.sessions.create({
      customer: user.stripe_customer_id,
      return_url: `${process.env.NEXT_PUBLIC_APP_URL}/settings/billing`,
    })

    return Response.json({ url: portalSession.url })
  }
  ```

Taxons: user-experience

HTML version: https://auditbuffet.com/patterns/ab-002257
