# Afrexai Stripe Production

> Stripe Production Engineering

- Skill: `johnalbertini14-glitch/afrexai-stripe-production` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add johnalbertini14-glitch/afrexai-stripe-production`
- Raw SKILL.md: https://api.skillmd.com/api/skills/johnalbertini14-glitch/afrexai-stripe-production/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: johnalbertini14-glitch (https://skillmd.com/u/johnalbertini14-glitch)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/johnalbertini14-glitch/afrexai-stripe-production

---

# Stripe Production Engineering

Complete methodology for building, scaling, and operating production Stripe payment systems. From first checkout to enterprise billing at scale.

## Quick Health Check

Run through these 8 signals. Score 1 point each. Below 5 = stop and fix.

1. ✅ Webhook endpoint verified and idempotent
2. ✅ All API calls use idempotency keys
3. ✅ Customer portal enabled for self-service
4. ✅ Stripe Tax or manual tax collection configured
5. ✅ Failed payment retry logic with dunning emails
6. ✅ PCI compliance questionnaire completed (SAQ-A minimum)
7. ✅ Test mode → live mode checklist completed
8. ✅ Monitoring/alerting on payment failures and webhook errors

**Score: /8** — Below 5? Fix gaps before adding features.

---

## Phase 1: Architecture Strategy

### Integration Pattern Decision

| Pattern | Best For | Complexity | PCI Scope |
|---------|----------|------------|-----------|
| **Stripe Checkout (hosted)** | MVPs, quick launch | Low | SAQ-A (minimal) |
| **Payment Element (embedded)** | Custom UX, brand control | Medium | SAQ-A |
| **Card Element (legacy)** | Existing integrations | Medium | SAQ-A-EP |
| **Direct API** | Platform/marketplace | High | SAQ-D (avoid) |

**Decision rule:** Start with Checkout. Move to Payment Element only when you have specific UX requirements that Checkout can't solve.

### Billing Model Selection

| Model | Stripe Product | Use When |
|-------|---------------|----------|
| One-time | Payment Links / Checkout | Single purchases, lifetime deals |
| Recurring flat | Subscriptions | Fixed monthly/annual SaaS |
| Usage-based | Metered billing | API calls, compute, storage |
| Per-seat | Subscriptions + quantity | Team/user-based pricing |
| Tiered | Tiered pricing | Volume discounts |
| Hybrid | Subscription + usage records | Base fee + overage |

### Project Structure

```
src/
  payments/
    stripe.config.ts        # Stripe client initialization
    webhooks.handler.ts     # Webhook endpoint + event routing
    checkout.service.ts     # Checkout session creation
    subscription.service.ts # Subscription lifecycle
    customer.service.ts     # Customer CRUD + portal
    invoice.service.ts      # Invoice customization
    tax.service.ts          # Tax calculation
    types.ts                # Shared types
  middleware/
    webhook-verify.ts       # Signature verification middleware
```

### 7 Architecture Rules

1. **Never trust the client** — verify payment status server-side via webhooks, never from redirect URLs
2. **Webhooks are the source of truth** — your database updates from webhook events, not API call responses
3. **Idempotency everywhere** — every mutating API call gets an idempotency key
4. **One Stripe customer per user** — create customer at signup, store `stripe_customer_id` in your DB
5. **Metadata is your friend** — attach `user_id`, `plan`, `source` to every object for debugging
6. **Test mode mirrors live** — your test environment should use the exact same code paths
7. **Never store card numbers** — use Stripe.js/Elements, never handle raw card data

---

## Phase 2: Core Integration Patterns

### Stripe Client Setup

```typescript
// stripe.config.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18.acacia',  // Pin API version!
  maxNetworkRetries: 2,
  timeout: 10_000,
  telemetry: false,
});

