[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Checkout Flow with Stripe

Build a Checkout Flow with Stripe

Build a production checkout flow — cart management, Stripe payment intents, 3D Secure handling, order confirmation, webhook-driven fulfillment, and failed payment recovery.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 6 skills

Avg quality 97/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

nextjs

v1.1.0

Assists with building production-grade React applications using Next.js 16. Use when working with the App Router, Server Components, Server Actions, the proxy file (formerly middleware), Cache Components, or deploying to Vercel or self-hosted environments. Trigger words: nextjs, next.js, app router, server components, server actions, use cache, proxy.ts, react framework, ssr, isr.

100/100 quality
5.29× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Nadia runs a 20-person e-commerce startup. Their checkout is a single "Buy Now" button that redirects to Stripe Checkout. It works, but they can't customize the flow, offer coupons, show order summaries, or handle cart abandonment. 35% of users abandon at checkout because the redirect feels jarring. They lose $15K/month in failed payments that are never retried. They need an embedded checkout with cart management, real-time price calculation, coupon support, and webhook-driven order processing.

Step 1: Build the Cart and Checkout API

typescript
// src/checkout/cart.ts — Server-side cart with pricing, coupons, and Stripe integration
import Stripe from "stripe";
import { pool } from "../db";
import { Redis } from "ioredis";
import { z } from "zod";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const redis = new Redis(process.env.REDIS_URL!);

interface CartItem {
  productId: string;
  name: string;
  price: number;           // in cents
  quantity: number;
  imageUrl: string;
}

interface Cart {
  id: string;
  userId: string | null;
  items: CartItem[];
  couponCode: string | null;
  discount: number;        // in cents
  subtotal: number;
  tax: number;
  total: number;
  currency: string;
}

const AddItemSchema = z.object({
  productId: z.string(),
  quantity: z.number().int().min(1).max(99),
});

// Get or create cart
export async function getCart(cartId: string): Promise<Cart> {
  const data = await redis.get(`cart:${cartId}`);
  if (data) return JSON.parse(data);

  return {
    id: cartId,
    userId: null,
    items: [],
    couponCode: null,
    discount: 0,
    subtotal: 0,
    tax: 0,
    total: 0,
    currency: "usd",
  };
}

// Add item to cart
export async function addToCart(cartId: string, productId: string, quantity: number): Promise<Cart> {
  const cart = await getCart(cartId);

  // Fetch product from DB
  const { rows: [product] } = await pool.query(
    "SELECT id, name, price_cents, image_url, stock FROM products WHERE id = $1 AND active = true",
    [productId]
  );
  if (!product) throw new Error("Product not found");
  if (product.stock < quantity) throw new Error(`Only ${product.stock} in stock`);

  // Update or add item
  const existing = cart.items.find((i) => i.productId === productId);
  if (existing) {
    existing.quantity = Math.min(existing.quantity + quantity, product.stock);
  } else {
    cart.items.push({
      productId: product.id,
      name: product.name,
      price: product.price_cents,
      quantity,
      imageUrl: product.image_url,
    });
  }

  return recalculateAndSave(cart);
}

// Apply coupon
export async function applyCoupon(cartId: string, code: string): Promise<Cart> {
  const cart = await getCart(cartId);

  const { rows: [coupon] } = await pool.query(
    `SELECT * FROM coupons
     WHERE code = $1 AND active = true
     AND (expires_at IS NULL OR expires_at > NOW())
     AND (max_uses IS NULL OR uses < max_uses)`,
    [code.toUpperCase()]
  );

  if (!coupon) throw new Error("Invalid or expired coupon");

  cart.couponCode = code.toUpperCase();

  if (coupon.type === "percentage") {
    cart.discount = Math.round(cart.subtotal * (coupon.value / 100));
  } else {
    cart.discount = Math.min(coupon.value, cart.subtotal); // don't exceed subtotal
  }

  return recalculateAndSave(cart);
}

// Create Stripe Payment Intent
export async function createPaymentIntent(cartId: string, userId: string): Promise<{
  clientSecret: string;
  paymentIntentId: string;
}> {
  const cart = await getCart(cartId);
  if (cart.items.length === 0) throw new Error("Cart is empty");

  // Get or create Stripe customer
  let { rows: [userRow] } = await pool.query(
    "SELECT stripe_customer_id FROM users WHERE id = $1",
    [userId]
  );

  let customerId = userRow?.stripe_customer_id;
  if (!customerId) {
    const { rows: [user] } = await pool.query("SELECT email, name FROM users WHERE id = $1", [userId]);
    const customer = await stripe.customers.create({ email: user.email, name: user.name });
    customerId = customer.id;
    await pool.query("UPDATE users SET stripe_customer_id = $1 WHERE id = $2", [customerId, userId]);
  }

  // Create payment intent
  const paymentIntent = await stripe.paymentIntents.create({
    amount: cart.total,
    currency: cart.currency,
    customer: customerId,
    metadata: {
      cartId,
      userId,
      couponCode: cart.couponCode || "",
      itemCount: String(cart.items.length),
    },
    automatic_payment_methods: { enabled: true },
  });

  // Store mapping for webhook processing
  await redis.setex(`pi:${paymentIntent.id}`, 3600, JSON.stringify({
    cartId, userId, items: cart.items, total: cart.total,
  }));

  return {
    clientSecret: paymentIntent.client_secret!,
    paymentIntentId: paymentIntent.id,
  };
}

// Webhook handler — processes successful payments
export async function handleStripeWebhook(event: Stripe.Event): Promise<void> {
  switch (event.type) {
    case "payment_intent.succeeded": {
      const pi = event.data.object as Stripe.PaymentIntent;
      const orderData = await redis.get(`pi:${pi.id}`);
      if (!orderData) return;

      const { cartId, userId, items, total } = JSON.parse(orderData);

      // Create order
      const { rows: [order] } = await pool.query(
        `INSERT INTO orders (user_id, items, subtotal, discount, tax, total, stripe_payment_intent_id, status, created_at)
         VALUES ($1, $2, $3, $4, $5, $6, $7, 'confirmed', NOW())
         RETURNING id`,
        [userId, JSON.stringify(items), total, 0, 0, total, pi.id]
      );

      // Decrement stock
      for (const item of items) {
        await pool.query(
          "UPDATE products SET stock = stock - $1 WHERE id = $2",
          [item.quantity, item.productId]
        );
      }

      // Clear cart
      await redis.del(`cart:${cartId}`);

      // Increment coupon usage
      if (pi.metadata.couponCode) {
        await pool.query("UPDATE coupons SET uses = uses + 1 WHERE code = $1", [pi.metadata.couponCode]);
      }

      // Send confirmation email (via queue)
      await redis.rpush("email:queue", JSON.stringify({
        type: "order_confirmation",
        userId,
        orderId: order.id,
      }));

      break;
    }

    case "payment_intent.payment_failed": {
      const pi = event.data.object as Stripe.PaymentIntent;
      // Queue recovery email
      await redis.rpush("email:queue", JSON.stringify({
        type: "payment_failed",
        userId: pi.metadata.userId,
        paymentIntentId: pi.id,
        retryUrl: `${process.env.APP_URL}/checkout/retry/${pi.id}`,
      }));
      break;
    }
  }
}

function recalculateAndSave(cart: Cart): Promise<Cart> {
  cart.subtotal = cart.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
  cart.tax = Math.round(cart.subtotal * 0.08);    // 8% tax
  cart.total = cart.subtotal - cart.discount + cart.tax;
  return redis.setex(`cart:${cart.id}`, 86400 * 7, JSON.stringify(cart)).then(() => cart);
}

Results

  • Checkout abandonment: 35% → 18% — embedded checkout feels native; no jarring redirect; order summary and coupon field visible on one page
  • Failed payment recovery: $0 → $8K/month recovered — webhook catches payment_failed, sends retry email with direct link; 53% of failed payments succeed on retry
  • Coupon system drives $12K/month extra revenue — 15% discount codes for first-time buyers convert at 2.3x the baseline rate
  • 3D Secure handled automatically — Stripe's automatic_payment_methods handles SCA compliance; European customers authenticate seamlessly
  • Order processing is reliable — webhook-driven fulfillment means orders are never lost even if the user's browser crashes after payment

Related use cases