Engineering StripePaymentsBilling

Stripe Integration Expert

Production-grade Stripe integrations: subscriptions, webhooks, and billing.

$skillfish add ~skills/stripe-integration-expert

Real Conversations

See how the Stripe Integration Expert skill responds to actual requests. These are representative examples of what your AI agent will produce.

You

Building a SaaS with a 14-day free trial. After trial, $49/month. Using Stripe. Need to handle trial end, failed payments, and cancellations in Node.js.

Agent

Stripe Subscription with Trial — Full Implementation:

Create subscription with trial:

// After customer completes checkout
const subscription = await stripe.subscriptions.create({
  customer: customerId,
  items: [{ price: 'price_49_monthly' }],
  trial_period_days: 14,
  payment_settings: { save_default_payment_method: 'on_subscription' },
  expand: ['latest_invoice.payment_intent'],
});
// subscription.status === 'trialing'

Webhook handler (the critical part):

router.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['stripe-signature']!;
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch { return res.status(400).send('Webhook signature verification failed'); }

  switch (event.type) {
    case 'customer.subscription.trial_will_end':
      // Fires 3 days before trial ends — send reminder email
      await sendTrialEndingEmail(event.data.object.customer as string);
      break;
    
    case 'invoice.payment_failed':
      // Stripe retries automatically (smart retries) — downgrade after 3 failures
      const invoice = event.data.object as Stripe.Invoice;
      if (invoice.attempt_count >= 3) {
        await downgradeToFree(invoice.customer as string);
      }
      break;

    case 'customer.subscription.deleted':
      // Cancelled or permanently failed — revoke access
      await revokeAccess(event.data.object.customer as string);
      break;
  }

  res.json({ received: true });
});

Enable Stripe Smart Retries in Dashboard → Billing → Settings → Retry schedule — reduces failed payment churn by ~30% automatically.

Get Started

How to install and use this skill in your preferred environment.

Skills are designed for AI coding agents (Claude Code, Cursor, Windsurf) and IDE-based workflows where the agent can read files, run scripts, and act on your codebase. Web-based AI can use the knowledge and frameworks, but won't have tool access.

Models & Context

Which AI models and context windows work best with this skill.

Recommended Models

Best
Claude Opus 4 Claude Sonnet 4 GPT-4.1 Gemini 2.5 Pro Grok 3 Kimi K2
Good
Claude Haiku 4.5 GPT-4.1 mini Gemini 2.5 Flash Grok 3 mini

Larger models produce more detailed, production-ready outputs.

Context Window

This skill's SKILL.md is typically 3–10 KB — fits in any modern context window.

8K Skill only
32K+ Skill + conversation
100K+ Skill + references + codebase

All current frontier models (Claude, GPT, Gemini) support 100K+ context. Use the full window for complex multi-service work.

Pro tips for best results

1

Be specific

Include numbers — users, budget, RPS — so the skill can size the architecture.

2

Share constraints

Compliance needs, team size, and existing stack all improve the output.

3

Iterate

Start with a high-level design, then ask follow-ups for IaC, cost analysis, or security review.

4

Combine skills

Pair with companion skills below for end-to-end coverage.

Good to Know

Advanced guide and reference material for Stripe Integration Expert. Background, edge cases, and patterns worth understanding.

Contents

Idempotency Keys

Every mutating API call to Stripe (creating charges, subscriptions, customers) must include an Idempotency-Key header. Without one, a network timeout followed by a retry can create duplicate charges.

How to generate them: Use a request-scoped UUID tied to the business operation, not a random value on each retry. For example, compose the key from userId + ":" + priceId + ":" + timestamp-of-intent. This way retries within the same intent reuse the same key.

Stripe deduplicates idempotent requests for 24 hours. After that window, the same key is treated as a new request. If your retry logic runs within that window, Stripe returns the original response without executing the operation again.

What happens without one: A timeout followed by a retry creates a second charge. The first charge may succeed server-side even though your client never got the 200 response.

Webhook Reliability

Stripe retries failed webhook deliveries up to 4 times over 72 hours (at roughly 1, 3, 24, and 72 hours after the first failure). Your endpoint must return a 2xx before it times out — Stripe's timeout is 30 seconds.

Critical pattern: Acknowledge immediately, process asynchronously. Return 200 OK as soon as you verify the signature, then push the event to a queue for processing. Never do database writes or API calls inside the synchronous response path.

Handling duplicate delivery: Stripe can deliver the same event more than once. Your handlers must be idempotent. Store processed event IDs and check before acting:

IF event_id already in processed_events → return early
ELSE → process + record event_id

Signature verification: Always verify using stripe.webhooks.constructEvent() with the raw request body (before any JSON parsing). Parsing the body first invalidates the signature.

Billing Edge Cases

Scenario Behavior to Know
Mid-cycle plan upgrade Stripe prorates by default. Customer is charged the difference for remaining days immediately.
Mid-cycle plan downgrade Credit is applied to next invoice, not refunded, unless you set proration_behavior: 'none'.
Trial to paid transition Stripe charges on the day the trial ends. Ensure payment method is collected before trial end — use customer.subscription.trial_will_end (fires 3 days early).
billing_cycle_anchor Sets a fixed billing date regardless of when the subscription starts. A customer subscribing on the 15th but anchored to the 1st will receive a prorated first invoice for the partial period.
Subscription pause Pausing stops invoice generation but keeps the subscription active. It is not the same as cancellation — the subscription resumes automatically. Use pause_collection with a resumes_at timestamp.

Test vs Live Mode Pitfalls

  • Restricted API keys: Scope restricted keys to the minimum permissions needed. A test key cannot be used against live mode endpoints — they are completely separate environments.
  • Test clocks: For time-sensitive billing scenarios (trial expiry, dunning, renewal), use Stripe Test Clocks to advance time in test mode rather than waiting. A test clock simulates the passage of time for a specific customer object.
  • Webhook behavior difference: In test mode, you trigger events manually via the Dashboard or CLI (stripe trigger). Live mode events fire from real customer actions. Test events may arrive in a different order than live events — do not depend on ordering.
  • Coupon and promotion codes: Codes created in test mode do not carry over to live mode. Recreate them explicitly before launch.

PCI Compliance Scope

Using Stripe Elements or the Payment Element reduces your PCI DSS scope to SAQ A — the simplest self-assessment questionnaire. Card data never touches your servers; it goes directly to Stripe's iframe.

What triggers SAQ D: Collecting card data through your own form fields (not Stripe-hosted), building a custom payment UI that proxies to Stripe's API, or storing raw card numbers. SAQ D requires ~300 controls and an annual QSA assessment.

Stay out of scope: Never log request bodies that might contain card data. Never build a server-side proxy between your frontend and Stripe's API for card collection. Use stripe.js loaded directly from js.stripe.com — do not self-host it.

Ready to try Stripe Integration Expert?

Install the skill and start getting expert-level guidance in your workflow — any agent, any IDE.

$skillfish add ~skills/stripe-integration-expert
← Browse all 169 skills