# Stripe Setup Guide — ModuleDesk

**Audience:** Developer/operator setting up Stripe for a new ModuleDesk instance.

---

## 1. Account prerequisites

### 1.1 Activate your Stripe account

1. Sign in at [dashboard.stripe.com](https://dashboard.stripe.com).
2. Complete business verification under **Settings → Business details**.
3. Add your bank account under **Settings → Payouts** and verify it.

### 1.2 Business profile (Spain entity)

Under **Settings → Business details**:

| Field | Value |
|---|---|
| Business type | Company |
| Country | Spain (ES) |
| Business category | SaaS / Technology |
| Website | Your production URL (e.g. `https://app.moduledesk.app`) |

### 1.3 Account-level statement descriptor

Under **Settings → Public business information**:

- **Statement descriptor:** `MODULEDESK` (max 22 chars; this appears on your customers' bank statements for recurring charges)
- **Short descriptor** (card suffix): `MDSK` — Stripe prepends this to the dynamic suffix you can pass per-charge; keep it ≤ 10 chars

> **22-char limit:** Stripe enforces a hard 22-character limit on the account-level statement descriptor. The per-charge `statement_descriptor_suffix` appends to the short descriptor with an asterisk separator (e.g. `MDSK* PREMIUM`) — the combined total must stay within 22 chars. The setup script sets `statement_descriptor` on each Product (visible in invoices/receipts), which is separate from the card statement descriptor.

---

## 2. Enable EU VAT ID collection

ModuleDesk handles reverse-charge VAT **entirely in application code** — `vat_service.py` validates VAT IDs via VIES and calls `stripe.Customer.create_tax_id(type="eu_vat", ...)` directly, and sets the invoice footer to the Art. 196 reverse-charge notice. You do **not** need to enable Stripe Tax.

What you must configure in Stripe:

1. **Customer tax IDs display on invoices** — no dashboard toggle required; Stripe automatically shows tax IDs attached to a customer on their invoices.
2. **All prices must have `tax_behavior=exclusive`** — the setup script sets this on every price. If you create prices manually, always select **"Exclusive of tax"**. This means the price you set (€19, €49, €5) is the net amount; tax is added on top if applicable.
3. **Invoice footer** — the app sets this per-customer via `invoice_settings.footer` on the Stripe Customer object after VAT validation. No manual Stripe configuration needed.
4. **Do NOT enable Stripe Tax** (the automatic tax calculation product) — the app issues B2B-only EU reverse-charge invoices. Stripe Tax is deferred until meaningful B2C volume appears.

---

## 3. Products & prices

### Option A: Run `scripts/setup_stripe_products.py` (recommended)

This is the fastest and most reliable path. The script is idempotent — safe to re-run; it updates existing products and prices rather than creating duplicates.

```bash
# Test mode (default — requires sk_test_... key in .env)
venv/bin/python scripts/setup_stripe_products.py

# Live mode (requires --live flag as a safety guard)
venv/bin/python scripts/setup_stripe_products.py --live
```

At the end the script prints four lines ready to paste into `.env`:

```
STRIPE_PRICE_PREMIUM=price_...
STRIPE_PRICE_MAX=price_...
STRIPE_PRICE_MODULE_ADDON=price_...
STRIPE_PRICE_TOPUP=price_...
```

### Option B: Manual dashboard steps

Create the following four products. For each one:

1. Go to **Dashboard → Product catalog → + Add product**
2. Set Name, Description, and the metadata key/value pairs shown below
3. Add the Price with the exact settings shown

---

#### Product 1 — ModuleDesk Premium

| Field | Value |
|---|---|
| **Name** | ModuleDesk Premium |
| **Description** | Professional support desk for PrestaShop sellers. Up to 6 modules, 3 team members, AI composer, semantic search, and RAG drafts. |
| **Statement descriptor** | `MODULEDESK PREMIUM` |
| **Image suggestion** | Purple-to-indigo gradient card with a crown icon and "Premium" wordmark |

**Metadata:**
```
product=moduledesk
plan=premium
modules_included=6
team_members=3
ai_credits_per_month=300
```

**Marketing features** (shown on hosted Stripe checkout/pricing pages):
- All 6 AI composer actions (~300 credits/month)
- Inbound & outbound translation
- Semantic search
- RAG drafts from your own documentation sources
- Unlimited quick-reply templates + Addons sync + categories
- 3 team members
- Full order history (24 months) + CSV export
- Smart Priority: full 10-signal scoring
- Auto-sync every 30 minutes
- Guide generation (Beta)

**Price:**
```
Amount:       €19.00
Currency:     EUR
Billing:      Monthly recurring
Tax behavior: Exclusive of tax
Lookup key:   moduledesk_premium_monthly
```

---

#### Product 2 — ModuleDesk MAX

| Field | Value |
|---|---|
| **Name** | ModuleDesk MAX |
| **Description** | Unlimited everything for serious PrestaShop businesses. Unlimited modules, unlimited team, 1000 AI credits + bring-your-own OpenAI key, credential vault, and health monitoring. |
| **Statement descriptor** | `MODULEDESK MAX` |
| **Image suggestion** | Dark navy-to-black gradient card with a lightning bolt icon and "MAX" wordmark in gold |

**Metadata:**
```
product=moduledesk
plan=max
modules_included=unlimited
team_members=unlimited
ai_credits_per_month=1000
byok=true
```

**Marketing features:**
- Unlimited modules connected
- Unlimited team members + roles
- ~1000 AI credits/month + bring-your-own OpenAI key (bypasses credit deduction entirely)
- Credential vault with connection testing
- Customer website health monitoring
- URL safety scanning (VirusTotal)
- Full 10-year order backfill + CSV import + margin/refund filters
- Audit log
- Geography map dashboard + value segment exports
- Guide generation (Beta)
- VIP customer segmentation + installed-modules detection

**Price:**
```
Amount:       €49.00
Currency:     EUR
Billing:      Monthly recurring
Tax behavior: Exclusive of tax
Lookup key:   moduledesk_max_monthly
```

---

#### Product 3 — ModuleDesk Module Add-on

> **Important:** This product is for **Premium subscribers only**. It is a recurring subscription add-on item (not a standalone subscription). Show it only on the in-app "module limit reached" screen — **never on the public pricing table**.

| Field | Value |
|---|---|
| **Name** | ModuleDesk Module Add-on |
| **Description** | Add 5 extra module slots to your Premium plan. Each unit = +5 modules. Add as many units as you need. |
| **Statement descriptor** | `MODULEDESK ADDON` |

**Metadata:**
```
product=moduledesk
addon_type=module_slots
slots_per_unit=5
plan_required=premium
```

**Price:**
```
Amount:       €5.00
Currency:     EUR
Billing:      Monthly recurring (per unit — customer can have multiple units)
Tax behavior: Exclusive of tax
Lookup key:   moduledesk_module_addon_monthly
```

---

#### Product 4 — ModuleDesk AI Credit Top-up

> **Important:** This is a **one-time payment** (not a subscription). It requires an **active Premium or MAX subscription** — Free plan customers cannot purchase top-ups. The app enforces this restriction; Stripe itself does not.

| Field | Value |
|---|---|
| **Name** | ModuleDesk AI Credit Top-up |
| **Description** | One-time pack of 200 AI credits for €5. Credits are added to your organization's pool immediately after purchase. Requires an active Premium or MAX subscription. |
| **Statement descriptor** | `MODULEDESK TOPUP` |
| **Image suggestion** | Electric teal gradient card with a lightning-bolt/battery icon and "+200 Credits" label |

**Metadata:**
```
product=moduledesk
topup_type=ai_credits
credits_per_pack=200
subscription_required=true
```

**Price:**
```
Amount:       €5.00
Currency:     EUR
Billing:      One-time (not recurring)
Tax behavior: Exclusive of tax
Lookup key:   moduledesk_topup_200credits
```

---

### Best practice: stable product names, new prices (not new products)

When you need to change a price (e.g. raise Premium from €19 to €24):

1. **Never delete the old Price.** Create a new Price under the same Product.
2. Use a new lookup key (e.g. `moduledesk_premium_monthly_v2`).
3. Update `STRIPE_PRICE_PREMIUM` in `.env` to the new Price ID.
4. Restart the service.
5. Existing subscribers keep billing on the old price until you explicitly migrate them (or until their next renewal if you use a migration script).

This preserves revenue history, invoice line-item descriptions, and prevents disruption to active subscribers.

---

## 4. Webhook endpoint

### 4.1 Create the endpoint

1. Go to **Dashboard → Developers → Webhooks → + Add endpoint**.
2. **Endpoint URL:**
   ```
   https://app.moduledesk.app/billing/webhook
   ```
   Replace `app.moduledesk.app` with your actual domain. The route is hardcoded at `/billing/webhook` in `supporthub/app/main.py`.

3. **API version:** Select the latest (the code uses the Stripe SDK's default).

4. **Events to listen for** — select exactly these four:

   | Event | What the code does |
   |---|---|
   | `invoice.paid` | Resets `org.ai_credits_used` to 0 and updates `ai_credits_reset_at` (monthly credit renewal) |
   | `customer.subscription.deleted` | Downgrades `org.plan` to `"free"` and clears `stripe_subscription_id` |
   | `customer.subscription.updated` | Syncs `org.plan` from subscription metadata; syncs `extra_module_slots` from module add-on quantity |
   | `checkout.session.completed` | Credits top-up packs: reads `metadata.topup_units` and `metadata.org_id`, adds `topup_units × 200` to `org.ai_credits_extra`. Idempotent via `StripeEventProcessed` table. |

   > All other events are silently ignored (the code returns `{"status": "ignored"}`).

5. Click **Add endpoint**.

### 4.2 Copy the signing secret

After creating the endpoint, reveal the **Signing secret** (`whsec_...`) and add it to `.env`:

```
STRIPE_WEBHOOK_SECRET=whsec_...
```

---

## 5. Fill `.env` and restart

After running the setup script (or copying price IDs manually from the dashboard), your `.env` Stripe block should look like:

```bash
# Stripe billing configuration
STRIPE_SECRET_KEY=sk_test_...          # or sk_live_... in production
STRIPE_WEBHOOK_SECRET=whsec_...

STRIPE_PRICE_PREMIUM=price_...         # Recurring €19/mo (quantity 1)
STRIPE_PRICE_MAX=price_...             # Recurring €49/mo (quantity 1)
STRIPE_PRICE_MODULE_ADDON=price_...    # Recurring €5/mo per unit (Premium only; 1 unit = +5 slots)
STRIPE_PRICE_TOPUP=price_...           # One-time €5 pack (200 credits; Premium & MAX only)
```

Restart the service:

```bash
sudo systemctl restart supporthub
```

Verify it's running:

```bash
systemctl status supporthub --no-pager
```

---

## 6. Test-mode checklist

Before switching to live keys, run through this flow in test mode.

### Test cards

| Scenario | Card number |
|---|---|
| Successful payment | `4242 4242 4242 4242` |
| Authentication required (3DS) | `4000 0025 0000 3155` |
| Payment declined | `4000 0000 0000 9995` |

Use any future expiry date and any 3-digit CVC.

### Trial → upgrade → top-up flow

1. **Register a new org** — confirm a Stripe customer is created (check Dashboard → Customers).
2. **Free → Premium upgrade** — trigger via `/billing/upgrade` (or call `create_upgrade_checkout`); complete checkout with test card `4242...`; confirm `org.plan = 'premium'` and `org.stripe_subscription_id` set.
3. **Module add-on** — call `update_module_addon(org, units=1)` via the API; confirm a second subscription item appears with `price=STRIPE_PRICE_MODULE_ADDON` and `quantity=1`; confirm `org.extra_module_slots = 5` after the `customer.subscription.updated` webhook fires.
4. **Credit top-up** — trigger via `create_topup_checkout`; complete checkout; confirm `checkout.session.completed` fires, `org.ai_credits_extra` increases by 200, and the event is recorded in `StripeEventProcessed`.
5. **Premium → MAX switch** — call `switch_plan(org, 'max')`; confirm the subscription item's price changes and `customer.subscription.updated` fires with `metadata.plan=max`; confirm `org.plan = 'max'`.
6. **Cancellation** — call `switch_plan(org, 'free')`; confirm `cancel_at_period_end=True` on the subscription; wait for (or simulate) `customer.subscription.deleted`; confirm `org.plan = 'free'`.
7. **Monthly renewal** — use the Stripe CLI to trigger `invoice.paid`; confirm `org.ai_credits_used` resets to 0.

### Go-live switch

1. Replace `STRIPE_SECRET_KEY` with `sk_live_...`.
2. Create a **new** webhook endpoint in live mode (test-mode endpoints do not receive live events); copy the new `STRIPE_WEBHOOK_SECRET`.
3. Re-run `scripts/setup_stripe_products.py --live` to create products/prices in live mode; paste the printed live Price IDs into `.env`.
4. Restart: `sudo systemctl restart supporthub`.
5. Place a real €0.50 test charge (create a coupon if desired) to verify the full flow end-to-end before announcing launch.

---

## 7. Stable product names — adding new prices, not new products

Key principle: **products are permanent catalog entries; prices are versioned**.

- When you change pricing, add a **new Price** to the existing Product with a new lookup key and update the env var.
- Never delete old Prices — they are referenced by existing subscription items and historical invoices.
- Never rename Products — the name appears on invoices; changing it rewrites historical descriptions.
- If you add a new plan (e.g. a yearly billing option), add a new Price to the existing Premium or MAX Product with `interval=year`.

This keeps your revenue analytics clean and avoids disruption to active subscribers.
