Skip to main content

Invoice generation and retrieval is implemented

ab-002257 · saas-billing.subscription-mgmt.invoice-generation
Severity: mediumactive

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: saas-billing.subscription-mgmt.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:

    // 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

History