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