{
  "schemaVersion": "1.0",
  "item": {
    "slug": "afrexai-stripe-production",
    "name": "Stripe Production Engineering",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/1kalin/afrexai-stripe-production",
    "canonicalUrl": "https://clawhub.ai/1kalin/afrexai-stripe-production",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/afrexai-stripe-production",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-stripe-production",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "README.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-07T17:22:31.273Z",
      "expiresAt": "2026-05-14T17:22:31.273Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
        "contentDisposition": "attachment; filename=\"afrexai-annual-report-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/afrexai-stripe-production"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/afrexai-stripe-production",
    "agentPageUrl": "https://openagent3.xyz/skills/afrexai-stripe-production/agent",
    "manifestUrl": "https://openagent3.xyz/skills/afrexai-stripe-production/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/afrexai-stripe-production/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Stripe Production Engineering",
        "body": "Complete methodology for building, scaling, and operating production Stripe payment systems. From first checkout to enterprise billing at scale."
      },
      {
        "title": "Quick Health Check",
        "body": "Run through these 8 signals. Score 1 point each. Below 5 = stop and fix.\n\n✅ Webhook endpoint verified and idempotent\n✅ All API calls use idempotency keys\n✅ Customer portal enabled for self-service\n✅ Stripe Tax or manual tax collection configured\n✅ Failed payment retry logic with dunning emails\n✅ PCI compliance questionnaire completed (SAQ-A minimum)\n✅ Test mode → live mode checklist completed\n✅ Monitoring/alerting on payment failures and webhook errors\n\nScore: /8 — Below 5? Fix gaps before adding features."
      },
      {
        "title": "Integration Pattern Decision",
        "body": "PatternBest ForComplexityPCI ScopeStripe Checkout (hosted)MVPs, quick launchLowSAQ-A (minimal)Payment Element (embedded)Custom UX, brand controlMediumSAQ-ACard Element (legacy)Existing integrationsMediumSAQ-A-EPDirect APIPlatform/marketplaceHighSAQ-D (avoid)\n\nDecision rule: Start with Checkout. Move to Payment Element only when you have specific UX requirements that Checkout can't solve."
      },
      {
        "title": "Billing Model Selection",
        "body": "ModelStripe ProductUse WhenOne-timePayment Links / CheckoutSingle purchases, lifetime dealsRecurring flatSubscriptionsFixed monthly/annual SaaSUsage-basedMetered billingAPI calls, compute, storagePer-seatSubscriptions + quantityTeam/user-based pricingTieredTiered pricingVolume discountsHybridSubscription + usage recordsBase fee + overage"
      },
      {
        "title": "Project Structure",
        "body": "src/\n  payments/\n    stripe.config.ts        # Stripe client initialization\n    webhooks.handler.ts     # Webhook endpoint + event routing\n    checkout.service.ts     # Checkout session creation\n    subscription.service.ts # Subscription lifecycle\n    customer.service.ts     # Customer CRUD + portal\n    invoice.service.ts      # Invoice customization\n    tax.service.ts          # Tax calculation\n    types.ts                # Shared types\n  middleware/\n    webhook-verify.ts       # Signature verification middleware"
      },
      {
        "title": "7 Architecture Rules",
        "body": "Never trust the client — verify payment status server-side via webhooks, never from redirect URLs\nWebhooks are the source of truth — your database updates from webhook events, not API call responses\nIdempotency everywhere — every mutating API call gets an idempotency key\nOne Stripe customer per user — create customer at signup, store stripe_customer_id in your DB\nMetadata is your friend — attach user_id, plan, source to every object for debugging\nTest mode mirrors live — your test environment should use the exact same code paths\nNever store card numbers — use Stripe.js/Elements, never handle raw card data"
      },
      {
        "title": "Stripe Client Setup",
        "body": "// stripe.config.ts\nimport Stripe from 'stripe';\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {\n  apiVersion: '2024-12-18.acacia',  // Pin API version!\n  maxNetworkRetries: 2,\n  timeout: 10_000,\n  telemetry: false,\n});\n\nexport { stripe };\n\nRules:\n\nPin API version — never use rolling latest\nSet explicit timeout (10s default is fine)\nDisable telemetry in production if privacy-sensitive\nUse restricted keys with minimum required permissions"
      },
      {
        "title": "Customer Lifecycle",
        "body": "// customer.service.ts\nasync function getOrCreateCustomer(userId: string, email: string, name?: string) {\n  // Check DB first\n  const existing = await db.users.findOne({ id: userId });\n  if (existing?.stripeCustomerId) {\n    return existing.stripeCustomerId;\n  }\n\n  // Create in Stripe\n  const customer = await stripe.customers.create({\n    email,\n    name,\n    metadata: {\n      user_id: userId,\n      created_via: 'signup',\n    },\n  });\n\n  // Store mapping\n  await db.users.update({ id: userId }, { stripeCustomerId: customer.id });\n  return customer.id;\n}\n\nCustomer rules:\n\nCreate at signup, not at first purchase\nAlways set metadata.user_id for reverse lookups\nStore stripe_customer_id in your users table\nUse customer email updates to sync — listen for customer.updated"
      },
      {
        "title": "Checkout Session (Recommended Starting Point)",
        "body": "// checkout.service.ts\nasync function createCheckoutSession(params: {\n  customerId: string;\n  priceId: string;\n  mode: 'payment' | 'subscription';\n  successUrl: string;\n  cancelUrl: string;\n  metadata?: Record<string, string>;\n}) {\n  const session = await stripe.checkout.sessions.create({\n    customer: params.customerId,\n    mode: params.mode,\n    line_items: [{ price: params.priceId, quantity: 1 }],\n    success_url: `${params.successUrl}?session_id={CHECKOUT_SESSION_ID}`,\n    cancel_url: params.cancelUrl,\n    metadata: params.metadata ?? {},\n\n    // Recommended defaults\n    allow_promotion_codes: true,\n    billing_address_collection: 'auto',\n    tax_id_collection: { enabled: true },\n    customer_update: { address: 'auto', name: 'auto' },\n    payment_method_types: ['card'],\n    \n    // For subscriptions\n    ...(params.mode === 'subscription' && {\n      subscription_data: {\n        metadata: params.metadata ?? {},\n        trial_period_days: 14,\n      },\n    }),\n  }, {\n    idempotencyKey: `checkout_${params.customerId}_${params.priceId}_${Date.now()}`,\n  });\n\n  return session;\n}"
      },
      {
        "title": "Payment Element (Custom UI)",
        "body": "// Server: create PaymentIntent\nasync function createPaymentIntent(params: {\n  customerId: string;\n  amount: number;      // in cents!\n  currency: string;\n  metadata?: Record<string, string>;\n}) {\n  return stripe.paymentIntents.create({\n    customer: params.customerId,\n    amount: params.amount,\n    currency: params.currency,\n    automatic_payment_methods: { enabled: true },\n    metadata: {\n      ...params.metadata,\n      created_at: new Date().toISOString(),\n    },\n  }, {\n    idempotencyKey: `pi_${params.customerId}_${params.amount}_${Date.now()}`,\n  });\n}\n\n// Client: React Payment Element\n// <PaymentElement /> handles all payment method rendering\n// Use confirmPayment() on form submit"
      },
      {
        "title": "Subscription Lifecycle Events",
        "body": "Created → Active → Past Due → Canceled → (optionally) Unpaid\n                ↓\n            Trialing → Active\n                ↓\n            Paused → Resumed → Active"
      },
      {
        "title": "Critical Webhook Events for Subscriptions",
        "body": "EventActioncustomer.subscription.createdProvision access, set plan in DBcustomer.subscription.updatedHandle plan changes, quantity updatescustomer.subscription.deletedRevoke access, clean upcustomer.subscription.trial_will_endSend conversion email (3 days before)invoice.payment_succeededConfirm access renewalinvoice.payment_failedStart dunning sequencecustomer.subscription.pausedRestrict access, retain datacustomer.subscription.resumedRestore access"
      },
      {
        "title": "Plan Change Patterns",
        "body": "// Upgrade (immediate)\nasync function upgradePlan(subscriptionId: string, newPriceId: string) {\n  const sub = await stripe.subscriptions.retrieve(subscriptionId);\n  return stripe.subscriptions.update(subscriptionId, {\n    items: [{\n      id: sub.items.data[0].id,\n      price: newPriceId,\n    }],\n    proration_behavior: 'create_prorations',  // charge difference immediately\n    payment_behavior: 'error_if_incomplete',\n  });\n}\n\n// Downgrade (at period end)\nasync function downgradePlan(subscriptionId: string, newPriceId: string) {\n  const sub = await stripe.subscriptions.retrieve(subscriptionId);\n  return stripe.subscriptions.update(subscriptionId, {\n    items: [{\n      id: sub.items.data[0].id,\n      price: newPriceId,\n    }],\n    proration_behavior: 'none',               // no refund, change at renewal\n    billing_cycle_anchor: 'unchanged',\n  });\n}\n\n// Cancel (at period end — always prefer this)\nasync function cancelSubscription(subscriptionId: string) {\n  return stripe.subscriptions.update(subscriptionId, {\n    cancel_at_period_end: true,\n  });\n  // User keeps access until period ends\n  // Handle `customer.subscription.deleted` to revoke\n}"
      },
      {
        "title": "Dunning (Failed Payment Recovery)",
        "body": "# Stripe Dashboard → Settings → Subscriptions → Smart Retries\nretry_schedule:\n  attempt_1: 1 day after failure    # Smart timing\n  attempt_2: 3 days after failure\n  attempt_3: 5 days after failure\n  attempt_4: 7 days after failure   # Final attempt\n\n# Custom dunning emails (supplement Stripe's built-in)\ndunning_sequence:\n  - day: 0\n    action: \"Email: payment failed, update card link\"\n    template: \"Your payment of {amount} failed. Update → {portal_url}\"\n  - day: 3\n    action: \"Email: second notice, urgency\"\n    template: \"Still unable to charge. Update payment to avoid interruption.\"\n  - day: 5\n    action: \"Email: final warning\"\n    template: \"Last chance to update. Access pauses in 48 hours.\"\n  - day: 7\n    action: \"Pause or cancel subscription\"\n    note: \"Automatic via Stripe if all retries fail\""
      },
      {
        "title": "Usage-Based Billing",
        "body": "// Report usage for metered billing\nasync function reportUsage(subscriptionItemId: string, quantity: number) {\n  return stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {\n    quantity,\n    timestamp: Math.floor(Date.now() / 1000),\n    action: 'increment',  // or 'set' for absolute value\n  }, {\n    idempotencyKey: `usage_${subscriptionItemId}_${Date.now()}`,\n  });\n}\n\n// Best practice: batch usage reports\n// Don't report every API call individually — aggregate per hour/day\n// Report at least daily to avoid surprise bills at period end"
      },
      {
        "title": "Webhook Handler Template",
        "body": "// webhooks.handler.ts\nimport { stripe } from './stripe.config';\nimport type { Request, Response } from 'express';\n\nconst WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;\n\n// Event handlers map\nconst handlers: Record<string, (event: Stripe.Event) => Promise<void>> = {\n  'checkout.session.completed': handleCheckoutCompleted,\n  'customer.subscription.created': handleSubscriptionCreated,\n  'customer.subscription.updated': handleSubscriptionUpdated,\n  'customer.subscription.deleted': handleSubscriptionDeleted,\n  'invoice.payment_succeeded': handleInvoicePaymentSucceeded,\n  'invoice.payment_failed': handleInvoicePaymentFailed,\n  'customer.subscription.trial_will_end': handleTrialEnding,\n  'payment_intent.succeeded': handlePaymentSucceeded,\n  'payment_intent.payment_failed': handlePaymentFailed,\n};\n\nexport async function handleWebhook(req: Request, res: Response) {\n  // 1. Verify signature (NEVER skip this)\n  let event: Stripe.Event;\n  try {\n    event = stripe.webhooks.constructEvent(\n      req.body,        // raw body, not parsed JSON!\n      req.headers['stripe-signature']!,\n      WEBHOOK_SECRET,\n    );\n  } catch (err) {\n    console.error('Webhook signature verification failed:', err);\n    return res.status(400).send('Invalid signature');\n  }\n\n  // 2. Idempotency check\n  const alreadyProcessed = await db.webhookEvents.findOne({ eventId: event.id });\n  if (alreadyProcessed) {\n    return res.status(200).json({ received: true, duplicate: true });\n  }\n\n  // 3. Route to handler\n  const handler = handlers[event.type];\n  if (handler) {\n    try {\n      await handler(event);\n      await db.webhookEvents.insert({ eventId: event.id, type: event.type, processedAt: new Date() });\n    } catch (err) {\n      console.error(`Webhook handler failed for ${event.type}:`, err);\n      return res.status(500).send('Handler error');  // Stripe will retry\n    }\n  }\n\n  // 4. Always return 200 quickly\n  res.status(200).json({ received: true });\n}"
      },
      {
        "title": "10 Webhook Rules",
        "body": "Verify every signature — never process unverified events\nUse raw body — don't parse JSON before verification (Express: express.raw({type: 'application/json'}))\nIdempotency — store processed event IDs, handle duplicates gracefully\nReturn 200 fast — do heavy processing async/in background\nHandle out-of-order — events may arrive in unexpected order; check current state before applying\nDon't rely on event data alone — for critical actions, re-fetch the object from the API\nLog everything — event ID, type, relevant object IDs, processing result\nMonitor failures — alert on repeated 500s or unhandled event types\nUse CLI for local dev — stripe listen --forward-to localhost:3000/webhooks\nRegister only events you handle — don't subscribe to everything"
      },
      {
        "title": "Essential Events by Use Case",
        "body": "Use CaseMust-Have EventsOne-time paymentscheckout.session.completed, payment_intent.succeeded, payment_intent.payment_failedSubscriptionsAll subscription.* + invoice.payment_succeeded, invoice.payment_failed, invoice.upcomingMarketplace/Connectaccount.updated, payout.paid, payout.failed, transfer.createdInvoicinginvoice.created, invoice.finalized, invoice.paid, invoice.payment_failed"
      },
      {
        "title": "Phase 5: Customer Portal & Self-Service",
        "body": "// Customer portal — saves you building billing UI\nasync function createPortalSession(customerId: string, returnUrl: string) {\n  return stripe.billingPortal.sessions.create({\n    customer: customerId,\n    return_url: returnUrl,\n  });\n}\n\n// Configure in Dashboard → Settings → Customer portal\n// Enable: Update payment method, Cancel subscription, View invoices\n// Optional: Plan switching, Invoice history download"
      },
      {
        "title": "Portal Configuration Checklist",
        "body": "Payment method update enabled\n Subscription cancellation with reason collection\n Plan switching with proration preview\n Invoice history visible\n Business information (name, logo) set\n Return URL configured\n Terms of service / privacy policy linked"
      },
      {
        "title": "Tax Decision Tree",
        "body": "Do you sell to EU customers?\n  YES → Need VAT collection\n    Use Stripe Tax (automatic) OR manual tax rates\n  NO → \nDo you sell to US customers in multiple states?\n  YES → Need sales tax (nexus rules)\n    Use Stripe Tax (automatic) — manual is nightmare\n  NO →\nDo you exceed $100K revenue or 200 transactions in any US state?\n  YES → You have economic nexus — collect tax\n  NO → May not need to collect, but verify with accountant"
      },
      {
        "title": "Stripe Tax Setup",
        "body": "// Enable automatic tax on Checkout\nconst session = await stripe.checkout.sessions.create({\n  // ... other config\n  automatic_tax: { enabled: true },\n  customer_update: { address: 'auto' },  // Required for tax calculation\n});\n\n// For subscriptions via API\nconst subscription = await stripe.subscriptions.create({\n  customer: customerId,\n  items: [{ price: priceId }],\n  automatic_tax: { enabled: true },\n});\n\nTax rules:\n\nStripe Tax handles calculation + collection automatically\nYou still need to file/remit taxes yourself (or use Stripe Tax filing in supported regions)\nAlways collect billing address for accurate tax calculation\nEnable tax ID collection for B2B reverse charge (EU)"
      },
      {
        "title": "Connect Account Types",
        "body": "TypeControlOnboardingBest ForStandardLow (Stripe-hosted dashboard)Stripe-hostedMarketplaces where sellers manage their own StripeExpressMedium (limited dashboard)Stripe-hostedPlatforms managing payouts for contractors/sellersCustomFull (you build everything)You build itEnterprise platforms needing total control\n\nDecision rule: Use Express unless you have a specific reason not to."
      },
      {
        "title": "Payment Flow Patterns",
        "body": "// Direct charge (platform takes cut)\nconst paymentIntent = await stripe.paymentIntents.create({\n  amount: 10000,  // $100\n  currency: 'usd',\n  application_fee_amount: 1500,  // $15 platform fee\n  transfer_data: {\n    destination: 'acct_seller123',\n  },\n});\n\n// Destination charge (seller's Stripe processes)\n// Same as above but payment appears on seller's statement\n\n// Separate charges and transfers (most flexible)\nconst charge = await stripe.paymentIntents.create({\n  amount: 10000,\n  currency: 'usd',\n});\n// Then transfer to seller\nconst transfer = await stripe.transfers.create({\n  amount: 8500,\n  currency: 'usd',\n  destination: 'acct_seller123',\n  transfer_group: 'order_123',\n});"
      },
      {
        "title": "Test Mode Checklist",
        "body": "TestHowPass CriteriaSuccessful paymentCard: 4242424242424242Checkout completes, webhook fires, DB updatedDeclined cardCard: 4000000000000002Error shown, no DB change3D Secure requiredCard: 4000002500003155Auth modal shown, completes afterInsufficient fundsCard: 4000000000009995Graceful failure messageSubscription createUse test price, complete checkoutSub active, access grantedPayment failure + retryAttach 4000000000000341, trigger invoiceDunning sequence firesWebhook replaystripe events resend evt_xxxIdempotent — no duplicate processingRefundRefund via API or DashboardCustomer notified, access handledUpgrade/downgradeChange plan mid-cycleProration correctCancel at period endCancel, verify access until period endAccess maintained, then revoked"
      },
      {
        "title": "Stripe CLI for Local Development",
        "body": "# Install\nbrew install stripe/stripe-cli/stripe\n\n# Login\nstripe login\n\n# Forward webhooks to local server\nstripe listen --forward-to localhost:3000/api/webhooks/stripe\n\n# Trigger specific events for testing\nstripe trigger checkout.session.completed\nstripe trigger invoice.payment_failed\nstripe trigger customer.subscription.deleted"
      },
      {
        "title": "PCI Compliance Levels",
        "body": "LevelCriteriaRequirementSAQ-ACheckout or Elements (recommended)Annual questionnaire, no scanSAQ-A-EPClient-side tokenizationAnnual questionnaire + quarterly scanSAQ-DDirect API card handling (avoid)Full audit, quarterly scans, penetration tests"
      },
      {
        "title": "Security Checklist (P0 — Mandatory)",
        "body": "API keys in environment variables, never in code\n Webhook signatures verified on every request\n Restricted API keys with minimum permissions\n HTTPS everywhere (Stripe requires it)\n No card numbers logged, stored, or transmitted\n CSRF protection on payment endpoints\n Rate limiting on checkout creation\n Idempotency keys on all mutating calls"
      },
      {
        "title": "API Key Strategy",
        "body": "Live mode:\n  sk_live_xxx    → Server only, env var, restricted permissions\n  pk_live_xxx    → Client-side (public, safe to expose)\n  whsec_xxx      → Webhook secret, server only\n\nTest mode:\n  sk_test_xxx    → Same restrictions as live\n  pk_test_xxx    → Client-side\n  whsec_test_xxx → Webhook secret (different from live!)\n\nRestricted key permissions (create in Dashboard → API Keys):\n\nCheckout Sessions: Write\nCustomers: Write\nSubscriptions: Read/Write\nWebhook Endpoints: Read\nEverything else: None"
      },
      {
        "title": "Pre-Launch (P0 — Mandatory)",
        "body": "Webhook endpoint registered and verified in live mode\n All webhook events subscribed (same as test)\n Live API keys in production environment\n Customer portal configured with live branding\n Test a real $1 payment end-to-end (then refund)\n Error handling for all payment states (success, failure, pending, requires_action)\n Logging: payment ID, customer ID, amount, status on every transaction\n PCI SAQ-A completed in Stripe Dashboard"
      },
      {
        "title": "Pre-Launch (P1 — Within First Week)",
        "body": "Monitoring: alert on payment failure rate > 5%\n Monitoring: alert on webhook delivery failures\n Receipt emails configured (Stripe auto-sends or custom)\n Refund process documented\n Dispute/chargeback response process\n Dunning emails active for subscriptions\n Stripe Tax enabled (if applicable)"
      },
      {
        "title": "Pre-Launch (P2 — Within First Month)",
        "body": "Revenue analytics dashboard\n MRR/churn tracking\n Coupon/promotion code strategy\n Annual vs monthly pricing toggle\n Customer portal self-service verified"
      },
      {
        "title": "Key Metrics Dashboard",
        "body": "metrics:\n  payment_success_rate:\n    calculation: \"successful_payments / total_attempts × 100\"\n    healthy: \">= 95%\"\n    warning: \"90-95%\"\n    critical: \"< 90%\"\n    \n  webhook_delivery_rate:\n    calculation: \"successful_deliveries / total_events × 100\"  \n    healthy: \">= 99.5%\"\n    critical: \"< 99%\"\n    \n  average_revenue_per_user:\n    calculation: \"total_revenue / active_customers\"\n    track: \"weekly trend\"\n    \n  monthly_recurring_revenue:\n    calculation: \"sum(active_subscription_amounts)\"\n    track: \"monthly growth rate\"\n    \n  churn_rate:\n    calculation: \"canceled_subscriptions / total_active × 100\"\n    healthy: \"< 5% monthly\"\n    warning: \"5-10%\"\n    critical: \"> 10%\"\n    \n  involuntary_churn:\n    calculation: \"failed_payment_cancellations / total_churn × 100\"\n    note: \"Should be < 30% of total churn — fix with better dunning\""
      },
      {
        "title": "Weekly Review Checklist",
        "body": "Payment success rate vs last week\n Failed payments — any patterns? (card type, region, amount)\n Webhook failures — any endpoints timing out?\n New disputes/chargebacks — respond within 7 days\n Subscription metrics: new, churned, upgraded, downgraded\n Revenue: MRR, net new MRR, expansion MRR, contraction MRR"
      },
      {
        "title": "Pricing Page with Annual Toggle",
        "body": "// Create both monthly and annual prices for each plan\nconst prices = {\n  starter: {\n    monthly: 'price_starter_monthly',   // $29/mo\n    annual: 'price_starter_annual',     // $290/yr (save ~17%)\n  },\n  pro: {\n    monthly: 'price_pro_monthly',       // $79/mo\n    annual: 'price_pro_annual',         // $790/yr\n  },\n};\n\n// Client toggles monthly/annual → creates checkout with correct priceId"
      },
      {
        "title": "Grandfathering & Price Increases",
        "body": "// New price, existing customers keep old price\n// 1. Create new price object (don't modify existing)\n// 2. New signups use new price\n// 3. Existing subs stay on old price until they change plans\n// 4. Optional: scheduled price migration with notice\n\nasync function schedulePriceIncrease(subscriptionId: string, newPriceId: string, effectiveDate: Date) {\n  // Create a subscription schedule\n  const schedule = await stripe.subscriptionSchedules.create({\n    from_subscription: subscriptionId,\n  });\n  \n  // Add phase with new price\n  await stripe.subscriptionSchedules.update(schedule.id, {\n    phases: [\n      { items: [{ price: currentPriceId }], end_date: Math.floor(effectiveDate.getTime() / 1000) },\n      { items: [{ price: newPriceId }] },\n    ],\n  });\n}"
      },
      {
        "title": "Coupon & Promotion Codes",
        "body": "// Create coupon (reusable)\nconst coupon = await stripe.coupons.create({\n  percent_off: 20,\n  duration: 'repeating',\n  duration_in_months: 3,\n  max_redemptions: 100,\n  metadata: { campaign: 'launch_2024' },\n});\n\n// Create promotion code (shareable link)\nconst promoCode = await stripe.promotionCodes.create({\n  coupon: coupon.id,\n  code: 'LAUNCH20',\n  max_redemptions: 50,\n  expires_at: Math.floor(new Date('2024-12-31').getTime() / 1000),\n});"
      },
      {
        "title": "Handling Disputes (Chargebacks)",
        "body": "dispute_response_process:\n  when_received:\n    - Log dispute details: amount, reason, deadline\n    - Alert team immediately\n    - Begin evidence collection within 24 hours\n    \n  evidence_to_collect:\n    - Customer communication (emails, chat logs)\n    - Delivery proof (access logs, download records)\n    - Terms of service acceptance timestamp\n    - Refund policy shown at checkout\n    - IP address and device fingerprint\n    - Prior successful transactions from same customer\n    \n  submit:\n    - Within 7 days (Stripe deadline is usually 21 days, but act fast)\n    - Use Stripe Dashboard evidence submission form\n    - Include written rebuttal addressing specific reason code\n    \n  prevention:\n    - Clear billing descriptor (customer recognizes charge)\n    - Send receipt emails immediately\n    - Offer easy refunds before disputes escalate\n    - Use Radar for fraud detection"
      },
      {
        "title": "10 Common Mistakes",
        "body": "#MistakeFix1Trusting client-side redirect as payment confirmationAlways verify via webhook2Parsing JSON body before webhook signature verificationUse raw body buffer3No idempotency keys on API callsAdd to every mutating call4Immediately canceling instead of cancel_at_period_endAlways cancel at period end unless refunding5Amounts in dollars instead of centsStripe uses smallest currency unit (cents)6Not handling 3D Secure / requires_action statusCheck payment_intent.status, handle authentication7Same webhook secret for test and liveUse separate secrets per environment8Not testing with Stripe CLI locallySet up stripe listen in development9Hardcoding price IDsUse config/env vars, different per environment10No dunning strategy for failed subscription paymentsConfigure Smart Retries + custom emails"
      },
      {
        "title": "Quality Rubric (0-100)",
        "body": "DimensionWeightCriteriaWebhook reliability25%Signature verification, idempotency, error handling, event coverageSecurity & PCI20%SAQ-A compliance, key management, no card data exposureSubscription lifecycle15%Create/upgrade/downgrade/cancel/pause all handled correctlyCustomer experience15%Portal, receipts, dunning emails, clear error messagesTesting coverage10%All payment states tested, CLI setup, edge casesMonitoring10%Failure alerts, revenue metrics, dispute trackingCode quality5%TypeScript types, error handling, idempotency keys, logging"
      },
      {
        "title": "Commands",
        "body": "The agent responds to these natural language requests:\n\n\"Set up Stripe checkout\" → Full Checkout integration with webhook handler\n\"Add subscriptions\" → Subscription lifecycle with plan changes and dunning\n\"Configure webhooks\" → Webhook handler with signature verification and idempotency\n\"Set up customer portal\" → Billing portal configuration\n\"Add usage-based billing\" → Metered subscription with usage reporting\n\"Stripe security audit\" → PCI compliance check + security hardening\n\"Go-live checklist\" → Pre-launch verification for production\n\"Set up Stripe Connect\" → Marketplace/platform payment flows\n\"Add coupons and promos\" → Promotion code system\n\"Review payment metrics\" → Revenue and payment health dashboard\n\"Handle a dispute\" → Chargeback response process\n\"Migrate pricing\" → Grandfathering and price increase strategy"
      }
    ],
    "body": "Stripe Production Engineering\n\nComplete methodology for building, scaling, and operating production Stripe payment systems. From first checkout to enterprise billing at scale.\n\nQuick Health Check\n\nRun through these 8 signals. Score 1 point each. Below 5 = stop and fix.\n\n✅ Webhook endpoint verified and idempotent\n✅ All API calls use idempotency keys\n✅ Customer portal enabled for self-service\n✅ Stripe Tax or manual tax collection configured\n✅ Failed payment retry logic with dunning emails\n✅ PCI compliance questionnaire completed (SAQ-A minimum)\n✅ Test mode → live mode checklist completed\n✅ Monitoring/alerting on payment failures and webhook errors\n\nScore: /8 — Below 5? Fix gaps before adding features.\n\nPhase 1: Architecture Strategy\nIntegration Pattern Decision\nPattern\tBest For\tComplexity\tPCI Scope\nStripe Checkout (hosted)\tMVPs, quick launch\tLow\tSAQ-A (minimal)\nPayment Element (embedded)\tCustom UX, brand control\tMedium\tSAQ-A\nCard Element (legacy)\tExisting integrations\tMedium\tSAQ-A-EP\nDirect API\tPlatform/marketplace\tHigh\tSAQ-D (avoid)\n\nDecision rule: Start with Checkout. Move to Payment Element only when you have specific UX requirements that Checkout can't solve.\n\nBilling Model Selection\nModel\tStripe Product\tUse When\nOne-time\tPayment Links / Checkout\tSingle purchases, lifetime deals\nRecurring flat\tSubscriptions\tFixed monthly/annual SaaS\nUsage-based\tMetered billing\tAPI calls, compute, storage\nPer-seat\tSubscriptions + quantity\tTeam/user-based pricing\nTiered\tTiered pricing\tVolume discounts\nHybrid\tSubscription + usage records\tBase fee + overage\nProject Structure\nsrc/\n  payments/\n    stripe.config.ts        # Stripe client initialization\n    webhooks.handler.ts     # Webhook endpoint + event routing\n    checkout.service.ts     # Checkout session creation\n    subscription.service.ts # Subscription lifecycle\n    customer.service.ts     # Customer CRUD + portal\n    invoice.service.ts      # Invoice customization\n    tax.service.ts          # Tax calculation\n    types.ts                # Shared types\n  middleware/\n    webhook-verify.ts       # Signature verification middleware\n\n7 Architecture Rules\nNever trust the client — verify payment status server-side via webhooks, never from redirect URLs\nWebhooks are the source of truth — your database updates from webhook events, not API call responses\nIdempotency everywhere — every mutating API call gets an idempotency key\nOne Stripe customer per user — create customer at signup, store stripe_customer_id in your DB\nMetadata is your friend — attach user_id, plan, source to every object for debugging\nTest mode mirrors live — your test environment should use the exact same code paths\nNever store card numbers — use Stripe.js/Elements, never handle raw card data\nPhase 2: Core Integration Patterns\nStripe Client Setup\n// stripe.config.ts\nimport Stripe from 'stripe';\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {\n  apiVersion: '2024-12-18.acacia',  // Pin API version!\n  maxNetworkRetries: 2,\n  timeout: 10_000,\n  telemetry: false,\n});\n\nexport { stripe };\n\n\nRules:\n\nPin API version — never use rolling latest\nSet explicit timeout (10s default is fine)\nDisable telemetry in production if privacy-sensitive\nUse restricted keys with minimum required permissions\nCustomer Lifecycle\n// customer.service.ts\nasync function getOrCreateCustomer(userId: string, email: string, name?: string) {\n  // Check DB first\n  const existing = await db.users.findOne({ id: userId });\n  if (existing?.stripeCustomerId) {\n    return existing.stripeCustomerId;\n  }\n\n  // Create in Stripe\n  const customer = await stripe.customers.create({\n    email,\n    name,\n    metadata: {\n      user_id: userId,\n      created_via: 'signup',\n    },\n  });\n\n  // Store mapping\n  await db.users.update({ id: userId }, { stripeCustomerId: customer.id });\n  return customer.id;\n}\n\n\nCustomer rules:\n\nCreate at signup, not at first purchase\nAlways set metadata.user_id for reverse lookups\nStore stripe_customer_id in your users table\nUse customer email updates to sync — listen for customer.updated\nCheckout Session (Recommended Starting Point)\n// checkout.service.ts\nasync function createCheckoutSession(params: {\n  customerId: string;\n  priceId: string;\n  mode: 'payment' | 'subscription';\n  successUrl: string;\n  cancelUrl: string;\n  metadata?: Record<string, string>;\n}) {\n  const session = await stripe.checkout.sessions.create({\n    customer: params.customerId,\n    mode: params.mode,\n    line_items: [{ price: params.priceId, quantity: 1 }],\n    success_url: `${params.successUrl}?session_id={CHECKOUT_SESSION_ID}`,\n    cancel_url: params.cancelUrl,\n    metadata: params.metadata ?? {},\n\n    // Recommended defaults\n    allow_promotion_codes: true,\n    billing_address_collection: 'auto',\n    tax_id_collection: { enabled: true },\n    customer_update: { address: 'auto', name: 'auto' },\n    payment_method_types: ['card'],\n    \n    // For subscriptions\n    ...(params.mode === 'subscription' && {\n      subscription_data: {\n        metadata: params.metadata ?? {},\n        trial_period_days: 14,\n      },\n    }),\n  }, {\n    idempotencyKey: `checkout_${params.customerId}_${params.priceId}_${Date.now()}`,\n  });\n\n  return session;\n}\n\nPayment Element (Custom UI)\n// Server: create PaymentIntent\nasync function createPaymentIntent(params: {\n  customerId: string;\n  amount: number;      // in cents!\n  currency: string;\n  metadata?: Record<string, string>;\n}) {\n  return stripe.paymentIntents.create({\n    customer: params.customerId,\n    amount: params.amount,\n    currency: params.currency,\n    automatic_payment_methods: { enabled: true },\n    metadata: {\n      ...params.metadata,\n      created_at: new Date().toISOString(),\n    },\n  }, {\n    idempotencyKey: `pi_${params.customerId}_${params.amount}_${Date.now()}`,\n  });\n}\n\n// Client: React Payment Element\n// <PaymentElement /> handles all payment method rendering\n// Use confirmPayment() on form submit\n\nPhase 3: Subscription Management\nSubscription Lifecycle Events\nCreated → Active → Past Due → Canceled → (optionally) Unpaid\n                ↓\n            Trialing → Active\n                ↓\n            Paused → Resumed → Active\n\nCritical Webhook Events for Subscriptions\nEvent\tAction\ncustomer.subscription.created\tProvision access, set plan in DB\ncustomer.subscription.updated\tHandle plan changes, quantity updates\ncustomer.subscription.deleted\tRevoke access, clean up\ncustomer.subscription.trial_will_end\tSend conversion email (3 days before)\ninvoice.payment_succeeded\tConfirm access renewal\ninvoice.payment_failed\tStart dunning sequence\ncustomer.subscription.paused\tRestrict access, retain data\ncustomer.subscription.resumed\tRestore access\nPlan Change Patterns\n// Upgrade (immediate)\nasync function upgradePlan(subscriptionId: string, newPriceId: string) {\n  const sub = await stripe.subscriptions.retrieve(subscriptionId);\n  return stripe.subscriptions.update(subscriptionId, {\n    items: [{\n      id: sub.items.data[0].id,\n      price: newPriceId,\n    }],\n    proration_behavior: 'create_prorations',  // charge difference immediately\n    payment_behavior: 'error_if_incomplete',\n  });\n}\n\n// Downgrade (at period end)\nasync function downgradePlan(subscriptionId: string, newPriceId: string) {\n  const sub = await stripe.subscriptions.retrieve(subscriptionId);\n  return stripe.subscriptions.update(subscriptionId, {\n    items: [{\n      id: sub.items.data[0].id,\n      price: newPriceId,\n    }],\n    proration_behavior: 'none',               // no refund, change at renewal\n    billing_cycle_anchor: 'unchanged',\n  });\n}\n\n// Cancel (at period end — always prefer this)\nasync function cancelSubscription(subscriptionId: string) {\n  return stripe.subscriptions.update(subscriptionId, {\n    cancel_at_period_end: true,\n  });\n  // User keeps access until period ends\n  // Handle `customer.subscription.deleted` to revoke\n}\n\nDunning (Failed Payment Recovery)\n# Stripe Dashboard → Settings → Subscriptions → Smart Retries\nretry_schedule:\n  attempt_1: 1 day after failure    # Smart timing\n  attempt_2: 3 days after failure\n  attempt_3: 5 days after failure\n  attempt_4: 7 days after failure   # Final attempt\n\n# Custom dunning emails (supplement Stripe's built-in)\ndunning_sequence:\n  - day: 0\n    action: \"Email: payment failed, update card link\"\n    template: \"Your payment of {amount} failed. Update → {portal_url}\"\n  - day: 3\n    action: \"Email: second notice, urgency\"\n    template: \"Still unable to charge. Update payment to avoid interruption.\"\n  - day: 5\n    action: \"Email: final warning\"\n    template: \"Last chance to update. Access pauses in 48 hours.\"\n  - day: 7\n    action: \"Pause or cancel subscription\"\n    note: \"Automatic via Stripe if all retries fail\"\n\nUsage-Based Billing\n// Report usage for metered billing\nasync function reportUsage(subscriptionItemId: string, quantity: number) {\n  return stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {\n    quantity,\n    timestamp: Math.floor(Date.now() / 1000),\n    action: 'increment',  // or 'set' for absolute value\n  }, {\n    idempotencyKey: `usage_${subscriptionItemId}_${Date.now()}`,\n  });\n}\n\n// Best practice: batch usage reports\n// Don't report every API call individually — aggregate per hour/day\n// Report at least daily to avoid surprise bills at period end\n\nPhase 4: Webhooks (The Most Critical Part)\nWebhook Handler Template\n// webhooks.handler.ts\nimport { stripe } from './stripe.config';\nimport type { Request, Response } from 'express';\n\nconst WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;\n\n// Event handlers map\nconst handlers: Record<string, (event: Stripe.Event) => Promise<void>> = {\n  'checkout.session.completed': handleCheckoutCompleted,\n  'customer.subscription.created': handleSubscriptionCreated,\n  'customer.subscription.updated': handleSubscriptionUpdated,\n  'customer.subscription.deleted': handleSubscriptionDeleted,\n  'invoice.payment_succeeded': handleInvoicePaymentSucceeded,\n  'invoice.payment_failed': handleInvoicePaymentFailed,\n  'customer.subscription.trial_will_end': handleTrialEnding,\n  'payment_intent.succeeded': handlePaymentSucceeded,\n  'payment_intent.payment_failed': handlePaymentFailed,\n};\n\nexport async function handleWebhook(req: Request, res: Response) {\n  // 1. Verify signature (NEVER skip this)\n  let event: Stripe.Event;\n  try {\n    event = stripe.webhooks.constructEvent(\n      req.body,        // raw body, not parsed JSON!\n      req.headers['stripe-signature']!,\n      WEBHOOK_SECRET,\n    );\n  } catch (err) {\n    console.error('Webhook signature verification failed:', err);\n    return res.status(400).send('Invalid signature');\n  }\n\n  // 2. Idempotency check\n  const alreadyProcessed = await db.webhookEvents.findOne({ eventId: event.id });\n  if (alreadyProcessed) {\n    return res.status(200).json({ received: true, duplicate: true });\n  }\n\n  // 3. Route to handler\n  const handler = handlers[event.type];\n  if (handler) {\n    try {\n      await handler(event);\n      await db.webhookEvents.insert({ eventId: event.id, type: event.type, processedAt: new Date() });\n    } catch (err) {\n      console.error(`Webhook handler failed for ${event.type}:`, err);\n      return res.status(500).send('Handler error');  // Stripe will retry\n    }\n  }\n\n  // 4. Always return 200 quickly\n  res.status(200).json({ received: true });\n}\n\n10 Webhook Rules\nVerify every signature — never process unverified events\nUse raw body — don't parse JSON before verification (Express: express.raw({type: 'application/json'}))\nIdempotency — store processed event IDs, handle duplicates gracefully\nReturn 200 fast — do heavy processing async/in background\nHandle out-of-order — events may arrive in unexpected order; check current state before applying\nDon't rely on event data alone — for critical actions, re-fetch the object from the API\nLog everything — event ID, type, relevant object IDs, processing result\nMonitor failures — alert on repeated 500s or unhandled event types\nUse CLI for local dev — stripe listen --forward-to localhost:3000/webhooks\nRegister only events you handle — don't subscribe to everything\nEssential Events by Use Case\nUse Case\tMust-Have Events\nOne-time payments\tcheckout.session.completed, payment_intent.succeeded, payment_intent.payment_failed\nSubscriptions\tAll subscription.* + invoice.payment_succeeded, invoice.payment_failed, invoice.upcoming\nMarketplace/Connect\taccount.updated, payout.paid, payout.failed, transfer.created\nInvoicing\tinvoice.created, invoice.finalized, invoice.paid, invoice.payment_failed\nPhase 5: Customer Portal & Self-Service\n// Customer portal — saves you building billing UI\nasync function createPortalSession(customerId: string, returnUrl: string) {\n  return stripe.billingPortal.sessions.create({\n    customer: customerId,\n    return_url: returnUrl,\n  });\n}\n\n// Configure in Dashboard → Settings → Customer portal\n// Enable: Update payment method, Cancel subscription, View invoices\n// Optional: Plan switching, Invoice history download\n\nPortal Configuration Checklist\n Payment method update enabled\n Subscription cancellation with reason collection\n Plan switching with proration preview\n Invoice history visible\n Business information (name, logo) set\n Return URL configured\n Terms of service / privacy policy linked\nPhase 6: Tax Collection\nTax Decision Tree\nDo you sell to EU customers?\n  YES → Need VAT collection\n    Use Stripe Tax (automatic) OR manual tax rates\n  NO → \nDo you sell to US customers in multiple states?\n  YES → Need sales tax (nexus rules)\n    Use Stripe Tax (automatic) — manual is nightmare\n  NO →\nDo you exceed $100K revenue or 200 transactions in any US state?\n  YES → You have economic nexus — collect tax\n  NO → May not need to collect, but verify with accountant\n\nStripe Tax Setup\n// Enable automatic tax on Checkout\nconst session = await stripe.checkout.sessions.create({\n  // ... other config\n  automatic_tax: { enabled: true },\n  customer_update: { address: 'auto' },  // Required for tax calculation\n});\n\n// For subscriptions via API\nconst subscription = await stripe.subscriptions.create({\n  customer: customerId,\n  items: [{ price: priceId }],\n  automatic_tax: { enabled: true },\n});\n\n\nTax rules:\n\nStripe Tax handles calculation + collection automatically\nYou still need to file/remit taxes yourself (or use Stripe Tax filing in supported regions)\nAlways collect billing address for accurate tax calculation\nEnable tax ID collection for B2B reverse charge (EU)\nPhase 7: Stripe Connect (Marketplaces & Platforms)\nConnect Account Types\nType\tControl\tOnboarding\tBest For\nStandard\tLow (Stripe-hosted dashboard)\tStripe-hosted\tMarketplaces where sellers manage their own Stripe\nExpress\tMedium (limited dashboard)\tStripe-hosted\tPlatforms managing payouts for contractors/sellers\nCustom\tFull (you build everything)\tYou build it\tEnterprise platforms needing total control\n\nDecision rule: Use Express unless you have a specific reason not to.\n\nPayment Flow Patterns\n// Direct charge (platform takes cut)\nconst paymentIntent = await stripe.paymentIntents.create({\n  amount: 10000,  // $100\n  currency: 'usd',\n  application_fee_amount: 1500,  // $15 platform fee\n  transfer_data: {\n    destination: 'acct_seller123',\n  },\n});\n\n// Destination charge (seller's Stripe processes)\n// Same as above but payment appears on seller's statement\n\n// Separate charges and transfers (most flexible)\nconst charge = await stripe.paymentIntents.create({\n  amount: 10000,\n  currency: 'usd',\n});\n// Then transfer to seller\nconst transfer = await stripe.transfers.create({\n  amount: 8500,\n  currency: 'usd',\n  destination: 'acct_seller123',\n  transfer_group: 'order_123',\n});\n\nPhase 8: Testing Strategy\nTest Mode Checklist\nTest\tHow\tPass Criteria\nSuccessful payment\tCard: 4242424242424242\tCheckout completes, webhook fires, DB updated\nDeclined card\tCard: 4000000000000002\tError shown, no DB change\n3D Secure required\tCard: 4000002500003155\tAuth modal shown, completes after\nInsufficient funds\tCard: 4000000000009995\tGraceful failure message\nSubscription create\tUse test price, complete checkout\tSub active, access granted\nPayment failure + retry\tAttach 4000000000000341, trigger invoice\tDunning sequence fires\nWebhook replay\tstripe events resend evt_xxx\tIdempotent — no duplicate processing\nRefund\tRefund via API or Dashboard\tCustomer notified, access handled\nUpgrade/downgrade\tChange plan mid-cycle\tProration correct\nCancel at period end\tCancel, verify access until period end\tAccess maintained, then revoked\nStripe CLI for Local Development\n# Install\nbrew install stripe/stripe-cli/stripe\n\n# Login\nstripe login\n\n# Forward webhooks to local server\nstripe listen --forward-to localhost:3000/api/webhooks/stripe\n\n# Trigger specific events for testing\nstripe trigger checkout.session.completed\nstripe trigger invoice.payment_failed\nstripe trigger customer.subscription.deleted\n\nPhase 9: Security & PCI Compliance\nPCI Compliance Levels\nLevel\tCriteria\tRequirement\nSAQ-A\tCheckout or Elements (recommended)\tAnnual questionnaire, no scan\nSAQ-A-EP\tClient-side tokenization\tAnnual questionnaire + quarterly scan\nSAQ-D\tDirect API card handling (avoid)\tFull audit, quarterly scans, penetration tests\nSecurity Checklist (P0 — Mandatory)\n API keys in environment variables, never in code\n Webhook signatures verified on every request\n Restricted API keys with minimum permissions\n HTTPS everywhere (Stripe requires it)\n No card numbers logged, stored, or transmitted\n CSRF protection on payment endpoints\n Rate limiting on checkout creation\n Idempotency keys on all mutating calls\nAPI Key Strategy\nLive mode:\n  sk_live_xxx    → Server only, env var, restricted permissions\n  pk_live_xxx    → Client-side (public, safe to expose)\n  whsec_xxx      → Webhook secret, server only\n\nTest mode:\n  sk_test_xxx    → Same restrictions as live\n  pk_test_xxx    → Client-side\n  whsec_test_xxx → Webhook secret (different from live!)\n\n\nRestricted key permissions (create in Dashboard → API Keys):\n\nCheckout Sessions: Write\nCustomers: Write\nSubscriptions: Read/Write\nWebhook Endpoints: Read\nEverything else: None\nPhase 10: Go-Live Checklist\nPre-Launch (P0 — Mandatory)\n Webhook endpoint registered and verified in live mode\n All webhook events subscribed (same as test)\n Live API keys in production environment\n Customer portal configured with live branding\n Test a real $1 payment end-to-end (then refund)\n Error handling for all payment states (success, failure, pending, requires_action)\n Logging: payment ID, customer ID, amount, status on every transaction\n PCI SAQ-A completed in Stripe Dashboard\nPre-Launch (P1 — Within First Week)\n Monitoring: alert on payment failure rate > 5%\n Monitoring: alert on webhook delivery failures\n Receipt emails configured (Stripe auto-sends or custom)\n Refund process documented\n Dispute/chargeback response process\n Dunning emails active for subscriptions\n Stripe Tax enabled (if applicable)\nPre-Launch (P2 — Within First Month)\n Revenue analytics dashboard\n MRR/churn tracking\n Coupon/promotion code strategy\n Annual vs monthly pricing toggle\n Customer portal self-service verified\nPhase 11: Monitoring & Operations\nKey Metrics Dashboard\nmetrics:\n  payment_success_rate:\n    calculation: \"successful_payments / total_attempts × 100\"\n    healthy: \">= 95%\"\n    warning: \"90-95%\"\n    critical: \"< 90%\"\n    \n  webhook_delivery_rate:\n    calculation: \"successful_deliveries / total_events × 100\"  \n    healthy: \">= 99.5%\"\n    critical: \"< 99%\"\n    \n  average_revenue_per_user:\n    calculation: \"total_revenue / active_customers\"\n    track: \"weekly trend\"\n    \n  monthly_recurring_revenue:\n    calculation: \"sum(active_subscription_amounts)\"\n    track: \"monthly growth rate\"\n    \n  churn_rate:\n    calculation: \"canceled_subscriptions / total_active × 100\"\n    healthy: \"< 5% monthly\"\n    warning: \"5-10%\"\n    critical: \"> 10%\"\n    \n  involuntary_churn:\n    calculation: \"failed_payment_cancellations / total_churn × 100\"\n    note: \"Should be < 30% of total churn — fix with better dunning\"\n\nWeekly Review Checklist\n Payment success rate vs last week\n Failed payments — any patterns? (card type, region, amount)\n Webhook failures — any endpoints timing out?\n New disputes/chargebacks — respond within 7 days\n Subscription metrics: new, churned, upgraded, downgraded\n Revenue: MRR, net new MRR, expansion MRR, contraction MRR\nPhase 12: Advanced Patterns\nPricing Page with Annual Toggle\n// Create both monthly and annual prices for each plan\nconst prices = {\n  starter: {\n    monthly: 'price_starter_monthly',   // $29/mo\n    annual: 'price_starter_annual',     // $290/yr (save ~17%)\n  },\n  pro: {\n    monthly: 'price_pro_monthly',       // $79/mo\n    annual: 'price_pro_annual',         // $790/yr\n  },\n};\n\n// Client toggles monthly/annual → creates checkout with correct priceId\n\nGrandfathering & Price Increases\n// New price, existing customers keep old price\n// 1. Create new price object (don't modify existing)\n// 2. New signups use new price\n// 3. Existing subs stay on old price until they change plans\n// 4. Optional: scheduled price migration with notice\n\nasync function schedulePriceIncrease(subscriptionId: string, newPriceId: string, effectiveDate: Date) {\n  // Create a subscription schedule\n  const schedule = await stripe.subscriptionSchedules.create({\n    from_subscription: subscriptionId,\n  });\n  \n  // Add phase with new price\n  await stripe.subscriptionSchedules.update(schedule.id, {\n    phases: [\n      { items: [{ price: currentPriceId }], end_date: Math.floor(effectiveDate.getTime() / 1000) },\n      { items: [{ price: newPriceId }] },\n    ],\n  });\n}\n\nCoupon & Promotion Codes\n// Create coupon (reusable)\nconst coupon = await stripe.coupons.create({\n  percent_off: 20,\n  duration: 'repeating',\n  duration_in_months: 3,\n  max_redemptions: 100,\n  metadata: { campaign: 'launch_2024' },\n});\n\n// Create promotion code (shareable link)\nconst promoCode = await stripe.promotionCodes.create({\n  coupon: coupon.id,\n  code: 'LAUNCH20',\n  max_redemptions: 50,\n  expires_at: Math.floor(new Date('2024-12-31').getTime() / 1000),\n});\n\nHandling Disputes (Chargebacks)\ndispute_response_process:\n  when_received:\n    - Log dispute details: amount, reason, deadline\n    - Alert team immediately\n    - Begin evidence collection within 24 hours\n    \n  evidence_to_collect:\n    - Customer communication (emails, chat logs)\n    - Delivery proof (access logs, download records)\n    - Terms of service acceptance timestamp\n    - Refund policy shown at checkout\n    - IP address and device fingerprint\n    - Prior successful transactions from same customer\n    \n  submit:\n    - Within 7 days (Stripe deadline is usually 21 days, but act fast)\n    - Use Stripe Dashboard evidence submission form\n    - Include written rebuttal addressing specific reason code\n    \n  prevention:\n    - Clear billing descriptor (customer recognizes charge)\n    - Send receipt emails immediately\n    - Offer easy refunds before disputes escalate\n    - Use Radar for fraud detection\n\n10 Common Mistakes\n#\tMistake\tFix\n1\tTrusting client-side redirect as payment confirmation\tAlways verify via webhook\n2\tParsing JSON body before webhook signature verification\tUse raw body buffer\n3\tNo idempotency keys on API calls\tAdd to every mutating call\n4\tImmediately canceling instead of cancel_at_period_end\tAlways cancel at period end unless refunding\n5\tAmounts in dollars instead of cents\tStripe uses smallest currency unit (cents)\n6\tNot handling 3D Secure / requires_action status\tCheck payment_intent.status, handle authentication\n7\tSame webhook secret for test and live\tUse separate secrets per environment\n8\tNot testing with Stripe CLI locally\tSet up stripe listen in development\n9\tHardcoding price IDs\tUse config/env vars, different per environment\n10\tNo dunning strategy for failed subscription payments\tConfigure Smart Retries + custom emails\nQuality Rubric (0-100)\nDimension\tWeight\tCriteria\nWebhook reliability\t25%\tSignature verification, idempotency, error handling, event coverage\nSecurity & PCI\t20%\tSAQ-A compliance, key management, no card data exposure\nSubscription lifecycle\t15%\tCreate/upgrade/downgrade/cancel/pause all handled correctly\nCustomer experience\t15%\tPortal, receipts, dunning emails, clear error messages\nTesting coverage\t10%\tAll payment states tested, CLI setup, edge cases\nMonitoring\t10%\tFailure alerts, revenue metrics, dispute tracking\nCode quality\t5%\tTypeScript types, error handling, idempotency keys, logging\nCommands\n\nThe agent responds to these natural language requests:\n\n\"Set up Stripe checkout\" → Full Checkout integration with webhook handler\n\"Add subscriptions\" → Subscription lifecycle with plan changes and dunning\n\"Configure webhooks\" → Webhook handler with signature verification and idempotency\n\"Set up customer portal\" → Billing portal configuration\n\"Add usage-based billing\" → Metered subscription with usage reporting\n\"Stripe security audit\" → PCI compliance check + security hardening\n\"Go-live checklist\" → Pre-launch verification for production\n\"Set up Stripe Connect\" → Marketplace/platform payment flows\n\"Add coupons and promos\" → Promotion code system\n\"Review payment metrics\" → Revenue and payment health dashboard\n\"Handle a dispute\" → Chargeback response process\n\"Migrate pricing\" → Grandfathering and price increase strategy"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/1kalin/afrexai-stripe-production",
    "publisherUrl": "https://clawhub.ai/1kalin/afrexai-stripe-production",
    "owner": "1kalin",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/afrexai-stripe-production",
    "downloadUrl": "https://openagent3.xyz/downloads/afrexai-stripe-production",
    "agentUrl": "https://openagent3.xyz/skills/afrexai-stripe-production/agent",
    "manifestUrl": "https://openagent3.xyz/skills/afrexai-stripe-production/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/afrexai-stripe-production/agent.md"
  }
}