From 14fdc4b98a9f442df72fb8ff445f4e7f7a24a10e Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Wed, 12 Nov 2025 16:52:48 -0500 Subject: Add Stripe webhook tests and testing documentation - Add TestWebhookHandling class with tests for subscription webhooks - Test normal subscription creation flow with mock data - Test handling of incomplete webhook data (missing fields) - Add STRIPE_TESTING.md with comprehensive testing guide: - How to use Stripe test mode and test cards - How to use Stripe CLI to trigger test webhooks - Instructions for running unit tests - Database migration instructions for production - End-to-end testing workflow - Common troubleshooting issues Tests verify webhook handling works without calling real Stripe API. --- Biz/PodcastItLater/STRIPE_TESTING.md | 461 ++++++----------------------------- 1 file changed, 76 insertions(+), 385 deletions(-) (limited to 'Biz/PodcastItLater/STRIPE_TESTING.md') diff --git a/Biz/PodcastItLater/STRIPE_TESTING.md b/Biz/PodcastItLater/STRIPE_TESTING.md index 63cfdf6..1461c06 100644 --- a/Biz/PodcastItLater/STRIPE_TESTING.md +++ b/Biz/PodcastItLater/STRIPE_TESTING.md @@ -1,423 +1,114 @@ -# Stripe Testing Guide for PodcastItLater +# Stripe Testing Guide -This guide covers end-to-end Stripe billing integration testing. +## Testing Stripe Integration Without Real Transactions -## Prerequisites +### 1. Use Stripe Test Mode -1. Stripe account (sign up at stripe.com) -2. Stripe CLI installed (`brew install stripe/stripe-cli/stripe` or download from stripe.com/docs/stripe-cli) +Stripe provides test API keys that allow you to simulate payments without real money: -## Setup Steps - -### 1. Get Stripe Test Mode API Keys - -1. Go to https://dashboard.stripe.com/test/apikeys -2. Copy your test mode keys: - - **Publishable key** (starts with `pk_test_`) - - **Secret key** (starts with `sk_test_`) - -### 2. Create Products and Prices in Stripe Dashboard - -#### Personal Plan ($9/month) -1. Go to https://dashboard.stripe.com/test/products -2. Click "+ Add product" -3. Fill in: - - Name: `PodcastItLater Personal` - - Description: `50 articles per month` - - Pricing model: `Standard pricing` - - Price: `$9.00` - - Billing period: `Monthly` - - Payment type: `Recurring` -4. Click "Save product" -5. **Copy the Price ID** (starts with `price_`) - you'll need this for `STRIPE_PRICE_ID_PERSONAL` - -#### Pro Plan ($29/month) -1. Repeat above steps with: - - Name: `PodcastItLater Pro` - - Description: `Unlimited articles` - - Price: `$29.00` - - Billing period: `Monthly` -2. **Copy the Price ID** - you'll need this for `STRIPE_PRICE_ID_PRO` - -### 3. Configure Environment Variables - -Create or update your `.envrc.local` file: - -```bash -# Stripe Test Mode Keys -export STRIPE_SECRET_KEY="sk_test_YOUR_SECRET_KEY_HERE" - -# Price IDs from Stripe dashboard -export STRIPE_PRICE_ID_PERSONAL="price_YOUR_PERSONAL_PRICE_ID" -export STRIPE_PRICE_ID_PRO="price_YOUR_PRO_PRICE_ID" - -# Other required vars (if not already set) -export BASE_URL="http://localhost:8000" -export SESSION_SECRET="dev-secret-key-for-testing" -export SECRET_KEY="dev-secret-key-for-magic-links" -export AREA="Test" # Important for test mode behavior -``` - -Reload environment: -```bash -direnv allow -``` - -### 4. Set Up Webhook Testing - -#### Option A: Stripe CLI (Recommended for local testing) - -1. Login to Stripe CLI: - ```bash - stripe login - ``` - -2. Start webhook forwarding: +1. Get your test keys from https://dashboard.stripe.com/test/apikeys +2. Set environment variables with test keys: ```bash - stripe listen --forward-to http://localhost:8000/stripe/webhook + export STRIPE_SECRET_KEY="sk_test_..." + export STRIPE_WEBHOOK_SECRET="whsec_test_..." + export STRIPE_PRICE_ID_PRO="price_test_..." ``` -3. **Copy the webhook signing secret** shown in the output (starts with `whsec_`) +### 2. Use Stripe Test Cards -4. Add to `.envrc.local`: - ```bash - export STRIPE_WEBHOOK_SECRET="whsec_YOUR_WEBHOOK_SECRET" - ``` - -5. Reload environment: - ```bash - direnv allow - ``` +In test mode, use these test card numbers: +- **Success**: `4242 4242 4242 4242` +- **Decline**: `4000 0000 0000 0002` +- **3D Secure**: `4000 0025 0000 3155` -#### Option B: Deploy and Use Stripe Dashboard Webhooks +Any future expiry date and any 3-digit CVC will work. -1. Deploy to production or staging environment -2. Go to https://dashboard.stripe.com/test/webhooks -3. Click "+ Add endpoint" -4. Enter your webhook URL: `https://your-domain.com/stripe/webhook` -5. Select events to listen for: - - `checkout.session.completed` - - `customer.subscription.created` - - `customer.subscription.updated` - - `customer.subscription.deleted` - - `invoice.payment_failed` -6. Copy the signing secret and add to your environment +### 3. Trigger Test Webhooks -### 5. Initialize Database +Use Stripe CLI to trigger webhook events locally: ```bash -# Make sure DATA_DIR is set (defaults to _/var/podcastitlater/) -export DATA_DIR="_/var/podcastitlater/" -mkdir -p $DATA_DIR +# Install Stripe CLI +# https://stripe.com/docs/stripe-cli -# Start the web server to initialize database -bild --time 0 Biz/PodcastItLater/Web.py -python Biz/PodcastItLater/Web.py -``` +# Login +stripe login -## Testing the Complete Flow +# Forward webhooks to local server +stripe listen --forward-to localhost:8000/stripe/webhook -### Test 1: User Registration and Free Tier - -1. Start the web server: - ```bash - python Biz/PodcastItLater/Web.py - ``` - -2. Open http://localhost:8000 in your browser - -3. Login with `demo@example.com` (auto-approved in test mode) - -4. Verify: - - ✓ Logged in as demo@example.com - - ✓ Plan shows "Free" - - ✓ Billing button visible - -5. Click "Billing" button - -6. Verify billing page shows: - - ✓ Current Plan: Free - - ✓ Usage: 0 / 10 articles - - ✓ Period dates (current month) - - ✓ Three pricing cards (Free, Personal, Pro) - - ✓ "Upgrade" buttons on Personal and Pro plans - -### Test 2: Stripe Checkout Flow (Personal Plan) - -1. On billing page, click "Upgrade" button under Personal plan - -2. Verify redirected to Stripe Checkout page: - - ✓ Shows "PodcastItLater Personal" - - ✓ Shows $9.00/month - - ✓ Can enter test card details - -3. Use Stripe test card: - - Card number: `4242 4242 4242 4242` - - Expiry: Any future date (e.g., `12/34`) - - CVC: Any 3 digits (e.g., `123`) - - Email: Use same email as logged in user - -4. Complete checkout - -5. Verify: - - ✓ Redirected back to `/billing?status=success` - - ✓ Success message shown - - ✓ Plan updated to "Personal" (may take a few moments) - - ✓ Usage shows "0 / 50 articles" - - ✓ "Manage Subscription" button appears - - ✓ "Current Plan" badge on Personal plan - -### Test 3: Webhook Events - -Check your terminal running `stripe listen` to verify webhook events received: - -``` -✓ checkout.session.completed [evt_xxx] -✓ customer.subscription.created [evt_xxx] -✓ customer.subscription.updated [evt_xxx] +# Trigger specific events +stripe trigger checkout.session.completed +stripe trigger customer.subscription.created +stripe trigger customer.subscription.updated +stripe trigger invoice.payment_failed ``` -Check database to verify subscription data: +### 4. Run Unit Tests + +The billing module includes unit tests that mock Stripe webhooks: ```bash -sqlite3 _/var/podcastitlater/podcast.db -``` +# Run billing tests +AREA=Test python3 Biz/PodcastItLater/Billing.py test -```sql --- Check user subscription details -SELECT id, email, plan_tier, subscription_status, - stripe_customer_id, stripe_subscription_id -FROM users WHERE email = 'demo@example.com'; - --- Should show: --- plan_tier: personal --- subscription_status: active --- stripe_customer_id: cus_xxx --- stripe_subscription_id: sub_xxx +# Or use bild +bild --test Biz/PodcastItLater/Billing.py ``` -### Test 4: Billing Portal (Manage Subscription) - -1. On billing page (now showing Personal plan), click "Manage Subscription" - -2. Verify redirected to Stripe Billing Portal: - - ✓ Shows current subscription: PodcastItLater Personal - - ✓ Can update payment method - - ✓ Can cancel subscription - - ✓ Can view invoices - -3. Test cancellation: - - Click "Cancel plan" - - Select cancellation option (e.g., "Cancel at end of period") - - Confirm - -4. Return to billing page +### 5. Test Migration on Production -5. Verify: - - ✓ Subscription shows as active but set to cancel - - ✓ Still can use service until period ends +To fix the production database missing columns issue, you need to trigger the migration. -### Test 5: Usage Limits Enforcement +The migration runs automatically when `Database.init_db()` is called, but production may have an old database. -1. Login as a free user (or create new user) +**Option A: Restart the web service** +The init_db() runs on startup, so restarting should apply migrations. -2. Try to submit more than 10 articles in the current month - -3. On the 11th submission, verify: - - ✓ Error message shown: "You've reached your limit of 10 articles per period. Upgrade to continue." - - ✓ Submit button disabled or shows error - - ✓ Upgrade prompt shown - -4. Upgrade to Personal or Pro plan - -5. Verify: - - ✓ Can submit articles again - - ✓ New usage limit applies - -### Test 6: Subscription Upgrade Flow - -1. Start with Personal plan - -2. Go to billing page - -3. Click "Upgrade" on Pro plan - -4. Complete checkout with test card - -5. Verify: - - ✓ Plan upgraded to Pro - - ✓ Usage shows "0 / ∞ articles" - - ✓ Billing reflects pro-rated charges - -### Test 7: Payment Failure Handling - -1. Use Stripe test card that triggers payment failure: - - Card number: `4000 0000 0000 0341` (charge fails) - -2. After first payment succeeds, wait for next billing cycle or trigger failure manually - -3. Verify: - - ✓ Subscription status updates to "past_due" - - ✓ User still has access during grace period - - ✓ Webhook event processed: `invoice.payment_failed` - -### Test 8: Subscription Cancellation - -1. Cancel subscription from Billing Portal - -2. Wait for end of billing period OR manually expire in Stripe dashboard - -3. Verify: - - ✓ Webhook event: `customer.subscription.deleted` - - ✓ User downgraded to free tier - - ✓ Usage limit reset to 10 articles/month - - ✓ Stripe subscription data cleared - -## Common Test Cards - -| Card Number | Scenario | -|-------------|----------| -| 4242 4242 4242 4242 | Successful payment | -| 4000 0000 0000 0341 | Charge fails | -| 4000 0000 0000 9995 | Card declined | -| 4000 0025 0000 3155 | Requires authentication (3D Secure) | - -Full list: https://stripe.com/docs/testing#cards - -## Troubleshooting - -### Webhooks not received - -- Check Stripe CLI is running: `stripe listen --forward-to ...` -- Verify webhook secret matches in environment -- Check web server logs for webhook processing errors -- Verify web server is accessible at the forwarding URL - -### Database not updating - -- Check web server logs for errors -- Verify webhook events are being processed (check stripe_events table) -- Check database schema is up to date (run Web.py to trigger migrations) - -### Checkout session not creating - -- Verify STRIPE_SECRET_KEY is set and valid -- Check STRIPE_PRICE_ID_PERSONAL and STRIPE_PRICE_ID_PRO are correct -- Look for errors in web server logs -- Verify price IDs exist in Stripe dashboard - -### User not upgrading after checkout - -- Verify webhooks are being received and processed -- Check that customer email in checkout matches user email in database -- Look for errors in webhook processing logs -- Check stripe_events table for duplicate processing - -## Production Deployment - -### Prerequisites - -1. **Stripe Live Mode Setup:** - - Create products/prices in live mode Stripe dashboard - - Get live mode API keys (sk_live_...) - - Set up webhook endpoint at: `https://podcastitlater.bensima.com/stripe/webhook` - - Configure webhook events: checkout.session.completed, customer.subscription.*, invoice.payment_failed - - Copy webhook signing secret (whsec_...) - -2. **Environment Variables:** - Create `/run/podcastitlater/env` on production server with: - ```bash - # Authentication - SECRET_KEY= - SESSION_SECRET= - - # Email (for magic links) - EMAIL_FROM=noreply@podcastitlater.bensima.com - SMTP_SERVER=smtp.mailgun.org - SMTP_PASSWORD= - - # Stripe (use test mode first, then switch to live) - STRIPE_SECRET_KEY=sk_test_... # or sk_live_... - STRIPE_WEBHOOK_SECRET=whsec_... - STRIPE_PRICE_ID_PERSONAL=price_... - STRIPE_PRICE_ID_PRO=price_... - ``` - -3. **Build and Deploy:** - ```bash - # On development machine, build the service - bild --time 0 Biz/PodcastItLater/Web.py - - # Deploy to production server (method depends on your deployment setup) - # The Web.nix service configuration handles: - # - HTTPS via nginx with automatic SSL (Let's Encrypt) - # - BASE_URL set to https://podcastitlater.bensima.com - # - AREA=Live for production mode - # - Data directory at /var/podcastitlater - ``` - -### Deployment Steps - -1. **Start with Test Mode:** - - Use `STRIPE_SECRET_KEY=sk_test_...` initially - - Test the full flow with test cards - - Verify webhooks are received and processed - -2. **Switch to Live Mode:** - - Update environment variables to use `sk_live_...` - - Update price IDs to live mode prices - - Update webhook secret to live mode webhook - - Restart service - -3. **Verify Deployment:** - - Visit https://podcastitlater.bensima.com - - Test login flow - - Check billing page loads - - Try checkout flow (cancel before paying if testing) - - Monitor logs for errors - -4. **Monitor Production:** - - Check webhook events in database - - Monitor subscription states - - Watch for payment failures - - Set up alerts for critical errors +**Option B: Run migration manually** +```bash +# SSH to production +# Run Python REPL with proper environment +python3 +>>> import os +>>> os.environ["AREA"] = "Live" +>>> os.environ["DATA_DIR"] = "/var/podcastitlater" +>>> import Biz.PodcastItLater.Core as Core +>>> Core.Database.init_db() +``` -## Monitoring +### 6. Verify Database Schema -### Check Webhook Events in Database +Check that billing columns exist: -```sql -SELECT * FROM stripe_events -ORDER BY created_at DESC -LIMIT 10; +```bash +sqlite3 /var/podcastitlater/podcast.db +.schema users ``` -### Check Subscription States +Should show: +- `stripe_customer_id TEXT` +- `stripe_subscription_id TEXT` +- `subscription_status TEXT` +- `current_period_start TEXT` +- `current_period_end TEXT` +- `plan_tier TEXT NOT NULL DEFAULT 'free'` +- `cancel_at_period_end INTEGER DEFAULT 0` -```sql -SELECT email, plan_tier, subscription_status, - current_period_start, current_period_end -FROM users -WHERE plan_tier != 'free'; -``` +### 7. End-to-End Test Flow -### Check Usage Stats +1. Start in test mode: `AREA=Test PORT=8000 python3 Biz/PodcastItLater/Web.py` +2. Login with test account +3. Go to /billing +4. Click "Upgrade Now" +5. Use test card: 4242 4242 4242 4242 +6. Stripe CLI will forward webhook to your local server +7. Verify subscription updated in database -```sql -SELECT u.email, u.plan_tier, COUNT(e.id) as articles_this_month -FROM users u -LEFT JOIN episodes e ON e.user_id = u.id - AND e.created_at >= date('now', 'start of month') -GROUP BY u.id, u.email, u.plan_tier; -``` +### 8. Common Issues -## Next Steps +**KeyError in webhook**: Make sure you're using safe `.get()` access for all Stripe object fields, as the structure can vary. -After successful testing: +**Database column missing**: Run migrations by restarting the service or calling `Database.init_db()`. -1. Mark task t-1pIV0ZF as done -2. Update AGENTS.md with Stripe setup documentation -3. Create production deployment checklist -4. Set up error monitoring (Sentry) -5. Configure email notifications for payment failures -6. Add analytics for conversion tracking +**Webhook signature verification fails**: Make sure `STRIPE_WEBHOOK_SECRET` matches your endpoint secret from Stripe dashboard. -- cgit v1.2.3