Files
bakery-ia/STRIPE_TESTING_GUIDE.md

1719 lines
53 KiB
Markdown
Raw Normal View History

2026-01-11 07:50:34 +01:00
# Stripe Integration Testing Guide
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Environment Setup](#environment-setup)
3. [Stripe Dashboard Configuration](#stripe-dashboard-configuration)
4. [Test Card Numbers](#test-card-numbers)
5. [Testing Scenarios](#testing-scenarios)
6. [Webhook Testing](#webhook-testing)
7. [Common Issues & Solutions](#common-issues--solutions)
8. [Production Checklist](#production-checklist)
---
## Prerequisites
Before you begin testing, ensure you have:
- ✅ Stripe account created (sign up at [stripe.com](https://stripe.com))
- ✅ Node.js and Python environments set up
- ✅ Frontend application running (React + Vite)
- ✅ Backend API running (FastAPI)
- ✅ Database configured and accessible
- ✅ Redis instance running (for caching)
2026-01-16 20:25:45 +01:00
stripe listen --forward-to https://bakery-ia.local/api/v1/webhooks/stripe --skip-verify
2026-01-11 07:50:34 +01:00
---
## Environment Setup
### Step 1: Access Stripe Test Mode
1. Log in to your Stripe Dashboard: [https://dashboard.stripe.com](https://dashboard.stripe.com)
2. Click on your profile icon in the top right corner
3. Ensure **Test Mode** is enabled (you'll see "TEST DATA" banner at the top)
4. If not enabled, toggle to "Switch to test data"
### Step 2: Retrieve API Keys
1. Navigate to **Developers****API keys**
2. You'll see two types of keys:
- **Publishable key** (starts with `pk_test_...`) - Used in frontend
- **Secret key** (starts with `sk_test_...`) - Used in backend
3. Click "Reveal test key" for the Secret key and copy both keys
### Step 3: Configure Environment Variables
2026-01-12 22:15:11 +01:00
**IMPORTANT:** This project **runs exclusively in Kubernetes**:
- **Development:** Kind/Colima + Tilt for local K8s development
- **Production:** MicroK8s on Ubuntu VPS
All configuration is managed through Kubernetes ConfigMaps and Secrets.
#### Frontend - Kubernetes ConfigMap:
Update [infrastructure/kubernetes/base/configmap.yaml](infrastructure/kubernetes/base/configmap.yaml:374-378):
```yaml
# FRONTEND CONFIGURATION section (lines 374-378)
VITE_STRIPE_PUBLISHABLE_KEY: "pk_test_your_actual_stripe_publishable_key_here"
VITE_PILOT_MODE_ENABLED: "true"
VITE_PILOT_COUPON_CODE: "PILOT2025"
VITE_PILOT_TRIAL_MONTHS: "3"
```
#### Backend - Kubernetes Secrets:
Update [infrastructure/kubernetes/base/secrets.yaml](infrastructure/kubernetes/base/secrets.yaml:142-150):
```yaml
# payment-secrets section (lines 142-150)
apiVersion: v1
kind: Secret
metadata:
name: payment-secrets
namespace: bakery-ia
type: Opaque
data:
STRIPE_SECRET_KEY: <base64_encoded_sk_test_key>
STRIPE_WEBHOOK_SECRET: <base64_encoded_whsec_key>
```
**Encode your Stripe keys:**
2026-01-11 07:50:34 +01:00
```bash
2026-01-12 22:15:11 +01:00
# Encode Stripe secret key
echo -n "sk_test_your_actual_key_here" | base64
# Encode webhook secret (obtained in next step)
echo -n "whsec_your_actual_secret_here" | base64
2026-01-11 07:50:34 +01:00
```
2026-01-12 22:15:11 +01:00
#### Apply Configuration Changes:
After updating the files, apply to your Kubernetes cluster:
2026-01-11 07:50:34 +01:00
```bash
2026-01-12 22:15:11 +01:00
# Development (Kind/Colima with Tilt)
# Tilt will automatically detect changes and reload
# Or manually apply
kubectl apply -f infrastructure/kubernetes/base/configmap.yaml
kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
# Production (MicroK8s on VPS)
microk8s kubectl apply -f infrastructure/kubernetes/base/configmap.yaml
microk8s kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
# Restart deployments to pick up new config
kubectl rollout restart deployment/frontend-deployment -n bakery-ia
kubectl rollout restart deployment/tenant-service -n bakery-ia
2026-01-11 07:50:34 +01:00
```
2026-01-12 22:15:11 +01:00
**Note:** The webhook secret will be obtained in the next step when setting up webhooks.
2026-01-11 07:50:34 +01:00
### Step 4: Install/Update Dependencies
#### Backend:
```bash
cd services/tenant
pip install -r requirements.txt
# This will install stripe==14.1.0
```
#### Frontend:
```bash
cd frontend
npm install
# Verifies @stripe/react-stripe-js and @stripe/stripe-js are installed
```
---
## Stripe Dashboard Configuration
### Step 1: Create Products and Prices
2026-01-12 22:15:11 +01:00
**Important:** Your application uses EUR currency and has specific pricing for the Spanish market.
#### 1.1 Create Starter Plan Product
2026-01-11 07:50:34 +01:00
1. In Stripe Dashboard (Test Mode), go to **Products****Add product**
2026-01-12 22:15:11 +01:00
2. Fill in product details:
- **Product name:** `Starter Plan`
- **Description:** `Plan básico para panaderías pequeñas - Includes basic forecasting, waste tracking, and supplier management`
- **Statement descriptor:** `BAKERY-STARTER` (appears on customer's credit card statement)
3. **Create Monthly Price:**
- Click **Add another price**
- Price: `€49.00`
- Billing period: `Monthly`
- Currency: `EUR`
- Price description: `Starter Plan - Monthly`
- Click **Save price**
- **⚠️ COPY THE PRICE ID** (starts with `price_...`) - Label it as `STARTER_MONTHLY_PRICE_ID`
4. **Create Yearly Price (with discount):**
- Click **Add another price** on the same product
- Price: `€490.00` (17% discount - equivalent to 2 months free)
- Billing period: `Yearly`
- Currency: `EUR`
- Price description: `Starter Plan - Yearly (2 months free)`
- Click **Save price**
- **⚠️ COPY THE PRICE ID** - Label it as `STARTER_YEARLY_PRICE_ID`
#### 1.2 Create Professional Plan Product
1. Click **Add product** to create a new product
2. Fill in product details:
- **Product name:** `Professional Plan`
- **Description:** `Plan profesional para panaderías en crecimiento - Business analytics, enhanced AI (92% accurate), what-if scenarios, multi-location support`
- **Statement descriptor:** `BAKERY-PRO`
3. **Create Monthly Price:**
- Price: `€149.00`
- Billing period: `Monthly`
- Currency: `EUR`
- Price description: `Professional Plan - Monthly`
- **⚠️ COPY THE PRICE ID** - Label it as `PROFESSIONAL_MONTHLY_PRICE_ID`
4. **Create Yearly Price (with discount):**
- Price: `€1,490.00` (17% discount - equivalent to 2 months free)
- Billing period: `Yearly`
- Currency: `EUR`
- Price description: `Professional Plan - Yearly (2 months free)`
- **⚠️ COPY THE PRICE ID** - Label it as `PROFESSIONAL_YEARLY_PRICE_ID`
#### 1.3 Create Enterprise Plan Product
1. Click **Add product** to create a new product
2. Fill in product details:
- **Product name:** `Enterprise Plan`
- **Description:** `Plan enterprise para grandes operaciones - Unlimited features, production distribution, centralized dashboard, white-label, SSO, dedicated support`
- **Statement descriptor:** `BAKERY-ENTERPRISE`
3. **Create Monthly Price:**
- Price: `€499.00`
- Billing period: `Monthly`
- Currency: `EUR`
- Price description: `Enterprise Plan - Monthly`
- **⚠️ COPY THE PRICE ID** - Label it as `ENTERPRISE_MONTHLY_PRICE_ID`
4. **Create Yearly Price (with discount):**
- Price: `€4,990.00` (17% discount - equivalent to 2 months free)
- Billing period: `Yearly`
- Currency: `EUR`
- Price description: `Enterprise Plan - Yearly (2 months free)`
- **⚠️ COPY THE PRICE ID** - Label it as `ENTERPRISE_YEARLY_PRICE_ID`
#### 1.4 Trial Period Configuration
**IMPORTANT:** Your system does NOT use Stripe's default trial periods. Instead:
- All trial periods are managed through the **PILOT2025 coupon only**
- Do NOT configure default trials on products in Stripe
- The coupon provides a 90-day (3-month) trial extension
- Without the PILOT2025 coupon, subscriptions start with immediate billing
#### 1.5 Price ID Reference Sheet
Create a reference document with all your Price IDs:
```
STARTER_MONTHLY_PRICE_ID=price_XXXXXXXXXXXXXXXX
STARTER_YEARLY_PRICE_ID=price_XXXXXXXXXXXXXXXX
PROFESSIONAL_MONTHLY_PRICE_ID=price_XXXXXXXXXXXXXXXX
PROFESSIONAL_YEARLY_PRICE_ID=price_XXXXXXXXXXXXXXXX
ENTERPRISE_MONTHLY_PRICE_ID=price_XXXXXXXXXXXXXXXX
ENTERPRISE_YEARLY_PRICE_ID=price_XXXXXXXXXXXXXXXX
```
⚠️ **You'll need these Price IDs when configuring your backend environment variables or updating subscription creation logic.**
### Step 2: Create the PILOT2025 Coupon (3 Months Free)
**CRITICAL:** This coupon provides a 90-day (3-month) trial period where customers pay €0.
#### How it Works:
Your application validates the `PILOT2025` coupon code and, when valid:
1. Creates the Stripe subscription with `trial_period_days=90`
2. Stripe automatically:
- Sets subscription status to `trialing`
- Charges **€0 for the first 90 days**
- Schedules the first invoice for day 91
- Automatically begins normal billing after trial ends
2026-01-13 22:22:38 +01:00
**IMPORTANT:** You do NOT need to create a coupon in Stripe Dashboard. The coupon is managed entirely in your application's database.
**How it works with Stripe:**
- Your application validates the `PILOT2025` coupon code against your database
- If valid, your backend passes `trial_period_days=90` parameter when creating the Stripe subscription
- Stripe doesn't know about the "PILOT2025" coupon itself - it only receives the trial duration
- Example API call to Stripe:
```python
stripe.Subscription.create(
customer=customer_id,
items=[{"price": price_id}],
trial_period_days=90, # <-- This is what Stripe needs
# No coupon parameter needed in Stripe
)
```
2026-01-12 22:15:11 +01:00
#### Verify PILOT2025 Coupon in Your Database:
The PILOT2025 coupon should already exist in your database (created by the startup seeder). Verify it exists:
```sql
SELECT * FROM coupons WHERE code = 'PILOT2025';
```
**Expected values:**
- `code`: `PILOT2025`
- `discount_type`: `trial_extension`
- `discount_value`: `90` (days)
- `max_redemptions`: `20`
- `active`: `true`
- `valid_until`: ~180 days from creation
If the coupon doesn't exist, it will be created automatically on application startup via the [startup_seeder.py](services/tenant/app/jobs/startup_seeder.py).
#### Environment Variables for Pilot Mode:
2026-01-11 07:50:34 +01:00
2026-01-12 22:15:11 +01:00
**Frontend - Kubernetes ConfigMap:**
These are already configured in [infrastructure/kubernetes/base/configmap.yaml](infrastructure/kubernetes/base/configmap.yaml:374-378):
```yaml
# FRONTEND CONFIGURATION (lines 374-378)
VITE_PILOT_MODE_ENABLED: "true"
VITE_PILOT_COUPON_CODE: "PILOT2025"
VITE_PILOT_TRIAL_MONTHS: "3"
VITE_STRIPE_PUBLISHABLE_KEY: "pk_test_your_stripe_publishable_key_here"
```
Update the `VITE_STRIPE_PUBLISHABLE_KEY` value with your actual test key from Stripe, then apply:
```bash
# Development (Tilt auto-reloads, but you can manually apply)
kubectl apply -f infrastructure/kubernetes/base/configmap.yaml
# Production
microk8s kubectl apply -f infrastructure/kubernetes/base/configmap.yaml
microk8s kubectl rollout restart deployment/frontend-deployment -n bakery-ia
```
**Backend Configuration:**
The backend coupon configuration is managed in code at [services/tenant/app/jobs/startup_seeder.py](services/tenant/app/jobs/startup_seeder.py). The PILOT2025 coupon with max_redemptions=20 is created automatically on application startup.
**Important Notes:**
- ✅ Coupon is validated in your application database, NOT in Stripe
- ✅ Works with ALL three tiers (Starter, Professional, Enterprise)
- ✅ Provides a 90-day (3-month) trial with **€0 charge** during trial
- ✅ Without this coupon, subscriptions start billing immediately
- ✅ After the 90-day trial ends, Stripe automatically bills the full monthly price
- ✅ Application tracks coupon usage to prevent double-redemption per tenant
- ✅ Limited to first 20 pilot customers
**Billing Example with PILOT2025:**
- Day 0: Customer subscribes to Professional Plan (€149/month) with PILOT2025
- Day 0-90: Customer pays **€0** (trialing status in Stripe)
- Day 91: Stripe automatically charges €149.00 and status becomes "active"
- Every 30 days after: €149.00 charged automatically
### Step 3: Configure Webhooks
2026-01-11 07:50:34 +01:00
2026-01-13 22:22:38 +01:00
**Important:** For local development, you'll use **Stripe CLI** instead of creating an endpoint in the Stripe Dashboard. The CLI automatically forwards webhook events to your local server.
#### For Local Development (Recommended):
**Use Stripe CLI** - See [Webhook Testing Section](#webhook-testing) below for detailed setup.
Quick start:
```bash
# Install Stripe CLI
brew install stripe/stripe-cli/stripe # macOS
# Login to Stripe
stripe login
# Forward webhooks to gateway
stripe listen --forward-to https://bakery-ia.local/api/v1/stripe
```
The CLI will provide a webhook signing secret. See the [Webhook Testing](#webhook-testing) section for complete instructions on updating your configuration.
#### For Production or Public Testing:
1. Navigate to **Developers****Webhooks** in Stripe Dashboard
2026-01-11 07:50:34 +01:00
2. Click **+ Add endpoint**
2026-01-13 22:22:38 +01:00
3. **Endpoint URL:**
- Production: `https://yourdomain.com/api/v1/stripe`
- Or use ngrok for testing: `https://your-ngrok-url.ngrok.io/api/v1/stripe`
2026-01-11 07:50:34 +01:00
4. **Select events to listen to:**
- `checkout.session.completed`
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `invoice.payment_succeeded`
- `invoice.payment_failed`
- `customer.subscription.trial_will_end`
2026-01-12 22:15:11 +01:00
- `coupon.created` (for coupon tracking)
- `coupon.deleted` (for coupon tracking)
- `promotion_code.created` (if using promotion codes)
2026-01-11 07:50:34 +01:00
5. Click **Add endpoint**
6. **Copy the Webhook Signing Secret:**
- Click on the newly created endpoint
- Click **Reveal** next to "Signing secret"
- Copy the secret (starts with `whsec_...`)
- Add it to your backend `.env` file as `STRIPE_WEBHOOK_SECRET`
---
## Test Card Numbers
Stripe provides test card numbers to simulate different scenarios. **Never use real card details in test mode.**
### Basic Test Cards
| Scenario | Card Number | CVC | Expiry Date |
|----------|-------------|-----|-------------|
| **Successful payment** | `4242 4242 4242 4242` | Any 3 digits | Any future date |
| **Visa (debit)** | `4000 0566 5566 5556` | Any 3 digits | Any future date |
| **Mastercard** | `5555 5555 5555 4444` | Any 3 digits | Any future date |
| **American Express** | `3782 822463 10005` | Any 4 digits | Any future date |
### Authentication & Security
| Scenario | Card Number | Notes |
|----------|-------------|-------|
| **3D Secure authentication required** | `4000 0025 0000 3155` | Triggers authentication modal |
| **3D Secure 2 authentication** | `4000 0027 6000 3184` | Requires SCA authentication |
### Declined Cards
| Scenario | Card Number | Error Message |
|----------|-------------|---------------|
| **Generic decline** | `4000 0000 0000 0002` | Card declined |
| **Insufficient funds** | `4000 0000 0000 9995` | Insufficient funds |
| **Lost card** | `4000 0000 0000 9987` | Lost card |
| **Stolen card** | `4000 0000 0000 9979` | Stolen card |
| **Expired card** | `4000 0000 0000 0069` | Expired card |
| **Incorrect CVC** | `4000 0000 0000 0127` | Incorrect CVC |
| **Processing error** | `4000 0000 0000 0119` | Processing error |
| **Card declined (rate limit)** | `4000 0000 0000 9954` | Exceeds velocity limit |
### Additional Scenarios
| Scenario | Card Number | Notes |
|----------|-------------|-------|
| **Charge succeeds, then fails** | `4000 0000 0000 0341` | Attaches successfully but charge fails |
| **Dispute (fraudulent)** | `4000 0000 0000 0259` | Creates a fraudulent dispute |
| **Dispute (warning)** | `4000 0000 0000 2685` | Creates early fraud warning |
**Important Notes:**
- For **expiry date**: Use any future date (e.g., 12/30)
- For **CVC**: Use any 3-digit number (e.g., 123) or 4-digit for Amex (e.g., 1234)
- For **postal code**: Use any valid format (e.g., 12345)
---
## Testing Scenarios
2026-01-12 22:15:11 +01:00
### Scenario 1: Successful Registration with Payment (Starter Plan)
2026-01-11 07:50:34 +01:00
2026-01-12 22:15:11 +01:00
**Objective:** Test the complete registration flow with valid payment method for Starter Plan.
2026-01-11 07:50:34 +01:00
**Steps:**
2026-01-12 22:15:11 +01:00
1. **Start your Kubernetes environment:**
**Development (Kind/Colima + Tilt):**
2026-01-11 07:50:34 +01:00
```bash
2026-01-12 22:15:11 +01:00
# Start Tilt (this starts all services)
tilt up
2026-01-11 07:50:34 +01:00
2026-01-12 22:15:11 +01:00
# Or if already running, just verify services are healthy
kubectl get pods -n bakery-ia
2026-01-11 07:50:34 +01:00
```
2026-01-12 22:15:11 +01:00
**Access the application:**
- Tilt will provide the URLs (usually port-forwarded)
- Or use: `kubectl port-forward svc/frontend-service 3000:3000 -n bakery-ia`
2026-01-11 07:50:34 +01:00
2. **Navigate to registration page:**
2026-01-12 22:15:11 +01:00
- Open browser: `http://localhost:3000/register` (or your Tilt-provided URL)
2026-01-11 07:50:34 +01:00
3. **Fill in user details:**
- Full Name: `John Doe`
- Email: `john.doe+test@example.com`
- Company: `Test Company`
- Password: Create a test password
4. **Fill in payment details:**
- Card Number: `4242 4242 4242 4242`
- Expiry: `12/30`
- CVC: `123`
- Cardholder Name: `John Doe`
- Email: `john.doe+test@example.com`
- Address: `123 Test Street`
- City: `Test City`
- State: `CA`
- Postal Code: `12345`
- Country: `US`
5. **Select a plan:**
2026-01-12 22:15:11 +01:00
- Choose `Starter Plan` (€49/month or €490/year)
2026-01-11 07:50:34 +01:00
2026-01-12 22:15:11 +01:00
6. **Do NOT apply coupon** (this scenario tests without the PILOT2025 coupon)
7. **Submit the form**
2026-01-11 07:50:34 +01:00
**Expected Results:**
- ✅ Payment method created successfully
- ✅ User account created
2026-01-12 22:15:11 +01:00
- ✅ Subscription created in Stripe with immediate billing (no trial)
2026-01-11 07:50:34 +01:00
- ✅ Database records created
- ✅ User redirected to dashboard
- ✅ No console errors
2026-01-12 22:15:11 +01:00
- ✅ First invoice created immediately for €49.00
2026-01-11 07:50:34 +01:00
**Verification:**
1. **In Stripe Dashboard:**
- Go to **Customers** → Find "John Doe"
- Go to **Subscriptions** → See active subscription
2026-01-12 22:15:11 +01:00
- Status should be `active` (no trial period without coupon)
- Verify pricing: €49.00 EUR / month
- Check that first invoice was paid immediately
2026-01-11 07:50:34 +01:00
2. **In your database:**
```sql
SELECT * FROM subscriptions WHERE tenant_id = 'your-tenant-id';
```
- Verify subscription record exists
- Status should be `active`
- Check `stripe_customer_id` is populated
2026-01-12 22:15:11 +01:00
- Verify `tier` = `starter`
- Verify `billing_cycle` = `monthly` or `yearly`
- Check `current_period_start` and `current_period_end` are set
2026-01-11 07:50:34 +01:00
3. **Check application logs:**
- Look for successful subscription creation messages
- Verify no error logs
2026-01-12 22:15:11 +01:00
- Check that subscription tier is cached properly
---
### Scenario 1B: Registration with PILOT2025 Coupon (3 Months Free)
**Objective:** Test the complete registration flow with the PILOT2025 coupon applied for 3-month free trial.
**Steps:**
1. **Start your applications** (same as Scenario 1)
2. **Navigate to registration page:**
- Open browser: `http://localhost:5173/register`
3. **Fill in user details:**
- Full Name: `Maria Garcia`
- Email: `maria.garcia+pilot@example.com`
- Company: `Panadería Piloto`
- Password: Create a test password
4. **Fill in payment details:**
- Card Number: `4242 4242 4242 4242`
- Expiry: `12/30`
- CVC: `123`
- Complete remaining billing details
5. **Select a plan:**
- Choose `Professional Plan` (€149/month or €1,490/year)
6. **Apply the PILOT2025 coupon:**
- Enter coupon code: `PILOT2025`
- Click "Apply" or "Validate"
- Verify coupon is accepted and shows "3 months free trial"
7. **Submit the form**
**Expected Results:**
- ✅ Payment method created successfully
- ✅ User account created
- ✅ Subscription created in Stripe with 90-day trial period
- ✅ Database records created
- ✅ User redirected to dashboard
- ✅ No console errors
- ✅ Trial period: 90 days from today
- ✅ First invoice will be created after trial ends (90 days from now)
- ✅ Coupon redemption tracked in database
**Verification:**
1. **In Stripe Dashboard:**
- Go to **Customers** → Find "Maria Garcia"
- Go to **Subscriptions** → See active subscription
- **Status should be `trialing`** (this is key!)
- Verify pricing: €149.00 EUR / month (will charge this after trial)
- **Check trial end date:** Should be **90 days** from creation (hover over trial end timestamp)
- **Verify amount due now: €0.00** (no charge during trial)
- Check upcoming invoice: Should be scheduled for 90 days from now for €149.00
2. **In Stripe Dashboard - Check Trial Status:**
- Note: There is NO coupon to check in Stripe Dashboard (coupons are managed in your database)
- Instead, verify the subscription has the correct trial period
3. **In your database:**
```sql
-- Check subscription
SELECT * FROM subscriptions WHERE tenant_id = 'maria-tenant-id';
-- Check coupon redemption
SELECT * FROM coupon_redemptions WHERE tenant_id = 'maria-tenant-id';
```
- Subscription status should be `active`
- Verify `tier` = `professional`
- Check trial dates: `trial_end` should be 90 days in the future
- Coupon redemption record should exist with:
- `coupon_code` = `PILOT2025`
- `redeemed_at` timestamp
- `tenant_id` matches
4. **Test Coupon Re-use Prevention:**
- Try registering another subscription with the same tenant
- Use coupon code `PILOT2025` again
- **Expected:** Should be rejected with error "Coupon already used by this tenant"
5. **Check application logs:**
- Look for coupon validation messages
- Verify coupon redemption was logged
- Check subscription creation with trial extension
- No errors related to Stripe or coupon processing
**Test Multiple Tiers:**
Repeat this scenario with all three plans to verify the coupon works universally:
- Starter Plan: €49/month → 90-day trial → €49/month after trial
- Professional Plan: €149/month → 90-day trial → €149/month after trial
- Enterprise Plan: €499/month → 90-day trial → €499/month after trial
2026-01-11 07:50:34 +01:00
---
### Scenario 2: Payment with 3D Secure Authentication
**Objective:** Test Strong Customer Authentication (SCA) flow.
**Steps:**
1. Follow steps 1-3 from Scenario 1
2. **Fill in payment details with 3DS card:**
- Card Number: `4000 0025 0000 3155`
- Expiry: `12/30`
- CVC: `123`
- Fill remaining details as before
3. **Submit the form**
4. **Complete authentication:**
- Stripe will display an authentication modal
- Click **"Complete"** (in test mode, no real auth needed)
**Expected Results:**
- ✅ Authentication modal appears
- ✅ After clicking "Complete", payment succeeds
- ✅ Subscription created successfully
- ✅ User redirected to dashboard
**Note:** This simulates European and other markets requiring SCA.
---
### Scenario 3: Declined Payment
**Objective:** Test error handling for declined cards.
**Steps:**
1. Follow steps 1-3 from Scenario 1
2. **Use a declined test card:**
- Card Number: `4000 0000 0000 0002`
- Fill remaining details as before
3. **Submit the form**
**Expected Results:**
- ❌ Payment fails with error message
- ✅ Error displayed to user: "Your card was declined"
- ✅ No customer created in Stripe
- ✅ No subscription created
- ✅ No database records created
- ✅ User remains on payment form
- ✅ Can retry with different card
**Verification:**
- Check Stripe Dashboard → Customers (should not see new customer)
- Check application logs for error handling
- Verify user-friendly error message displayed
---
### Scenario 4: Insufficient Funds
**Objective:** Test specific decline reason handling.
**Steps:**
1. Use card number: `4000 0000 0000 9995`
2. Follow same process as Scenario 3
**Expected Results:**
- ❌ Payment fails
- ✅ Error message: "Your card has insufficient funds"
- ✅ Proper error handling and logging
---
### Scenario 5: Subscription Cancellation
**Objective:** Test subscription cancellation flow.
**Steps:**
1. **Create an active subscription** (use Scenario 1)
2. **Cancel the subscription:**
- Method 1: Through your application UI (if implemented)
- Method 2: API call:
```bash
curl -X POST http://localhost:8000/api/v1/subscriptions/cancel \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-d '{
"tenant_id": "your-tenant-id",
"reason": "Testing cancellation"
}'
```
**Expected Results:**
- ✅ Subscription status changes to `pending_cancellation`
-`cancellation_effective_date` is set
- ✅ User retains access until end of billing period
- ✅ Response includes days remaining
- ✅ Subscription cache invalidated
**Verification:**
1. Check database:
```sql
SELECT status, cancellation_effective_date, cancelled_at
FROM subscriptions
WHERE tenant_id = 'your-tenant-id';
```
2. Verify API response:
```json
{
"success": true,
"message": "Subscription cancelled successfully...",
"status": "pending_cancellation",
"cancellation_effective_date": "2026-02-10T...",
"days_remaining": 30
}
```
---
### Scenario 6: Subscription Reactivation
**Objective:** Test reactivating a cancelled subscription.
**Steps:**
1. **Cancel a subscription** (use Scenario 5)
2. **Reactivate the subscription:**
```bash
curl -X POST http://localhost:8000/api/v1/subscriptions/reactivate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-d '{
"tenant_id": "your-tenant-id",
"plan": "starter"
}'
```
**Expected Results:**
- ✅ Subscription status changes back to `active`
-`cancelled_at` and `cancellation_effective_date` cleared
- ✅ Next billing date set
- ✅ Subscription cache invalidated
---
2026-01-12 22:15:11 +01:00
### Scenario 7: PILOT2025 Coupon - Maximum Redemptions Reached
**Objective:** Test behavior when PILOT2025 coupon reaches its 20-customer limit.
**Preparation:**
- This test should be performed after 20 successful redemptions
- Or manually update coupon max redemptions in Stripe to a lower number for testing
**Steps:**
1. **Set up for testing:**
- Option A: After 20 real redemptions
- Option B: In Stripe Dashboard, edit PILOT2025 coupon and set "Maximum redemptions" to 1, then redeem it once
2. **Attempt to register with the exhausted coupon:**
- Navigate to registration page
- Fill in user details with a NEW user (e.g., `customer21@example.com`)
- Fill in payment details
- Select any plan
- Enter coupon code: `PILOT2025`
- Try to apply/validate the coupon
**Expected Results:**
- ❌ Coupon validation fails
- ✅ Error message displayed: "This coupon has reached its maximum redemption limit" or similar
- ✅ User cannot proceed with this coupon
- ✅ User can still register without the coupon (immediate billing)
- ✅ Application logs show coupon limit reached
**Verification:**
1. **In your database:**
```sql
-- Check coupon redemption count
SELECT code, current_redemptions, max_redemptions
FROM coupons
WHERE code = 'PILOT2025';
-- Count actual redemptions
SELECT COUNT(*) FROM coupon_redemptions WHERE coupon_code = 'PILOT2025';
```
- Verify `current_redemptions` shows `20` (or your test limit)
- Verify actual redemption count matches
2. **Test alternate registration path:**
- Remove the coupon code
- Complete registration without coupon
- Verify subscription created successfully with immediate billing
**Important Notes:**
- Once the pilot program ends (20 customers), you can:
- Create a new coupon code (e.g., `PILOT2026`) for future pilots
- Increase the max redemptions if extending the program
- Disable PILOT2025 in your application's environment variables
---
### Scenario 8: Retrieve Invoices
2026-01-11 07:50:34 +01:00
**Objective:** Test invoice retrieval from Stripe.
**Steps:**
1. **Create subscription with successful payment** (Scenario 1)
2. **Retrieve invoices:**
```bash
curl -X GET http://localhost:8000/api/v1/subscriptions/{tenant_id}/invoices \
-H "Authorization: Bearer YOUR_AUTH_TOKEN"
```
**Expected Results:**
- ✅ List of invoices returned
- ✅ Each invoice contains:
- `id`
- `date`
- `amount`
- `currency`
- `status`
- `invoice_pdf` URL
- `hosted_invoice_url` URL
**Verification:**
- Click on `hosted_invoice_url` to view invoice in browser
- Download PDF from `invoice_pdf` URL
---
## Webhook Testing
Webhooks are critical for handling asynchronous events from Stripe. Test them thoroughly.
2026-01-12 22:15:11 +01:00
### Webhook Implementation Status
**✅ Your webhook implementation is COMPLETE and ready for production use.**
The webhook endpoint is implemented in [services/tenant/app/api/webhooks.py](services/tenant/app/api/webhooks.py:29-118) with:
**Implemented Features:**
- ✅ Proper signature verification (lines 52-61)
- ✅ Direct endpoint at `/webhooks/stripe` (bypasses gateway)
- ✅ All critical event handlers:
- `checkout.session.completed` - New checkout completion
- `customer.subscription.created` - New subscription tracking
- `customer.subscription.updated` - Status changes, plan updates (lines 162-205)
- `customer.subscription.deleted` - Cancellation handling (lines 207-240)
- `invoice.payment_succeeded` - Payment success, activation (lines 242-266)
- `invoice.payment_failed` - Payment failure, past_due status (lines 268-297)
- `customer.subscription.trial_will_end` - Trial ending notification
- ✅ Database updates with proper async handling
- ✅ Subscription cache invalidation (lines 192-200, 226-235)
- ✅ Structured logging with `structlog`
**Database Model Support:**
The [Subscription model](services/tenant/app/models/tenants.py:149-185) includes all necessary fields:
- `stripe_subscription_id` - Links to Stripe subscription
- `stripe_customer_id` - Links to Stripe customer
- `status` - Tracks subscription status
- `trial_ends_at` - Trial period tracking
- Complete lifecycle management
**Webhook Flow:**
1. Stripe sends event → `https://your-domain/webhooks/stripe`
2. Signature verification with `STRIPE_WEBHOOK_SECRET`
3. Event routing to appropriate handler
4. Database updates (subscription status, dates, etc.)
5. Cache invalidation for updated tenants
6. Success/failure response to Stripe
### Checking Tenant Service Logs
Since webhooks go directly to the tenant service (bypassing the gateway), you need to monitor tenant service logs to verify webhook processing.
#### Kubernetes Log Commands
**Real-time logs (Development with Kind/Colima):**
```bash
# Follow tenant service logs in real-time
kubectl logs -f deployment/tenant-service -n bakery-ia
# Filter for Stripe-related events only
kubectl logs -f deployment/tenant-service -n bakery-ia | grep -i stripe
# Filter for webhook events specifically
kubectl logs -f deployment/tenant-service -n bakery-ia | grep "webhook"
```
**Real-time logs (Production with MicroK8s):**
```bash
# Follow tenant service logs in real-time
microk8s kubectl logs -f deployment/tenant-service -n bakery-ia
# Filter for Stripe events
microk8s kubectl logs -f deployment/tenant-service -n bakery-ia | grep -i stripe
# Filter for webhook events
microk8s kubectl logs -f deployment/tenant-service -n bakery-ia | grep "webhook"
```
**Historical logs:**
```bash
# View last hour of logs
kubectl logs --since=1h deployment/tenant-service -n bakery-ia
# View last 100 log lines
kubectl logs --tail=100 deployment/tenant-service -n bakery-ia
# View logs for specific pod
kubectl get pods -n bakery-ia # Find pod name
kubectl logs <pod-name> -n bakery-ia
```
**Search logs for specific events:**
```bash
# Find subscription update events
kubectl logs deployment/tenant-service -n bakery-ia | grep "subscription.updated"
# Find payment processing
kubectl logs deployment/tenant-service -n bakery-ia | grep "payment"
# Find errors
kubectl logs deployment/tenant-service -n bakery-ia | grep -i error
```
#### Expected Log Output
When webhooks are processed successfully, you should see logs like:
**Successful webhook processing:**
```json
INFO Processing Stripe webhook event event_type=customer.subscription.updated event_id=evt_xxx
INFO Subscription updated in database subscription_id=sub_xxx tenant_id=tenant-uuid-xxx
```
**Payment succeeded:**
```json
INFO Processing invoice.payment_succeeded invoice_id=in_xxx subscription_id=sub_xxx
INFO Payment succeeded, subscription activated subscription_id=sub_xxx tenant_id=tenant-uuid-xxx
```
**Payment failed:**
```json
ERROR Processing invoice.payment_failed invoice_id=in_xxx subscription_id=sub_xxx customer_id=cus_xxx
WARNING Payment failed, subscription marked past_due subscription_id=sub_xxx tenant_id=tenant-uuid-xxx
```
**Signature verification errors (indicates webhook secret mismatch):**
```json
ERROR Invalid webhook signature error=...
```
#### Troubleshooting Webhook Issues via Logs
**1. Webhook not reaching service:**
- Check if any logs appear when Stripe sends webhook
- Verify network connectivity to tenant service
- Confirm webhook URL is correct in Stripe Dashboard
**2. Signature verification failing:**
```bash
# Look for signature errors
kubectl logs deployment/tenant-service -n bakery-ia | grep "Invalid webhook signature"
```
- Verify `STRIPE_WEBHOOK_SECRET` in secrets.yaml matches Stripe Dashboard
- Ensure webhook secret is properly base64 encoded
**3. Database not updating:**
```bash
# Check for database-related errors
kubectl logs deployment/tenant-service -n bakery-ia | grep -i "database\|commit\|rollback"
```
- Verify database connection is healthy
- Check for transaction commit errors
- Look for subscription not found errors
**4. Cache invalidation failing:**
```bash
# Check for cache-related errors
kubectl logs deployment/tenant-service -n bakery-ia | grep -i "cache\|redis"
```
- Verify Redis connection is healthy
- Check if cache service is running
### Testing Webhooks in Kubernetes Environment
You have two options for testing webhooks when running in Kubernetes:
#### Option 1: Using Stripe CLI with Port-Forward (Recommended for Local Dev)
This approach works with your Kind/Colima + Tilt setup:
**Step 1: Port-forward the tenant service**
```bash
# Forward tenant service to localhost
kubectl port-forward svc/tenant-service 8001:8000 -n bakery-ia
```
**Step 2: Use Stripe CLI to forward webhooks**
```bash
# In a new terminal, forward webhooks to the port-forwarded service
stripe listen --forward-to http://localhost:8001/webhooks/stripe
```
**Step 3: Copy the webhook secret**
```bash
# The stripe listen command will output a webhook secret like:
# > Ready! Your webhook signing secret is whsec_abc123...
# Encode it for Kubernetes secrets
echo -n "whsec_abc123..." | base64
```
**Step 4: Update secrets and restart**
```bash
# Update the STRIPE_WEBHOOK_SECRET in secrets.yaml with the base64 value
# Then apply:
kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
kubectl rollout restart deployment/tenant-service -n bakery-ia
```
**Step 5: Test webhook events**
```bash
# In another terminal, trigger test events
stripe trigger customer.subscription.updated
stripe trigger invoice.payment_succeeded
# Watch tenant service logs
kubectl logs -f deployment/tenant-service -n bakery-ia
```
#### Option 2: Using ngrok for Public URL (Works for Dev and Prod Testing)
This approach exposes your local Kubernetes cluster (or VPS) to the internet:
**For Development (Kind/Colima):**
**Step 1: Port-forward tenant service**
```bash
kubectl port-forward svc/tenant-service 8001:8000 -n bakery-ia
```
**Step 2: Start ngrok**
```bash
ngrok http 8001
```
**Step 3: Configure Stripe webhook**
- Copy the ngrok HTTPS URL (e.g., `https://abc123.ngrok.io`)
- Go to Stripe Dashboard → Developers → Webhooks
- Add endpoint: `https://abc123.ngrok.io/webhooks/stripe`
- Copy the webhook signing secret
- Update secrets.yaml and restart tenant service
**For Production (MicroK8s on VPS):**
If your VPS has a public IP, you can configure webhooks directly:
**Step 1: Ensure tenant service is accessible**
```bash
# Check if tenant service is exposed externally
microk8s kubectl get svc tenant-service -n bakery-ia
# If using NodePort or LoadBalancer, get the public endpoint
# If using Ingress, ensure DNS points to your VPS
```
**Step 2: Configure Stripe webhook**
- Use your public URL: `https://your-domain.com/webhooks/stripe`
- Or IP: `https://your-vps-ip:port/webhooks/stripe`
- Add endpoint in Stripe Dashboard
- Copy webhook signing secret
- Update secrets.yaml and apply to production cluster
2026-01-11 07:50:34 +01:00
### Option 1: Using Stripe CLI (Recommended for Local Development)
#### Step 1: Install Stripe CLI
**macOS:**
```bash
brew install stripe/stripe-cli/stripe
```
**Windows:**
Download from: https://github.com/stripe/stripe-cli/releases
**Linux:**
```bash
wget https://github.com/stripe/stripe-cli/releases/latest/download/stripe_linux_x86_64.tar.gz
tar -xvf stripe_linux_x86_64.tar.gz
sudo mv stripe /usr/local/bin/
```
#### Step 2: Login to Stripe
```bash
stripe login
```
This opens a browser to authorize the CLI.
#### Step 3: Forward Webhooks to Local Server
2026-01-13 22:22:38 +01:00
**For Development with Stripe CLI:**
The Stripe CLI creates a secure tunnel to forward webhook events from Stripe's servers to your local development environment.
2026-01-11 07:50:34 +01:00
```bash
2026-01-13 22:22:38 +01:00
# Forward webhook events to your gateway (which proxies to tenant service)
stripe listen --forward-to https://bakery-ia.local/api/v1/stripe
2026-01-11 07:50:34 +01:00
```
**Expected Output:**
```
> Ready! Your webhook signing secret is whsec_abc123... (^C to quit)
```
2026-01-13 22:22:38 +01:00
**Important - Update Your Configuration:**
1. **Copy the webhook signing secret** provided by `stripe listen`
2. **Encode it for Kubernetes:**
```bash
echo -n "whsec_abc123..." | base64
```
3. **Update secrets.yaml:**
```bash
# Edit infrastructure/kubernetes/base/secrets.yaml
# Update the STRIPE_WEBHOOK_SECRET with the base64 value
```
4. **Apply to your cluster:**
```bash
kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
kubectl rollout restart deployment/tenant-service -n bakery-ia
```
**Note:** The webhook secret from `stripe listen` is temporary and only works while the CLI is running. Each time you restart `stripe listen`, you'll get a new webhook secret.
2026-01-11 07:50:34 +01:00
#### Step 4: Trigger Test Events
Open a new terminal and run:
```bash
# Test subscription created
stripe trigger customer.subscription.created
# Test payment succeeded
stripe trigger invoice.payment_succeeded
# Test payment failed
stripe trigger invoice.payment_failed
# Test subscription updated
stripe trigger customer.subscription.updated
# Test subscription deleted
stripe trigger customer.subscription.deleted
# Test trial ending
stripe trigger customer.subscription.trial_will_end
```
#### Step 5: Verify Webhook Processing
**Check your application logs for:**
- ✅ "Processing Stripe webhook event"
- ✅ Event type logged
- ✅ Database updates (check subscription status)
- ✅ No signature verification errors
**Example log output:**
```
INFO Processing Stripe webhook event event_type=customer.subscription.updated
INFO Subscription updated in database subscription_id=sub_123 tenant_id=tenant-id
```
### Option 2: Using ngrok (For Public URL Testing)
#### Step 1: Install ngrok
Download from: https://ngrok.com/download
#### Step 2: Start ngrok
```bash
ngrok http 8000
```
**Output:**
```
Forwarding https://abc123.ngrok.io -> http://localhost:8000
```
#### Step 3: Update Stripe Webhook Endpoint
1. Go to Stripe Dashboard → Developers → Webhooks
2. Click on your endpoint
3. Update URL to: `https://abc123.ngrok.io/webhooks/stripe`
4. Save changes
#### Step 4: Test by Creating Real Events
Create a test subscription through your app, and webhooks will be sent to your ngrok URL.
### Option 3: Testing Webhook Handlers Directly
You can also test webhook handlers by sending test payloads:
```bash
curl -X POST http://localhost:8000/webhooks/stripe \
-H "Content-Type: application/json" \
-H "stripe-signature: test-signature" \
-d @webhook-test-payload.json
```
**Note:** This will fail signature verification unless you disable it temporarily for testing.
---
## Common Issues & Solutions
### Issue 1: "Stripe.js has not loaded correctly"
**Symptoms:**
- Error when submitting payment form
- Console error about Stripe not being loaded
**Solutions:**
1. Check internet connection (Stripe.js loads from CDN)
2. Verify `VITE_STRIPE_PUBLISHABLE_KEY` is set correctly
3. Check browser console for loading errors
4. Ensure no ad blockers blocking Stripe.js
### Issue 2: "Invalid signature" on Webhook
**Symptoms:**
- Webhook endpoint returns 400 error
- Log shows "Invalid webhook signature"
**Solutions:**
1. Verify `STRIPE_WEBHOOK_SECRET` matches Stripe Dashboard
2. For Stripe CLI, use the secret from `stripe listen` output
3. Ensure you're using the test mode secret, not live mode
4. Check that you're not modifying the request body before verification
### Issue 3: Payment Method Not Attaching
**Symptoms:**
- PaymentMethod created but subscription fails
- Error about payment method not found
**Solutions:**
1. Verify you're passing `paymentMethod.id` to backend
2. Check that payment method is being attached to customer
3. Ensure customer_id exists before creating subscription
4. Review backend logs for detailed error messages
### Issue 4: Test Mode vs Live Mode Confusion
**Symptoms:**
- Keys not working
- Data not appearing in dashboard
**Solutions:**
1. **Always check the mode indicator** in Stripe Dashboard
2. Test keys start with `pk_test_` and `sk_test_`
3. Live keys start with `pk_live_` and `sk_live_`
4. Never mix test and live keys
5. Use separate databases for test and live environments
### Issue 5: CORS Errors
**Symptoms:**
- Browser console shows CORS errors
- Requests to backend failing
**Solutions:**
1. Ensure FastAPI CORS middleware is configured:
```python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
### Issue 6: Webhook Events Not Processing
**Symptoms:**
- Webhooks received but database not updating
- Events logged but handlers not executing
**Solutions:**
1. Check event type matches handler (case-sensitive)
2. Verify database session is committed
3. Check for exceptions in handler functions
4. Review logs for specific error messages
5. Ensure subscription exists in database before update
### Issue 7: Card Element vs Payment Element Confusion
**Symptoms:**
- TypeError when calling `stripe.createPaymentMethod()`
- Elements not rendering correctly
**Solutions:**
- Use `PaymentElement` (modern, recommended)
- Call `elements.submit()` before creating payment method
- Pass `elements` object to `createPaymentMethod({ elements })`
- **Our implementation now uses the correct PaymentElement API**
---
## Production Checklist
Before going live with Stripe payments:
### Security
- [ ] All API keys stored in environment variables (never in code)
- [ ] Webhook signature verification enabled and working
- [ ] HTTPS enabled on all endpoints
- [ ] Rate limiting implemented on payment endpoints
- [ ] Input validation on all payment-related forms
- [ ] SQL injection prevention (using parameterized queries)
- [ ] XSS protection enabled
- [ ] CSRF tokens implemented where needed
### Stripe Configuration
- [ ] Live mode API keys obtained from Stripe Dashboard
- [ ] Live mode webhook endpoints configured
- [ ] Webhook signing secret updated for live mode
- [ ] Products and prices created in live mode
- [ ] Business information completed in Stripe Dashboard
- [ ] Bank account added for payouts
- [ ] Tax settings configured (if applicable)
- [ ] Stripe account activated and verified
### Application Configuration
- [ ] Environment variables updated for production
- [ ] Database migrations run on production database
- [ ] Redis cache configured and accessible
- [ ] Error monitoring/logging configured (e.g., Sentry)
- [ ] Payment failure notifications set up
- [ ] Trial ending notifications configured
- [ ] Invoice email delivery tested
### Testing
- [ ] All test scenarios passed (see above)
- [ ] Webhook handling verified for all event types
- [ ] 3D Secure authentication tested
- [ ] Subscription lifecycle tested (create, update, cancel, reactivate)
- [ ] Error handling tested for all failure scenarios
- [ ] Invoice retrieval tested
- [ ] Load testing completed
- [ ] Security audit performed
### Monitoring
- [ ] Stripe Dashboard monitoring set up
- [ ] Application logs reviewed regularly
- [ ] Webhook delivery monitoring configured
- [ ] Payment success/failure metrics tracked
- [ ] Alert thresholds configured
- [ ] Failed payment retry logic implemented
### Compliance
- [ ] Terms of Service updated to mention subscriptions
- [ ] Privacy Policy updated for payment data handling
- [ ] GDPR compliance verified (if applicable)
- [ ] PCI compliance requirements reviewed
- [ ] Customer data retention policy defined
- [ ] Refund policy documented
### Documentation
- [ ] API documentation updated
- [ ] Internal team trained on Stripe integration
- [ ] Customer support documentation created
- [ ] Troubleshooting guide prepared
- [ ] Subscription management procedures documented
---
## Quick Reference Commands
### Stripe CLI Commands
```bash
# Login to Stripe
stripe login
# Listen for webhooks (local development)
stripe listen --forward-to localhost:8000/webhooks/stripe
# Trigger test events
stripe trigger customer.subscription.created
stripe trigger invoice.payment_succeeded
stripe trigger invoice.payment_failed
# View recent events
stripe events list
# Get specific event
stripe events retrieve evt_abc123
# Test webhook endpoint
stripe webhooks test --endpoint-secret whsec_abc123
```
### Testing Shortcuts
```bash
# Start backend (from project root)
cd services/tenant && uvicorn app.main:app --reload --port 8000
# Start frontend (from project root)
cd frontend && npm run dev
# Update backend dependencies
cd services/tenant && pip install -r requirements.txt
# Run database migrations (if using Alembic)
cd services/tenant && alembic upgrade head
```
---
## Additional Resources
- **Stripe Documentation:** https://stripe.com/docs
- **Stripe API Reference:** https://stripe.com/docs/api
- **Stripe Testing Guide:** https://stripe.com/docs/testing
- **Stripe Webhooks Guide:** https://stripe.com/docs/webhooks
- **Stripe CLI Documentation:** https://stripe.com/docs/stripe-cli
- **React Stripe.js Docs:** https://stripe.com/docs/stripe-js/react
- **Stripe Support:** https://support.stripe.com
---
## Support
If you encounter issues not covered in this guide:
1. **Check Stripe Dashboard Logs:**
- Developers → Logs
- View detailed request/response information
2. **Review Application Logs:**
- Check backend logs for detailed error messages
- Look for structured log output from `structlog`
3. **Test in Isolation:**
- Test frontend separately
- Test backend API with cURL
- Verify webhook handling with Stripe CLI
4. **Contact Stripe Support:**
- Live chat available in Stripe Dashboard
- Email support: support@stripe.com
- Community forum: https://stripe.com/community
---
**Last Updated:** January 2026
**Stripe Library Versions:**
- Frontend: `@stripe/stripe-js@4.0.0`, `@stripe/react-stripe-js@3.0.0`
- Backend: `stripe@14.1.0`
2026-01-12 22:15:11 +01:00
---
## Appendix A: Quick Setup Checklist for Bakery IA
Use this checklist when setting up your Stripe test environment:
### 1. Stripe Dashboard Setup
- [ ] Create Stripe account and enable Test Mode
- [ ] Create **Starter Plan** product with 2 prices:
- [ ] Monthly: €49.00 EUR (Price ID: `price_XXXXXX`)
- [ ] Yearly: €490.00 EUR (Price ID: `price_XXXXXX`)
- [ ] Create **Professional Plan** product with 2 prices:
- [ ] Monthly: €149.00 EUR (Price ID: `price_XXXXXX`)
- [ ] Yearly: €1,490.00 EUR (Price ID: `price_XXXXXX`)
- [ ] Create **Enterprise Plan** product with 2 prices:
- [ ] Monthly: €499.00 EUR (Price ID: `price_XXXXXX`)
- [ ] Yearly: €4,990.00 EUR (Price ID: `price_XXXXXX`)
- [ ] Do NOT configure default trial periods on products
- [ ] Copy all 6 Price IDs for configuration
- [ ] Retrieve Stripe API keys (publishable and secret)
- [ ] Configure webhook endpoint (use Stripe CLI for local testing)
- [ ] Get webhook signing secret
### 2. Environment Configuration
**Note:** This project runs exclusively in Kubernetes (Kind/Colima + Tilt for dev, MicroK8s for prod).
**Frontend - Kubernetes ConfigMap:**
Update [infrastructure/kubernetes/base/configmap.yaml](infrastructure/kubernetes/base/configmap.yaml:374-378):
```yaml
# FRONTEND CONFIGURATION section
VITE_STRIPE_PUBLISHABLE_KEY: "pk_test_XXXXXXXXXXXXXXXX"
VITE_PILOT_MODE_ENABLED: "true"
VITE_PILOT_COUPON_CODE: "PILOT2025"
VITE_PILOT_TRIAL_MONTHS: "3"
```
**Backend - Kubernetes Secrets:**
Update [infrastructure/kubernetes/base/secrets.yaml](infrastructure/kubernetes/base/secrets.yaml:142-150):
```yaml
# payment-secrets section
data:
STRIPE_SECRET_KEY: <base64_of_sk_test_key>
STRIPE_WEBHOOK_SECRET: <base64_of_whsec_key>
```
Encode your keys:
```bash
echo -n "sk_test_YOUR_KEY" | base64
echo -n "whsec_YOUR_SECRET" | base64
```
**Apply to Kubernetes:**
```bash
# Development (Kind/Colima with Tilt - auto-reloads)
kubectl apply -f infrastructure/kubernetes/base/configmap.yaml
kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
# Production (MicroK8s)
microk8s kubectl apply -f infrastructure/kubernetes/base/configmap.yaml
microk8s kubectl apply -f infrastructure/kubernetes/base/secrets.yaml
microk8s kubectl rollout restart deployment/frontend-deployment -n bakery-ia
microk8s kubectl rollout restart deployment/tenant-service -n bakery-ia
```
### 3. Database Verification
- [ ] Verify PILOT2025 coupon exists in database:
```sql
SELECT * FROM coupons WHERE code = 'PILOT2025';
```
- [ ] Confirm coupon has max_redemptions = 20 and active = true
### 4. Test Scenarios Priority Order
Run these tests in order:
1. **Scenario 1:** Registration without coupon (immediate billing)
2. **Scenario 1B:** Registration with PILOT2025 coupon (90-day trial, €0 charge)
3. **Scenario 2:** 3D Secure authentication
4. **Scenario 3:** Declined payment
5. **Scenario 5:** Subscription cancellation
6. **Scenario 7:** Coupon redemption limit
8. **Webhook testing:** Use Stripe CLI
### 5. Stripe CLI Setup (Recommended)
```bash
# Install Stripe CLI
brew install stripe/stripe-cli/stripe # macOS
# Login
stripe login
# Forward webhooks to local backend
stripe listen --forward-to localhost:8000/webhooks/stripe
# Copy the webhook secret (whsec_...) to your backend .env
```
---
## Appendix B: Pricing Summary
### Monthly Pricing (EUR)
| Tier | Monthly | Yearly | Yearly Savings | Trial (with PILOT2025) |
|------|---------|--------|----------------|------------------------|
| **Starter** | €49 | €490 | €98 (17%) | 90 days €0 |
| **Professional** | €149 | €1,490 | €298 (17%) | 90 days €0 |
| **Enterprise** | €499 | €4,990 | €998 (17%) | 90 days €0 |
### PILOT2025 Coupon Details
- **Code:** `PILOT2025`
- **Type:** Trial extension (90 days)
- **Discount:** 100% off for first 3 months (€0 charge)
- **Applies to:** All tiers
- **Max redemptions:** 20 customers
- **Valid period:** 180 days from creation
- **Managed in:** Application database (NOT Stripe)
### Billing Timeline Example
**Professional Plan with PILOT2025:**
| Day | Event | Charge |
|-----|-------|--------|
| 0 | Subscription created | €0 |
| 1-90 | Trial period (trialing status) | €0 |
| 91 | Trial ends, first invoice | €149 |
| 121 | Second invoice (30 days later) | €149 |
| 151 | Third invoice | €149 |
| ... | Monthly recurring | €149 |
**Professional Plan without coupon:**
| Day | Event | Charge |
|-----|-------|--------|
| 0 | Subscription created | €149 |
| 30 | First renewal | €149 |
| 60 | Second renewal | €149 |
| ... | Monthly recurring | €149 |
### Test Card Quick Reference
| Purpose | Card Number | Result |
|---------|-------------|--------|
| **Success** | `4242 4242 4242 4242` | Payment succeeds |
| **3D Secure** | `4000 0025 0000 3155` | Requires authentication |
| **Declined** | `4000 0000 0000 0002` | Card declined |
| **Insufficient Funds** | `4000 0000 0000 9995` | Insufficient funds |
**Always use:**
- Expiry: Any future date (e.g., `12/30`)
- CVC: Any 3 digits (e.g., `123`)
- ZIP: Any valid format (e.g., `12345`)
---
## Appendix C: Troubleshooting PILOT2025 Coupon
### Issue: Coupon code not recognized
**Possible causes:**
1. Coupon not created in database
2. Frontend environment variable not set
3. Coupon expired or inactive
**Solution:**
```sql
-- Check if coupon exists
SELECT * FROM coupons WHERE code = 'PILOT2025';
-- If not found, manually insert (or restart application to trigger seeder)
INSERT INTO coupons (code, discount_type, discount_value, max_redemptions, active, valid_from, valid_until)
VALUES ('PILOT2025', 'trial_extension', 90, 20, true, NOW(), NOW() + INTERVAL '180 days');
```
### Issue: Trial not applying in Stripe
**Check:**
1. Backend logs for trial_period_days parameter
2. Verify coupon validation succeeded before subscription creation
3. Check subscription creation params in Stripe Dashboard logs
**Expected log output:**
```
INFO: Coupon PILOT2025 validated successfully
INFO: Creating subscription with trial_period_days=90
INFO: Stripe subscription created with status=trialing
```
### Issue: Customer charged immediately despite coupon
**Possible causes:**
1. Coupon validation failed silently
2. trial_period_days not passed to Stripe
3. Payment method attached before subscription (triggers charge)
**Debug:**
```sql
-- Check if coupon was redeemed
SELECT * FROM coupon_redemptions WHERE tenant_id = 'your-tenant-id';
-- Check subscription trial dates
SELECT stripe_subscription_id, status, trial_ends_at
FROM subscriptions WHERE tenant_id = 'your-tenant-id';
```
### Issue: Coupon already redeemed error
**This is expected behavior.** Each tenant can only use PILOT2025 once. To test multiple times:
- Create new tenant accounts with different emails
- Or manually delete redemption record from database (test only):
```sql
DELETE FROM coupon_redemptions WHERE tenant_id = 'test-tenant-id';
```