# Local Webhook Testing Guide

This guide will help you test Stripe webhooks locally before deploying to production. This is **CRITICAL** for subscription systems to avoid breaking production.

---

## 🎯 Overview

We need to test that our webhook handlers correctly sync subscription data when Stripe sends events like:
- `customer.subscription.updated` - Status changes, payment updates
- `invoice.payment_failed` - Failed payment attempts
- `invoice.payment_succeeded` - Successful payments
- `customer.subscription.deleted` - Subscription cancellations

---

## 📋 Prerequisites

Before testing, make sure you have:
- ✅ Stripe CLI installed
- ✅ Local Laravel app running
- ✅ Test Stripe account/keys in `.env`
- ✅ Database with test subscription data

---

## 🚀 Method 1: Stripe CLI (Recommended)

### Step 1: Install Stripe CLI

**Windows (PowerShell as Administrator):**
```powershell
# Using Scoop package manager
scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
scoop install stripe

# OR download manually from:
# https://github.com/stripe/stripe-cli/releases/latest
```

**Verify installation:**
```powershell
stripe --version
```

### Step 2: Login to Stripe

```powershell
stripe login
```
This will open your browser to authenticate with your Stripe account.

### Step 3: Start Your Laravel App

```powershell
php artisan serve
# Your app will run on http://localhost:8000
```

### Step 4: Forward Webhooks to Local Server

Open a new terminal and run:
```powershell
stripe listen --forward-to localhost:8000/stripe/webhook
```

**Expected output:**
```
> Ready! Your webhook signing secret is whsec_xxxxxxxxxxxxx (^C to quit)
```

⚠️ **IMPORTANT:** Copy the webhook signing secret (starts with `whsec_`)

### Step 5: Update Local Environment

Add the webhook secret to your `.env`:
```
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
```

**Restart your Laravel app** to load the new secret.

### Step 6: Test Webhook Events

#### A. Test Individual Events

Open another terminal and trigger test events:

**Test successful payment:**
```powershell
stripe trigger invoice.payment_succeeded
```

**Test failed payment:**
```powershell
stripe trigger invoice.payment_failed
```

**Test subscription update:**
```powershell
stripe trigger customer.subscription.updated
```

**Test subscription deletion:**
```powershell
stripe trigger customer.subscription.deleted
```

#### B. Monitor Logs

Watch your Laravel logs in real-time:
```powershell
Get-Content storage\logs\laravel.log -Wait -Tail 50
```

You should see:
```
Stripe Webhook Received - event_type: invoice.payment_succeeded
Subscription synced from webhook - user_id: 123, status: active
```

---

## 🚀 Method 2: Ngrok (For Testing with Real Stripe Dashboard)

Use this when you want to test using the actual Stripe Dashboard or need external access.

### Step 1: Install Ngrok

Download from: https://ngrok.com/download

```powershell
# Extract and run
.\ngrok.exe http 8000
```

### Step 2: Get Public URL

Ngrok will show:
```
Forwarding   https://xxxx-xx-xx-xxx-xxx.ngrok-free.app -> http://localhost:8000
```

Copy the HTTPS URL (e.g., `https://xxxx-xx-xx-xxx-xxx.ngrok-free.app`)

### Step 3: Configure Stripe Webhook

1. Go to https://dashboard.stripe.com/test/webhooks
2. Click **"Add endpoint"**
3. Enter: `https://xxxx-xx-xx-xxx-xxx.ngrok-free.app/stripe/webhook`
4. Select events to listen for:
   - ✅ `customer.subscription.updated`
   - ✅ `customer.subscription.deleted`
   - ✅ `invoice.payment_succeeded`
   - ✅ `invoice.payment_failed`
   - ✅ `customer.subscription.trial_will_end`
5. Click **"Add endpoint"**
6. Copy the **Signing secret** (starts with `whsec_`)
7. Update `.env` with the secret

### Step 4: Test in Stripe Dashboard

1. Go to your Stripe test customers
2. Manually update a subscription (change plan, cancel, etc.)
3. Watch the webhook logs in:
   - Stripe Dashboard → Webhooks → Recent events
   - Your Laravel logs: `storage/logs/laravel.log`

---

## 🧪 Method 3: Sync Command Testing (Safest)

Test the sync command to fix existing mismatches without touching webhooks.

### Step 1: Dry Run (No Changes)

```powershell
php artisan subscriptions:sync-from-stripe --dry-run
```

This shows what WOULD be changed without actually changing anything.

### Step 2: Test on Specific User

```powershell
php artisan subscriptions:sync-from-stripe --user=123 --dry-run
```

Replace `123` with a test user ID.

### Step 3: Apply Changes (When Ready)

```powershell
php artisan subscriptions:sync-from-stripe
```

---

## 🧪 Manual Testing Scenarios

### Test Case 1: Failed Payment → Status Update

**Scenario:** User's payment fails, Stripe marks subscription as `past_due`

