Skip to content

SameedKhan12/Saas-Invoice

Repository files navigation

Invoice SaaS

A full-stack invoice management platform built with Next.js 15, Drizzle ORM, Stripe Connect, and Resend. Create and send professional invoices, accept payments online, and automatically deliver receipts to clients.

Dashboard Overview


Table of Contents


Features

  • 🔐 Google OAuth — Secure authentication via NextAuth.js
  • 🧾 Invoice Management — Create, send, and track invoices with line items, due dates, and status tracking
  • 👥 Client Management — Manage your client list with names and contact details
  • 💳 Stripe Connect — Accept payments on behalf of users via Stripe Connect (platform model)
  • 📄 PDF Generation — Auto-generate professional invoice and receipt PDFs using @react-pdf/renderer
  • 📧 Email Delivery — Send invoices and payment receipts via Resend
  • 🔔 Webhook Automation — Automatically mark invoices as paid and send receipts when payment completes
  • 📊 Dashboard Stats — Revenue overview and invoice status breakdown
  • 🔍 Filterable Table — Sort, filter, and paginate invoices with TanStack Table
  • 📱 Responsive — Fully responsive layout built with Tailwind CSS and shadcn/ui

Tech Stack

Category Technology
Framework Next.js 15 (App Router)
Language TypeScript
Database PostgreSQL (via Neon)
ORM Drizzle ORM
Auth NextAuth.js v5 (Google OAuth)
Payments Stripe Connect
Email Resend
PDF @react-pdf/renderer
UI shadcn/ui + Tailwind CSS
Tables TanStack Table v8
Deployment Vercel

Getting Started

Prerequisites

Make sure you have the following installed:

Installation

# Clone the repository
git clone https://github.com/yourusername/invoice-saas.git
cd invoice-saas
 
# Install dependencies
pnpm install
 
# Copy environment variables
cp .env.example .env.local

Environment Variables

Create a .env.local file in the root of the project and fill in the following values:

# App
NEXT_PUBLIC_BASE_URL=http://localhost:3000
 
# Database
DATABASE_URL=postgresql://...
 
# Auth
AUTH_SECRET=your-nextauth-secret
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
 
# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
 
# Resend
RESEND_API_KEY=re_...
RESEND_FROM_EMAIL=noreply@yourdomain.com
 
# Platform
PLATFORM_NAME=Your Company Name
PLATFORM_EMAIL=support@yourdomain.com

Note: Never commit your .env.local file. It is already listed in .gitignore.

Database Setup

This project uses Drizzle ORM. Run the following commands to generate and apply migrations:

# Generate migration files from schema
pnpm drizzle-kit generate
 
# Apply migrations to your database
pnpm drizzle-kit migrate

To inspect your database visually:

pnpm drizzle-kit studio

Usage

Creating an Invoice

  1. Navigate to Invoices in the sidebar
  2. Click New Invoice
  3. Select a client, add line items, set a due date and payment terms
  4. The live preview on the right updates as you type
  5. Click Create Invoice — the invoice is saved with draft status

New Invoice Form


Sending an Invoice

  1. On the Invoices page, find the invoice you want to send
  2. Click the actions menu on the right
  3. Select Send Email
  4. The client receives a professional email with the invoice PDF attached and a Pay Now link
  5. The invoice status automatically updates to pending

Send Invoice

Invoice Email


Accepting Payments

Clients receive a unique payment link (/pay/[invoiceId]). Clicking Pay Now takes them to a Stripe-hosted checkout page. After payment:

  • The invoice is automatically marked as paid
  • The client receives a receipt email with a PDF containing full proof of payment

Pay Page

Stripe Checkout

Payment Success

Receipt Email


Stripe Integration

Stripe Connect Setup

This app uses Stripe Connect (Standard accounts) so that each user can accept payments directly into their own Stripe account.

  1. Go to your Stripe DashboardSettingsConnect
  2. Enable Connect and note your platform client ID
  3. In the app, go to Settings and click Connect Stripe Account
  4. Complete the Stripe onboarding flow
  5. Once connected, your stripeAccountId is saved and clients can pay you directly

Stripe Connect


Webhook Configuration

Webhooks allow Stripe to notify your server when a payment completes.

For local development, use the Stripe CLI:

# Install Stripe CLI
# Windows: https://github.com/stripe/stripe-cli/releases
# Mac: brew install stripe/stripe-cli/stripe
 
# Login
stripe login
 
