# 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.