**Steps:**
1. Create a test subscription in Stripe Dashboard
2. Use a test card that fails: `4000000000000341`
3. Wait for payment to fail (or trigger with Stripe CLI)
4. Check your database:
   ```sql
   SELECT user_id, stripe_status, ends_at FROM subscriptions WHERE user_id = 123;
   ```
5. Verify status is `past_due` and `ends_at` is updated

**Expected Result:**
- Database status: `past_due`
- Log entry: "Subscription marked as failed payment"
- User should still have access if within grace period

---

### Test Case 2: Trial Conversion → Active Subscription

**Scenario:** User's trial ends and payment succeeds

**Steps:**
1. Create a trial subscription with end date in past
2. Trigger: `stripe trigger customer.subscription.updated`
3. Check database:
   ```sql
   SELECT stripe_status, trial_ends_at, ends_at FROM subscriptions WHERE user_id = 123;
   ```

**Expected Result:**
- Status changes from `trialing` to `active`
- `trial_ends_at` is null or in past
- `ends_at` updated to new period end

---

### Test Case 3: Subscription Cancellation

**Scenario:** User cancels subscription

**Steps:**
1. Cancel a subscription in Stripe Dashboard
2. Check webhook is received
3. Verify database:
   ```sql
   SELECT stripe_status, ends_at FROM subscriptions WHERE stripe_id = 'sub_xxxxx';
   ```

**Expected Result:**
- Status: `canceled`
- `ends_at` set to now

---

## 🔍 Verification Checklist

Before deploying to production, verify:

- [ ] Webhook secret is configured in production `.env`
- [ ] All 5 webhook events are registered in Stripe Dashboard
- [ ] Webhook endpoint is accessible: `https://yourdomain.com/stripe/webhook`
- [ ] Test mode webhooks work correctly
- [ ] Logs are being written to `storage/logs/laravel.log`
- [ ] Sync command runs without errors: `php artisan subscriptions:sync-from-stripe --dry-run`
- [ ] Database fields update correctly:
  - [ ] `stripe_status`
  - [ ] `ends_at`
  - [ ] `trial_ends_at`
  - [ ] `stripe_price`

---

## 📊 Monitoring After Deployment

### Check Webhook Deliveries

**Stripe Dashboard:**
1. Go to Developers → Webhooks
2. Click your production endpoint
3. View "Recent events"
4. Check for failed deliveries (should be none)

### Check Laravel Logs

```powershell
# On production server
tail -f storage/logs/laravel.log | grep "Stripe Webhook"
```

### Run Sync Audit

```powershell
# Compare database vs Stripe (dry-run)
php artisan subscriptions:sync-from-stripe --dry-run
```

Should show: "✅ All subscriptions are in sync!"

---

## 🚨 Troubleshooting

### Webhook Not Received

1. Check ngrok/Stripe CLI is running
2. Verify webhook secret in `.env` matches
3. Check Laravel logs for signature errors
4. Ensure URL is accessible: `curl http://localhost:8000/stripe/webhook`

### Database Not Updating

1. Check logs: `tail -f storage/logs/laravel.log`
2. Verify subscription exists: `SELECT * FROM subscriptions WHERE stripe_id = 'sub_xxx'`
3. Check for errors in webhook handler
4. Manually trigger sync: `php artisan subscriptions:sync-from-stripe --user=123`

### Signature Verification Failed

1. Confirm webhook secret matches Stripe
2. Check raw payload isn't being modified by middleware
3. Verify CSRF is excluded for webhook routes

---

## 📝 Testing Checklist Summary

**Before Production:**
- [ ] Test all 5 webhook event types locally
- [ ] Verify database updates correctly
- [ ] Run sync command to fix existing mismatches
- [ ] Check logs for errors
- [ ] Test with failed payment scenario
- [ ] Test with trial conversion scenario

**In Production (Carefully):**
- [ ] Update Stripe webhook endpoint URL
- [ ] Add production webhook secret to `.env`
- [ ] Monitor webhook delivery success rate
- [ ] Run sync command once to fix existing data
- [ ] Keep monitoring logs for first 24 hours

---

## 💡 Pro Tips

1. **Always test in Stripe's test mode first**
2. **Keep Stripe CLI running during development**
3. **Monitor logs actively** when testing
4. **Use dry-run flag** before running sync commands
5. **Test failed payments specifically** - this is where most issues occur
6. **Have rollback plan ready** - database backup before production deployment

---

## 🆘 Emergency Procedures

### If Webhooks Break in Production

1. **Don't panic** - subscriptions in Stripe are still valid
2. **Check Stripe Dashboard** - webhooks → failed deliveries
3. **Run sync command** to fix database:
   ```bash
   php artisan subscriptions:sync-from-stripe
   ```
4. **Fix webhook issue** (usually secret mismatch)
5. **Re-verify** with dry-run:
   ```bash
   php artisan subscriptions:sync-from-stripe --dry-run
   ```

### Rollback Plan

If you need to revert:
1. Change webhook route back to old controller
2. Run database migrations rollback if needed
3. Run sync command to ensure consistency

---

**Remember: Test mode Stripe data won't affect real customers. Use it liberally!**
