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>
12 KiB
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
- Sign Up or Log In: Go to the Stripe Dashboard.
- 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).
- Ensure your default currency is set to EUR (€).
3. Obtaining API Keys
- Navigate to Developers > API keys (
https://dashboard.stripe.com/test/apikeys). - Publishable key: Copy the key starting with
pk_test_.... - Secret key: Click "Reveal live key token" (or create a new restricted/standard key) starting with
sk_test_.... - Add these keys to your
.env.localfile: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)
- In Stripe Dashboard, go to Product catalog (
https://dashboard.stripe.com/test/products) and click + Add product. - Name:
Receipt Scanner Pro - Wochen-Pass - Description:
Unbegrenzte Belege, Dual-Sheet Excel & Buchhaltungs-CSV (inkl. 3 Tage Trial) - Pricing:
- Pricing model: Standard pricing
- Price: 4,99 EUR
- Billing period: Weekly (Wöchentlich)
- Click Save product.
- Under the Pricing section of this product, copy the Price ID (starts with
price_...). - Add to
.env.local:STRIPE_WEEKLY_PRICE_ID=price_1Q...
4.2 Product: Annual Pass (Jahres-Pass)
- Click + Add product.
- Name:
Receipt Scanner Pro - Jahres-Pass - Description:
Volle 12 Monate unbegrenzte Belege, Dual-Sheet Excel & Prioritäts-Support - Pricing:
- Pricing model: Standard pricing
- Price: 39,99 EUR
- Billing period: Yearly (Jährlich)
- Click Save product.
- Copy the Price ID (
price_...). - Add to
.env.local:STRIPE_ANNUAL_PRICE_ID=price_1Q...
4.3 Product: Lifetime License (Lebenslange Lizenz)
- Click + Add product.
- Name:
Receipt Scanner Pro - Lifetime Lizenz - Description:
Lebenslanger unbegrenzter Zugriff ohne Folgekosten inklusive aller Updates - Pricing:
- Pricing model: Standard pricing
- Price: 59,99 EUR
- Billing period: One-time (Einmalig)
- Click Save product.
- Copy the Price ID (
price_...). - Add to
.env.local: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.
- Navigate to Developers > Webhooks (
https://dashboard.stripe.com/test/webhooks). - Click + Add destination (or + Add endpoint).
- Endpoint URL:
- Production:
https://your-domain.com/api/webhooks/stripe - Staging/Preview:
https://staging.your-domain.com/api/webhooks/stripe
- Production:
- 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)
- Click Add endpoint.
- In the newly created webhook page, find Signing secret and click Reveal.
- Copy the signing secret (starts with
whsec_...) and add to.env.local:STRIPE_WEBHOOK_SECRET=whsec_...
5.1 Webhook verification & license-granting policy (security notes)
The webhook endpoint (/api/webhooks/stripe) enforces, in order:
- 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 a400— nothing else executes. Never disable or bypass this check. - Licenses are only granted for confirmed payments.
- One-time payments (Lifetime,
mode: "payment"): only whenpayment_status === "paid". - Subscriptions (Weekly/Annual,
mode: "subscription"): the subscription is retrieved from Stripe and onlyactive/trialingstatuses activate a license;expiresAtis derived fromsubscription.current_period_end(never from the server clock). - Unconfirmed events are acknowledged with
200+received: trueand logged, but create no license and no Stripe retry.
- One-time payments (Lifetime,
- 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 carryamount_total = 0, so subscription amounts are not cross-checked (status gating covers them). - 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.completedgenerates 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 card4242 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):
scoop install stripe - macOS (Homebrew):
brew install stripe/stripe-cli/stripe - Direct Binary Download:
Download the latest release executable from GitHub Releases and add it to your
PATH.
6.2 Authenticate CLI
Run:
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:
npm run dev
In a separate terminal, forward Stripe events to your local webhook route:
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:
- Test Checkout Session Completion:
stripe trigger checkout.session.completed - Test Subscription Cancellation:
stripe trigger customer.subscription.deleted - Test Subscription Renewal Payment:
stripe trigger invoice.payment_succeeded - Test Failed Payment:
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:
- In your Discord server, open Server Settings > Integrations > Webhooks.
- Click New Webhook, name it (e.g.
Receipt Scanner Sales), select a channel, and click Copy Webhook URL. - Add the URL to your
.env.local:DISCORD_SALES_WEBHOOK_URL=https://discord.com/api/webhooks/123456789/abcdef... - 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:
keyorlicenseKey: The generated license key (e.g.,RS-PRO-A1B2-C3D4)sessionIdorsession_id: The Stripe checkout session ID (cs_test_...orcs_live_...)
Example Request:
curl "http://localhost:3000/api/license/verify?key=RS-PRO-A1B2-C3D4"
Success Response (HTTP 200):
{
"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:
# ==============================================
# 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_SECRETmatches 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_URLto 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_URLis omitted, the checkout and verification routes gracefully fall back to zero-friction local validation without crashing or returning errors to the user.