# Forward webhooks to your local server
stripe listen --forward-to localhost:3000/api/stripe/webhook

Copy the webhook signing secret printed in the terminal and add it to .env.local:

STRIPE_WEBHOOK_SECRET=whsec_...  # ← the one from stripe listen

For production (Vercel):

  1. Go to Stripe DashboardDevelopersWebhooks
  2. Click Add destination
  3. Select Your account (not Connected accounts)
  4. Select event: checkout.session.completed
  5. Choose Webhook as destination type
  6. Set the endpoint URL to:
    https://yourdomain.vercel.app/api/stripe/webhook
    
  7. Click Reveal next to Signing secret → copy the whsec_... value
  8. Add it to Vercel environment variables as STRIPE_WEBHOOK_SECRET
  9. Redeploy your Vercel project

Important: The local CLI secret and the Dashboard secret are different. Never mix them up.

Stripe Webhook


Deployment

This app is designed to deploy on Vercel.

# Install Vercel CLI
pnpm i -g vercel
 
# Deploy
vercel

Or connect your GitHub repository to Vercel for automatic deployments on every push.

Required environment variables on Vercel (add under Project → Settings → Environment Variables):

NEXT_PUBLIC_BASE_URL
DATABASE_URL
AUTH_SECRET
AUTH_GOOGLE_ID
AUTH_GOOGLE_SECRET
STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET
RESEND_API_KEY
RESEND_FROM_EMAIL
PLATFORM_NAME
PLATFORM_EMAIL

After adding or changing environment variables, you must redeploy for changes to take effect.


Project Structure

invoice-saas/
├── app/
│   ├── (auth)/
│   │   └── login/               # Login page
│   ├── (dashboard)/
│   │   ├── page.tsx             # Dashboard home + stats
│   │   ├── clients/             # Client list page
│   │   └── invoices/
│   │       ├── page.tsx         # Invoices table
│   │       └── new/             # Create invoice form + live preview
│   ├── api/
│   │   ├── auth/                # NextAuth handlers
│   │   ├── client/              # Client CRUD
│   │   ├── invoices/            # Invoice CRUD + send + PDF
│   │   └── stripe/
│   │       ├── checkout/        # Create Stripe checkout session
│   │       ├── connect/         # Stripe Connect onboarding
│   │       └── webhook/         # Stripe webhook handler
│   ├── pay/[id]/                # Public client payment page
│   └── success/                 # Post-payment success page
├── components/
│   ├── ui/                      # shadcn/ui components
│   ├── app-sidebar.tsx          # Main sidebar with user dropdown
│   ├── columns.tsx              # TanStack Table column definitions
│   ├── data-table.tsx           # Filterable, sortable invoice table
│   └── invoice-preview.tsx      # Live invoice preview component
├── db/
│   ├── index.ts                 # Drizzle DB client
│   └── schema.ts                # Database schema + types
├── drizzle/                     # Auto-generated migration files
├── lib/
│   ├── auth.ts                  # NextAuth configuration
│   ├── stripe.ts                # Stripe client
│   ├── email.ts                 # Invoice email sender (Resend)
│   ├── receipt-email.ts         # Receipt email sender (Resend)
│   ├── pdf.tsx                  # Invoice PDF generator
│   ├── receipt-pdf.tsx          # Receipt PDF generator
│   └── utils/
│       ├── zodSchema.ts         # Zod validation schemas
│       └── utilityFunctions.ts  # Helper functions
└── public/
    └── logo/                    # App logo assets

Database Schema

users
├── id           uuid  PK
├── email        text  unique
├── password     text
├── stripeAccountId text
└── createdAt    timestamp
 
clients
├── id           uuid  PK
├── userId       uuid  FK → users.id (cascade)
├── name         text
├── email        text
└── createdAt    timestamp
 
invoices
├── id           uuid  PK
├── userId       uuid  FK → users.id (cascade)
├── clientId     uuid  FK → clients.id (cascade)
├── description  text
├── items        jsonb  (InvoiceItem[])
├── amount_cents integer
├── dueDate      timestamp
├── status       enum  (draft | pending | paid | overdue)
├── createdAt    timestamp
└── updatedAt    timestamp

InvoiceItem shape (stored in items JSONB column):

{
  description: string;
  quantity:    number;
  unitPrice:   number; // in dollars (e.g. 50.00)
}

License

MIT License — feel free to use this project as a portfolio piece or starting point for your own SaaS.


Built by Sameed Khan