Add full application: receipt scanning, auth, billing, and account deletion

Brings the working codebase (Next.js app, auth system, Stripe billing,
Docker/deploy config, tests, docs) into version control on top of the
placeholder initial commit, and adds account self-deletion (Danger Zone
in Settings, password + typed-email confirmation, cascading DB cleanup,
Stripe cancellation) per GDPR right-to-erasure.

Excludes local build caches, node_modules, and internal agent scratch
files; .gitignore hardened to keep those out going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Timo
2026-08-19 20:59:04 +02:00
parent 650a74da97
commit 84b9987c49
415 changed files with 96619 additions and 0 deletions

315
STRIPE_SETUP_GUIDE.md Normal file
View File

@@ -0,0 +1,315 @@
# Stripe Setup & Integration Guide (ScanReceipts Pro)
This comprehensive guide walks you through configuring **Stripe** for payment processing, subscription management, webhooks, and license generation for **Receipt Scanner to Excel / DATEV (ScanReceipts)**.
---
## 1. Overview & Pricing Architecture
ScanReceipts offers three Pro access tiers:
| Tier / Plan | Price (EUR) | Billing Type | Trial / Terms | Environment Variable |
|---|---|---|---|---|
| **Weekly Pass** | **4,99 €** | Recurring (weekly) | **3-Day Free Trial** | `STRIPE_WEEKLY_PRICE_ID` |
| **Annual Pass** | **39,99 €** | Recurring (yearly) | Full 12 Months Access (~3,33 €/Mo) | `STRIPE_ANNUAL_PRICE_ID` |
| **Lifetime License** | **59,99 €** | One-Time Payment | Perpetual access & all future updates | `STRIPE_LIFETIME_PRICE_ID` |
> 💡 **Inline Fallback**: If you do not create custom Price IDs in Stripe, the application will automatically create ad-hoc inline line items (`price_data`) with the exact prices and trial settings above. Supplying explicit Price IDs is recommended for production accounting and analytics.
---
## 2. Prerequisites & Stripe Account Setup
1. **Sign Up or Log In**: Go to the [Stripe Dashboard](https://dashboard.stripe.com/).
2. **Activate Test Mode**: Toggle the **"Test mode"** switch in the top-right corner of the Stripe Dashboard (the UI will indicate test mode with an orange/yellow banner).
3. Ensure your default currency is set to **EUR (€)**.
---
## 3. Obtaining API Keys
1. Navigate to **Developers > API keys** (`https://dashboard.stripe.com/test/apikeys`).
2. **Publishable key**: Copy the key starting with `pk_test_...`.
3. **Secret key**: Click **"Reveal live key token"** (or create a new restricted/standard key) starting with `sk_test_...`.
4. Add these keys to your `.env.local` file:
```env
STRIPE_PUBLISHABLE_KEY=pk_test_51...
STRIPE_SECRET_KEY=sk_test_51...
```
---
## 4. Creating Products & Prices in the Stripe Dashboard
To track subscriptions and revenue analytics cleanly in Stripe, create the three Pro products:
### 4.1 Product: Weekly Pass (Wochen-Pass)
1. In Stripe Dashboard, go to **Product catalog** (`https://dashboard.stripe.com/test/products`) and click **+ Add product**.
2. **Name**: `Receipt Scanner Pro - Wochen-Pass`
3. **Description**: `Unbegrenzte Belege, Dual-Sheet Excel & Buchhaltungs-CSV (inkl. 3 Tage Trial)`
4. **Pricing**:
- Pricing model: **Standard pricing**
- Price: **4,99 EUR**
- Billing period: **Weekly (Wöchentlich)**
5. Click **Save product**.
6. Under the **Pricing** section of this product, copy the **Price ID** (starts with `price_...`).
7. Add to `.env.local`:
```env
STRIPE_WEEKLY_PRICE_ID=price_1Q...
```
### 4.2 Product: Annual Pass (Jahres-Pass)
1. Click **+ Add product**.
2. **Name**: `Receipt Scanner Pro - Jahres-Pass`
3. **Description**: `Volle 12 Monate unbegrenzte Belege, Dual-Sheet Excel & Prioritäts-Support`
4. **Pricing**:
- Pricing model: **Standard pricing**
- Price: **39,99 EUR**
- Billing period: **Yearly (Jährlich)**
5. Click **Save product**.
6. Copy the **Price ID** (`price_...`).
7. Add to `.env.local`:
```env
STRIPE_ANNUAL_PRICE_ID=price_1Q...
```
### 4.3 Product: Lifetime License (Lebenslange Lizenz)
1. Click **+ Add product**.
2. **Name**: `Receipt Scanner Pro - Lifetime Lizenz`
3. **Description**: `Lebenslanger unbegrenzter Zugriff ohne Folgekosten inklusive aller Updates`
4. **Pricing**:
- Pricing model: **Standard pricing**
- Price: **59,99 EUR**
- Billing period: **One-time (Einmalig)**
5. Click **Save product**.
6. Copy the **Price ID** (`price_...`).
7. Add to `.env.local`:
```env
STRIPE_LIFETIME_PRICE_ID=price_1Q...
```
---
## 5. Webhook Configuration (Production & Staging)
When a customer completes checkout or cancels a subscription, Stripe sends webhook events to your server.
1. Navigate to **Developers > Webhooks** (`https://dashboard.stripe.com/test/webhooks`).
2. Click **+ Add destination** (or **+ Add endpoint**).
3. **Endpoint URL**:
- Production: `https://your-domain.com/api/webhooks/stripe`
- Staging/Preview: `https://staging.your-domain.com/api/webhooks/stripe`
4. **Select events to listen to**:
- `checkout.session.completed` (Creates license in PostgreSQL & triggers Discord notification)
- `customer.subscription.updated` (Updates expiration date and active status)
- `customer.subscription.deleted` (Downgrades user license to cancelled/free)
- `invoice.payment_succeeded` (Renews active subscription period)
- `invoice.payment_failed` (Marks license as past due)
5. Click **Add endpoint**.
6. In the newly created webhook page, find **Signing secret** and click **Reveal**.
7. Copy the signing secret (starts with `whsec_...`) and add to `.env.local`:
```env
STRIPE_WEBHOOK_SECRET=whsec_...
```
### 5.1 Webhook verification & license-granting policy (security notes)
The webhook endpoint (`/api/webhooks/stripe`) enforces, in order:
1. **Signature verification is the sole entry gate.** Every request is verified
with `stripe.webhooks.constructEvent(rawBody, sig, STRIPE_WEBHOOK_SECRET)`
before any business logic runs. A missing/invalid signature gets a
`400` — nothing else executes. Never disable or bypass this check.
2. **Licenses are only granted for confirmed payments.**
- One-time payments (Lifetime, `mode: "payment"`): only when
`payment_status === "paid"`.
- Subscriptions (Weekly/Annual, `mode: "subscription"`): the subscription is
retrieved from Stripe and only `active` / `trialing` statuses activate a
license; `expiresAt` is derived from `subscription.current_period_end`
(never from the server clock).
- Unconfirmed events are acknowledged with `200` + `received: true` and
logged, but create **no** license and no Stripe retry.
3. **Amount cross-check (defense in depth).** For one-time payments the paid
`amount_total` (minor units) is compared against the shared price catalog
(`src/lib/billing/pricing.ts`, e.g. Lifetime = 5999). On mismatch the event
is acknowledged and logged but no license is created. Subscription trials
legitimately carry `amount_total = 0`, so subscription amounts are not
cross-checked (status gating covers them).
4. **Plan validation.** The plan from checkout metadata is validated with
`isPlanId`/`resolvePlan`; unknown values fall back to the annual plan (same
contract as the checkout route) and are logged as a warning.
> ⚠️ **Testing note:** `stripe trigger checkout.session.completed` generates a
> synthetic session without plan metadata and without a subscription, so it is
> (correctly) treated as unconfirmed and will **not** create a license. To test
> license creation end-to-end, run a real checkout through the app
> (`stripe listen --forward-to localhost:3000/api/webhooks/stripe`) and pay
> with Stripe's test card `4242 4242 4242 4242`.
> **Provider limitation:** Stripe is currently the only integrated payment
> provider (Paddle is not a dependency and no Paddle route exists). If a second
> provider is added later, it must follow the same verification pattern:
> cryptographic signature check as the sole gate, payment-status confirmation
> before granting, amount cross-check against the shared catalog, and
> idempotent license creation.
---
## 6. Local Testing with the Stripe CLI
You can test the entire checkout, webhook, and licensing pipeline on your local machine (`http://localhost:3000`) using the official Stripe CLI.
### 6.1 Install Stripe CLI
- **Windows (Scoop)**:
```powershell
scoop install stripe
```
- **macOS (Homebrew)**:
```bash
brew install stripe/stripe-cli/stripe
```
- **Direct Binary Download**:
Download the latest release executable from [GitHub Releases](https://github.com/stripe/stripe-cli/releases) and add it to your `PATH`.
### 6.2 Authenticate CLI
Run:
```bash
stripe login
```
Follow the in-terminal link to authenticate with your Stripe account.
### 6.3 Forward Webhooks to Local Server
Start your Next.js development server:
```bash
npm run dev
```
In a separate terminal, forward Stripe events to your local webhook route:
```bash
stripe listen --forward-to localhost:3000/api/webhooks/stripe
```
Stripe CLI will print a local webhook signing secret in your terminal:
```
> Ready! Your webhook signing secret is whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
Copy this secret and set `STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxx...` in `.env.local` (and restart `npm run dev` if needed).
### 6.4 Triggering Test Events
You can trigger synthetic Stripe events directly from the CLI:
1. **Test Checkout Session Completion**:
```bash
stripe trigger checkout.session.completed
```
2. **Test Subscription Cancellation**:
```bash
stripe trigger customer.subscription.deleted
```
3. **Test Subscription Renewal Payment**:
```bash
stripe trigger invoice.payment_succeeded
```
4. **Test Failed Payment**:
```bash
stripe trigger invoice.payment_failed
```
Check your terminal logs and database to see the license generated and logged.
---
## 7. Discord Sales Alert Bot (Optional)
ScanReceipts includes real-time sales notifications via Discord Webhook:
1. In your Discord server, open **Server Settings > Integrations > Webhooks**.
2. Click **New Webhook**, name it (e.g. `Receipt Scanner Sales`), select a channel, and click **Copy Webhook URL**.
3. Add the URL to your `.env.local`:
```env
DISCORD_SALES_WEBHOOK_URL=https://discord.com/api/webhooks/123456789/abcdef...
```
4. Whenever a checkout completes, the bot will post an emerald green embed containing the plan purchased, formatted amount in EUR, customer email, and timestamp.
---
## 8. License Verification API Reference
### `GET /api/license/verify`
Verify an active license key or Stripe checkout session ID.
**Query Parameters:**
- `key` or `licenseKey`: The generated license key (e.g., `RS-PRO-A1B2-C3D4`)
- `sessionId` or `session_id`: The Stripe checkout session ID (`cs_test_...` or `cs_live_...`)
**Example Request:**
```bash
curl "http://localhost:3000/api/license/verify?key=RS-PRO-A1B2-C3D4"
```
**Success Response (HTTP 200):**
```json
{
"valid": true,
"plan": "lifetime",
"status": "active",
"expiresAt": null,
"licenseKey": "RS-PRO-A1B2-C3D4",
"source": "database"
}
```
---
## 9. Environment Variables Reference (.env.local)
Here is the complete template for your environment configuration:
```env
# ==============================================
# Next.js Application URL
# ==============================================
NEXT_PUBLIC_APP_URL=http://localhost:3000
# ==============================================
# Stripe API Keys & Secrets
# ==============================================
STRIPE_SECRET_KEY=sk_test_51...
STRIPE_PUBLISHABLE_KEY=pk_test_51...
STRIPE_WEBHOOK_SECRET=whsec_...
# ==============================================
# Optional Stripe Price IDs (Dashboard Created)
# ==============================================
STRIPE_WEEKLY_PRICE_ID=price_...
STRIPE_ANNUAL_PRICE_ID=price_...
STRIPE_LIFETIME_PRICE_ID=price_...
# ==============================================
# Discord Sales Notification Webhook (Optional)
# ==============================================
DISCORD_SALES_WEBHOOK_URL=https://discord.com/api/webhooks/...
# ==============================================
# Database Persistence (PostgreSQL)
# ==============================================
DATABASE_URL=postgresql://receipt_user:receipt_password@localhost:5432/receipt_scanner
```
---
## 10. Troubleshooting & FAQ
### Issue: "Invalid signature" error in webhook logs
- Ensure `STRIPE_WEBHOOK_SECRET` matches the signing secret displayed by Stripe CLI (`stripe listen`) during local development, or the signing secret in Stripe Dashboard under Webhooks for production.
- Ensure the raw request body is read cleanly without intermediate JSON serialization before signature validation (ScanReceipts handles this via `await req.text()`).
### Issue: Stripe Checkout redirects to 404 or localhost in production
- Set `NEXT_PUBLIC_APP_URL` to your production domain (e.g. `https://scanreceipts.io`).
### Issue: Database is offline during checkout
- ScanReceipts uses a **Local-First Architecture**. If PostgreSQL is unreachable or `DATABASE_URL` is omitted, the checkout and verification routes gracefully fall back to zero-friction local validation without crashing or returning errors to the user.