export { stripe };
```

**Rules:**
- Pin API version — never use rolling latest
- Set explicit timeout (10s default is fine)
- Disable telemetry in production if privacy-sensitive
- Use restricted keys with minimum required permissions

### Customer Lifecycle

```typescript
// customer.service.ts
async function getOrCreateCustomer(userId: string, email: string, name?: string) {
  // Check DB first
  const existing = await db.users.findOne({ id: userId });
  if (existing?.stripeCustomerId) {
    return existing.stripeCustomerId;
  }

  // Create in Stripe
  const customer = await stripe.customers.create({
    email,
    name,
    metadata: {
      user_id: userId,
      created_via: 'signup',
    },
  });

  // Store mapping
  await db.users.update({ id: userId }, { stripeCustomerId: customer.id });
  return customer.id;
}
```

**Customer rules:**
- Create at signup, not at first purchase
- Always set metadata.user_id for reverse lookups
- Store stripe_customer_id in your users table
- Use customer email updates to sync — listen for `customer.updated`

### Checkout Session (Recommended Starting Point)

```typescript
// checkout.service.ts
async function createCheckoutSession(params: {
  customerId: string;
  priceId: string;
  mode: 'payment' | 'subscription';
  successUrl: string;
  cancelUrl: string;
  metadata?: Record<string, string>;
}) {
  const session = await stripe.checkout.sessions.create({
    customer: params.customerId,
    mode: params.mode,
    line_items: [{ price: params.priceId, quantity: 1 }],
    success_url: `${params.successUrl}?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: params.cancelUrl,
    metadata: params.metadata ?? {},

    // Recommended defaults
    allow_promotion_codes: true,
    billing_address_collection: 'auto',
    tax_id_collection: { enabled: true },
    customer_update: { address: 'auto', name: 'auto' },
    payment_method_types: ['card'],
    
    // For subscriptions
    ...(params.mode === 'subscription' && {
      subscription_data: {
        metadata: params.metadata ?? {},
        trial_period_days: 14,
      },
    }),
  }, {
    idempotencyKey: `checkout_${params.customerId}_${params.priceId}_${Date.now()}`,
  });

  return session;
}
```

### Payment Element (Custom UI)

```typescript
// Server: create PaymentIntent
async function createPaymentIntent(params: {
  customerId: string;
  amount: number;      // in cents!
  currency: string;
  metadata?: Record<string, string>;
}) {
  return stripe.paymentIntents.create({
    customer: params.customerId,
    amount: params.amount,
    currency: params.currency,
    automatic_payment_methods: { enabled: true },
    metadata: {
      ...params.metadata,
      created_at: new Date().toISOString(),
    },
  }, {
    idempotencyKey: `pi_${params.customerId}_${params.amount}_${Date.now()}`,
  });
}

// Client: React Payment Element
// <PaymentElement /> handles all payment method rendering
// Use confirmPayment() on form submit
```

---

## Phase 3: Subscription Management

### Subscription Lifecycle Events

```
Created → Active → Past Due → Canceled → (optionally) Unpaid
                ↓
            Trialing → Active
                ↓
            Paused → Resumed → Active
```

### Critical Webhook Events for Subscriptions

| Event | Action |
|-------|--------|
| `customer.subscription.created` | Provision access, set plan in DB |
| `customer.subscription.updated` | Handle plan changes, quantity updates |
| `customer.subscription.deleted` | Revoke access, clean up |
| `customer.subscription.trial_will_end` | Send conversion email (3 days before) |
| `invoice.payment_succeeded` | Confirm access renewal |
| `invoice.payment_failed` | Start dunning sequence |
| `customer.subscription.paused` | Restrict access, retain data |
| `customer.subscription.resumed` | Restore access |

### Plan Change Patterns

```typescript
// Upgrade (immediate)
async function upgradePlan(subscriptionId: string, newPriceId: string) {
  const sub = await stripe.subscriptions.retrieve(subscriptionId);
  return stripe.subscriptions.update(subscriptionId, {
    items: [{
      id: sub.items.data[0].id,
      price: newPriceId,
    }],
    proration_behavior: 'create_prorations',  // charge difference immediately
    payment_behavior: 'error_if_incomplete',
  });
}

// Downgrade (at period end)
async function downgradePlan(subscriptionId: string, newPriceId: string) {
  const sub = await stripe.subscriptions.retrieve(subscriptionId);
  return stripe.subscriptions.update(subscriptionId, {
    items: [{
      id: sub.items.data[0].id,
      price: newPriceId,
    }],
    proration_behavior: 'none',               // no refund, change at renewal
    billing_cycle_anchor: 'unchanged',
  });
}

// Cancel (at period end — always prefer this)
async function cancelSubscription(subscriptionId: string) {
  return stripe.subscriptions.update(subscriptionId, {
    cancel_at_period_end: true,
  });
  // User keeps access until period ends
  // Handle `customer.subscription.deleted` to revoke
}
```

### Dunning (Failed Payment Recovery)

```yaml
# Stripe Dashboard → Settings → Subscriptions → Smart Retries
retry_schedule:
  attempt_1: 1 day after failure    # Smart timing
  attempt_2: 3 days after failure
  attempt_3: 5 days after failure
  attempt_4: 7 days after failure   # Final attempt

# Custom dunning emails (supplement Stripe's built-in)
dunning_sequence:
  - day: 0
    action: "Email: payment failed, update card link"
    template: "Your payment of {amount} failed. Update → {portal_url}"
  - day: 3
    action: "Email: second notice, urgency"
    template: "Still unable to charge. Update payment to avoid interruption."
  - day: 5
    action: "Email: final warning"
    template: "Last chance to update. Access pauses in 48 hours."
  - day: 7
    action: "Pause or cancel subscription"
    note: "Automatic via Stripe if all retries fail"
```

### Usage-Based Billing

```typescript
// Report usage for metered billing
async function reportUsage(subscriptionItemId: string, quantity: number) {
  return stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
    quantity,
    timestamp: Math.floor(Date.now() / 1000),
    action: 'increment',  // or 'set' for absolute value
  }, {
    idempotencyKey: `usage_${subscriptionItemId}_${Date.now()}`,
  });
}

// Best practice: batch usage reports
// Don't report every API call individually — aggregate per hour/day
// Report at least daily to avoid surprise bills at period end
```

---

## Phase 4: Webhooks (The Most Critical Part)

### Webhook Handler Template

```typescript
// webhooks.handler.ts
import { stripe } from './stripe.config';
import type { Request, Response } from 'express';

const WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;

// Event handlers map
const handlers: Record<string, (event: Stripe.Event) => Promise<void>> = {
  'checkout.session.completed': handleCheckoutCompleted,
  'customer.subscription.created': handleSubscriptionCreated,
  'customer.subscription.updated': handleSubscriptionUpdated,
  'customer.subscription.deleted': handleSubscriptionDeleted,
  'invoice.payment_succeeded': handleInvoicePaymentSucceeded,
  'invoice.payment_failed': handleInvoicePaymentFailed,
  'customer.subscription.trial_will_end': handleTrialEnding,
  'payment_intent.succeeded': handlePaymentSucceeded,
  'payment_intent.payment_failed': handlePaymentFailed,
};

export async function handleWebhook(req: Request, res: Response) {
  // 1. Verify signature (NEVER skip this)
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,        // raw body, not parsed JSON!
      req.headers['stripe-signature']!,
      WEBHOOK_SECRET,
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return res.status(400).send('Invalid signature');
  }

  // 2. Idempotency check
  const alreadyProcessed = await db.webhookEvents.findOne({ eventId: event.id });
  if (alreadyProcessed) {
    return res.status(200).json({ received: true, duplicate: true });
  }

  // 3. Route to handler
  const handler = handlers[event.type];
  if (handler) {
    try {
      await handler(event);
      await db.webhookEvents.insert({ eventId: event.id, type: event.type, processedAt: new Date() });
    } catch (err) {
      console.error(`Webhook handler failed for ${event.type}:`, err);
      return res.status(500).send('Handler error');  // Stripe will retry
    }
  }

  // 4. Always return 200 quickly
  res.status(200).json({ received: true });
}
```

### 10 Webhook Rules

1. **Verify every signature** — never process unverified events
2. **Use raw body** — don't parse JSON before verification (Express: `express.raw({type: 'application/json'})`)
3. **Idempotency** — store processed event IDs, handle duplicates gracefully
4. **Return 200 fast** — do heavy processing async/in background
5. **Handle out-of-order** — events may arrive in unexpected order; check current state before applying
6. **Don't rely on event data alone** — for critical actions, re-fetch the object from the API
7. **Log everything** — event ID, type, relevant object IDs, processing result
8. **Monitor failures** — alert on repeated 500s or unhandled event types
9. **Use CLI for local dev** — `stripe listen --forward-to localhost:3000/webhooks`
10. **Register only events you handle** — don't subscribe to everything

### Essential Events by Use Case

| Use Case | Must-Have Events |
|----------|-----------------|
| One-time payments | `checkout.session.completed`, `payment_intent.succeeded`, `payment_intent.payment_failed` |
| Subscriptions | All subscription.* + `invoice.payment_succeeded`, `invoice.payment_failed`, `invoice.upcoming` |
| Marketplace/Connect | `account.updated`, `payout.paid`, `payout.failed`, `transfer.created` |
| Invoicing | `invoice.created`, `invoice.finalized`, `invoice.paid`, `invoice.payment_failed` |

---

## Phase 5: Customer Portal & Self-Service

```typescript
// Customer portal — saves you building billing UI
async function createPortalSession(customerId: string, returnUrl: string) {
  return stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: returnUrl,
  });
}

// Configure in Dashboard → Settings → Customer portal
// Enable: Update payment method, Cancel subscription, View invoices
// Optional: Plan switching, Invoice history download
```

### Portal Configuration Checklist

- [ ] Payment method update enabled
- [ ] Subscription cancellation with reason collection
- [ ] Plan switching with proration preview
- [ ] Invoice history visible
- [ ] Business information (name, logo) set
- [ ] Return URL configured
- [ ] Terms of service / privacy policy linked

---

## Phase 6: Tax Collection

### Tax Decision Tree

```
Do you sell to EU customers?
  YES → Need VAT collection
    Use Stripe Tax (automatic) OR manual tax rates
  NO → 
Do you sell to US customers in multiple states?
  YES → Need sales tax (nexus rules)
    Use Stripe Tax (automatic) — manual is nightmare
  NO →
Do you exceed $100K revenue or 200 transactions in any US state?
  YES → You have economic nexus — collect tax
  NO → May not need to collect, but verify with accountant
```

### Stripe Tax Setup

```typescript
// Enable automatic tax on Checkout
const session = await stripe.checkout.sessions.create({
  // ... other config
  automatic_tax: { enabled: true },
  customer_update: { address: 'auto' },  // Required for tax calculation
});

// For subscriptions via API
const subscription = await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: priceId }],
  automatic_tax: { enabled: true },
});
```

**Tax rules:**
- Stripe Tax handles calculation + collection automatically
- You still need to file/remit taxes yourself (or use Stripe Tax filing in supported regions)
- Always collect billing address for accurate tax calculation
- Enable tax ID collection for B2B reverse charge (EU)

---

## Phase 7: Stripe Connect (Marketplaces & Platforms)

### Connect Account Types

| Type | Control | Onboarding | Best For |
|------|---------|------------|----------|
| **Standard** | Low (Stripe-hosted dashboard) | Stripe-hosted | Marketplaces where sellers manage their own Stripe |
| **Express** | Medium (limited dashboard) | Stripe-hosted | Platforms managing payouts for contractors/sellers |
| **Custom** | Full (you build everything) | You build it | Enterprise platforms needing total control |

**Decision rule:** Use Express unless you have a specific reason not to.

### Payment Flow Patterns

```typescript
// Direct charge (platform takes cut)
const paymentIntent = await stripe.paymentIntents.create({
  amount: 10000,  // $100
  currency: 'usd',
  application_fee_amount: 1500,  // $15 platform fee
  transfer_data: {
    destination: 'acct_seller123',
  },
});

// Destination charge (seller's Stripe processes)
// Same as above but payment appears on seller's statement

// Separate charges and transfers (most flexible)
const charge = await stripe.paymentIntents.create({
  amount: 10000,
  currency: 'usd',
});
// Then transfer to seller
const transfer = await stripe.transfers.create({
  amount: 8500,
  currency: 'usd',
  destination: 'acct_seller123',
  transfer_group: 'order_123',
});
```

---

## Phase 8: Testing Strategy

### Test Mode Checklist

| Test | How | Pass Criteria |
|------|-----|--------------|
| Successful payment | Card: 4242424242424242 | Checkout completes, webhook fires, DB updated |
| Declined card | Card: 4000000000000002 | Error shown, no DB change |
| 3D Secure required | Card: 4000002500003155 | Auth modal shown, completes after |
| Insufficient funds | Card: 4000000000009995 | Graceful failure message |
| Subscription create | Use test price, complete checkout | Sub active, access granted |
| Payment failure + retry | Attach 4000000000000341, trigger invoice | Dunning sequence fires |
| Webhook replay | `stripe events resend evt_xxx` | Idempotent — no duplicate processing |
| Refund | Refund via API or Dashboard | Customer notified, access handled |
| Upgrade/downgrade | Change plan mid-cycle | Proration correct |
| Cancel at period end | Cancel, verify access until period end | Access maintained, then revoked |

### Stripe CLI for Local Development

```bash
# Install
brew install stripe/stripe-cli/stripe

# Login
stripe login

# Forward webhooks to local server
stripe listen --forward-to localhost:3000/api/webhooks/stripe

# Trigger specific events for testing
stripe trigger checkout.session.completed
stripe trigger invoice.payment_failed
stripe trigger customer.subscription.deleted
```

---

## Phase 9: Security & PCI Compliance

### PCI Compliance Levels

| Level | Criteria | Requirement |
|-------|----------|-------------|
| **SAQ-A** | Checkout or Elements (recommended) | Annual questionnaire, no scan |
| **SAQ-A-EP** | Client-side tokenization | Annual questionnaire + quarterly scan |
| **SAQ-D** | Direct API card handling (avoid) | Full audit, quarterly scans, penetration tests |

### Security Checklist (P0 — Mandatory)

- [ ] API keys in environment variables, never in code
- [ ] Webhook signatures verified on every request
- [ ] Restricted API keys with minimum permissions
- [ ] HTTPS everywhere (Stripe requires it)
- [ ] No card numbers logged, stored, or transmitted
- [ ] CSRF protection on payment endpoints
- [ ] Rate limiting on checkout creation
- [ ] Idempotency keys on all mutating calls

### API Key Strategy

```
Live mode:
  sk_live_xxx    → Server only, env var, restricted permissions
  pk_live_xxx    → Client-side (public, safe to expose)
  whsec_xxx      → Webhook secret, server only

Test mode:
  sk_test_xxx    → Same restrictions as live
  pk_test_xxx    → Client-side
  whsec_test_xxx → Webhook secret (different from live!)
```

**Restricted key permissions** (create in Dashboard → API Keys):
- Checkout Sessions: Write
- Customers: Write
- Subscriptions: Read/Write
- Webhook Endpoints: Read
- Everything else: None

---

## Phase 10: Go-Live Checklist

### Pre-Launch (P0 — Mandatory)

- [ ] Webhook endpoint registered and verified in live mode
- [ ] All webhook events subscribed (same as test)
- [ ] Live API keys in production environment
- [ ] Customer portal configured with live branding
- [ ] Test a real $1 payment end-to-end (then refund)
- [ ] Error handling for all payment states (success, failure, pending, requires_action)
- [ ] Logging: payment ID, customer ID, amount, status on every transaction
- [ ] PCI SAQ-A completed in Stripe Dashboard

### Pre-Launch (P1 — Within First Week)

- [ ] Monitoring: alert on payment failure rate > 5%
- [ ] Monitoring: alert on webhook delivery failures
- [ ] Receipt emails configured (Stripe auto-sends or custom)
- [ ] Refund process documented
- [ ] Dispute/chargeback response process
- [ ] Dunning emails active for subscriptions
- [ ] Stripe Tax enabled (if applicable)

### Pre-Launch (P2 — Within First Month)

- [ ] Revenue analytics dashboard
- [ ] MRR/churn tracking
- [ ] Coupon/promotion code strategy
- [ ] Annual vs monthly pricing toggle
- [ ] Customer portal self-service verified

---

## Phase 11: Monitoring & Operations

### Key Metrics Dashboard

```yaml
metrics:
  payment_success_rate:
    calculation: "successful_payments / total_attempts × 100"
    healthy: ">= 95%"
    warning: "90-95%"
    critical: "< 90%"
    
  webhook_delivery_rate:
    calculation: "successful_deliveries / total_events × 100"  
    healthy: ">= 99.5%"
    critical: "< 99%"
    
  average_revenue_per_user:
    calculation: "total_revenue / active_customers"
    track: "weekly trend"
    
  monthly_recurring_revenue:
    calculation: "sum(active_subscription_amounts)"
    track: "monthly growth rate"
    
  churn_rate:
    calculation: "canceled_subscriptions / total_active × 100"
    healthy: "< 5% monthly"
    warning: "5-10%"
    critical: "> 10%"
    
  involuntary_churn:
    calculation: "failed_payment_cancellations / total_churn × 100"
    note: "Should be < 30% of total churn — fix with better dunning"
```

### Weekly Review Checklist

- [ ] Payment success rate vs last week
- [ ] Failed payments — any patterns? (card type, region, amount)
- [ ] Webhook failures — any endpoints timing out?
- [ ] New disputes/chargebacks — respond within 7 days
- [ ] Subscription metrics: new, churned, upgraded, downgraded
- [ ] Revenue: MRR, net new MRR, expansion MRR, contraction MRR

---

## Phase 12: Advanced Patterns

### Pricing Page with Annual Toggle

```typescript
// Create both monthly and annual prices for each plan
const prices = {
  starter: {
    monthly: 'price_starter_monthly',   // $29/mo
    annual: 'price_starter_annual',     // $290/yr (save ~17%)
  },
  pro: {
    monthly: 'price_pro_monthly',       // $79/mo
    annual: 'price_pro_annual',         // $790/yr
  },
};

// Client toggles monthly/annual → creates checkout with correct priceId
```

### Grandfathering & Price Increases

```typescript
// New price, existing customers keep old price
// 1. Create new price object (don't modify existing)
// 2. New signups use new price
// 3. Existing subs stay on old price until they change plans
// 4. Optional: scheduled price migration with notice

async function schedulePriceIncrease(subscriptionId: string, newPriceId: string, effectiveDate: Date) {
  // Create a subscription schedule
  const schedule = await stripe.subscriptionSchedules.create({
    from_subscription: subscriptionId,
  });
  
  // Add phase with new price
  await stripe.subscriptionSchedules.update(schedule.id, {
    phases: [
      { items: [{ price: currentPriceId }], end_date: Math.floor(effectiveDate.getTime() / 1000) },
      { items: [{ price: newPriceId }] },
    ],
  });
}
```

### Coupon & Promotion Codes

```typescript
// Create coupon (reusable)
const coupon = await stripe.coupons.create({
  percent_off: 20,
  duration: 'repeating',
  duration_in_months: 3,
  max_redemptions: 100,
  metadata: { campaign: 'launch_2024' },
});

// Create promotion code (shareable link)
const promoCode = await stripe.promotionCodes.create({
  coupon: coupon.id,
  code: 'LAUNCH20',
  max_redemptions: 50,
  expires_at: Math.floor(new Date('2024-12-31').getTime() / 1000),
});
```

### Handling Disputes (Chargebacks)

```yaml
dispute_response_process:
  when_received:
    - Log dispute details: amount, reason, deadline
    - Alert team immediately
    - Begin evidence collection within 24 hours
    
  evidence_to_collect:
    - Customer communication (emails, chat logs)
    - Delivery proof (access logs, download records)
    - Terms of service acceptance timestamp
    - Refund policy shown at checkout
    - IP address and device fingerprint
    - Prior successful transactions from same customer
    
  submit:
    - Within 7 days (Stripe deadline is usually 21 days, but act fast)
    - Use Stripe Dashboard evidence submission form
    - Include written rebuttal addressing specific reason code
    
  prevention:
    - Clear billing descriptor (customer recognizes charge)
    - Send receipt emails immediately
    - Offer easy refunds before disputes escalate
    - Use Radar for fraud detection
```

---

## 10 Common Mistakes

| # | Mistake | Fix |
|---|---------|-----|
| 1 | Trusting client-side redirect as payment confirmation | Always verify via webhook |
| 2 | Parsing JSON body before webhook signature verification | Use raw body buffer |
| 3 | No idempotency keys on API calls | Add to every mutating call |
| 4 | Immediately canceling instead of cancel_at_period_end | Always cancel at period end unless refunding |
| 5 | Amounts in dollars instead of cents | Stripe uses smallest currency unit (cents) |
| 6 | Not handling 3D Secure / requires_action status | Check payment_intent.status, handle authentication |
| 7 | Same webhook secret for test and live | Use separate secrets per environment |
| 8 | Not testing with Stripe CLI locally | Set up `stripe listen` in development |
| 9 | Hardcoding price IDs | Use config/env vars, different per environment |
| 10 | No dunning strategy for failed subscription payments | Configure Smart Retries + custom emails |

---

## Quality Rubric (0-100)

| Dimension | Weight | Criteria |
|-----------|--------|----------|
| Webhook reliability | 25% | Signature verification, idempotency, error handling, event coverage |
| Security & PCI | 20% | SAQ-A compliance, key management, no card data exposure |
| Subscription lifecycle | 15% | Create/upgrade/downgrade/cancel/pause all handled correctly |
| Customer experience | 15% | Portal, receipts, dunning emails, clear error messages |
| Testing coverage | 10% | All payment states tested, CLI setup, edge cases |
| Monitoring | 10% | Failure alerts, revenue metrics, dispute tracking |
| Code quality | 5% | TypeScript types, error handling, idempotency keys, logging |

---

## Commands

The agent responds to these natural language requests:

1. "Set up Stripe checkout" → Full Checkout integration with webhook handler
2. "Add subscriptions" → Subscription lifecycle with plan changes and dunning
3. "Configure webhooks" → Webhook handler with signature verification and idempotency
4. "Set up customer portal" → Billing portal configuration
5. "Add usage-based billing" → Metered subscription with usage reporting
6. "Stripe security audit" → PCI compliance check + security hardening
7. "Go-live checklist" → Pre-launch verification for production
8. "Set up Stripe Connect" → Marketplace/platform payment flows
9. "Add coupons and promos" → Promotion code system
10. "Review payment metrics" → Revenue and payment health dashboard
11. "Handle a dispute" → Chargeback response process
12. "Migrate pricing" → Grandfathering and price increase strategy

