Use Cases (अपडेट: 1/6/2026)

Claude Code से e-commerce store बनाना: Next.js, Stripe Checkout और inventory

Claude Code से product list, cart, inventory, Stripe Checkout, Webhook, admin, SEO, analytics और returns वाला e-commerce flow बनाएं।

Claude Code से e-commerce store बनाना: Next.js, Stripe Checkout और inventory

E-commerce build में Claude Code को क्या देना चाहिए

E-commerce store सिर्फ product cards और payment button से पूरा नहीं होता। एक भरोसेमंद store में product listing, product detail, cart, inventory reservation, order creation, Stripe Checkout, Webhook से order confirmation, admin screen, SEO, analytics, returns और cancellations एक साथ काम करते हैं।

Claude Code का सही उपयोग तब होता है जब आप उसे business boundaries साफ बताकर काम देते हैं। Masa ने एक छोटे physical product prototype में पहले success page को order confirmation मान लिया था। लेकिन customer payment के बाद browser बंद कर सकता है, Stripe Webhook फिर से भेज सकता है, और expired Checkout Session का stock वापस करना पड़ता है। इसलिए बेहतर rule है: payment confirmation Webhook से होगा, और price तथा stock server पर फिर से calculate होंगे।

इस लेख में Next.js App Router और TypeScript का उपयोग है। उदाहरण local testing के लिए in-memory store रखते हैं; production में इसे PostgreSQL, Prisma या अपने order database से बदलें। Payment side पर Stripe की official Checkout Sessions API और Checkout fulfillment guide देखें। Next.js के लिए Route Handlers और Metadata API उपयोगी हैं।

Related guides: Claude Code से Stripe Checkout, Claude Code से SEO optimization, और Claude Code dashboard development

पहले architecture साफ करें

Flow को चार हिस्सों में बांटें: order बनाना, stock reserve करना, payment confirm करना, और payment के बाद order operate करना। इससे Claude Code का काम और human review दोनों स्पष्ट होते हैं।

flowchart LR
  A["Product listing और detail"] --> B["Cart"]
  B --> C["Order creation API"]
  C --> D["Inventory reservation"]
  D --> E["Stripe Checkout Session"]
  E --> F["Webhook"]
  F --> G["Paid order और fulfillment"]
  G --> H["Admin screen"]
  H --> I["Shipping, returns, cancellations"]
  A --> J["SEO और structured data"]
  B --> K["Analytics events"]

पहला prompt business rules के साथ लिखें।

Next.js App Router और TypeScript से छोटा e-commerce store बनाएं।
Product list, cart, inventory reservation, Stripe Checkout Session creation,
Webhook-based order confirmation, और admin shipping/cancellation actions अलग रखें।
Client से आए price या stock पर भरोसा न करें।
Stripe Webhook signature verify करें और checkout.session.completed तथा async_payment_succeeded handle करें।
सिर्फ success_url visit होने से order को paid न मानें।

Product, inventory और order model

नीचे का module local test के लिए copy-paste किया जा सकता है। यह memory में data रखता है, इसलिए production के लिए database जरूरी है। मुख्य बात यह है कि server ही price, stock और order status का source of truth है।

// src/lib/store.ts
export type Product = {
  id: string;
  slug: string;
  name: string;
  description: string;
  priceJPY: number;
  stock: number;
  active: boolean;
  image: string;
};

export type CartLine = {
  productId: string;
  quantity: number;
};

export type OrderLine = CartLine & {
  name: string;
  unitAmount: number;
};

export type OrderStatus = "pending" | "paid" | "shipped" | "canceled" | "refunded";

export type Order = {
  id: string;
  lines: OrderLine[];
  amountTotal: number;
  status: OrderStatus;
  reserved: boolean;
  stripeSessionId?: string;
  customerEmail?: string;
  createdAt: string;
  updatedAt: string;
};

const products: Product[] = [
  {
    id: "tea-001",
    slug: "roasted-green-tea",
    name: "Hojicha gift set",
    description: "पहली खरीद के लिए gift box set.",
    priceJPY: 3200,
    stock: 12,
    active: true,
    image: "/images/products/tea.jpg",
  },
  {
    id: "mug-001",
    slug: "ceramic-mug",
    name: "Handmade ceramic mug",
    description: "Small batch mug. Return restock से पहले damage check करें.",
    priceJPY: 4800,
    stock: 6,
    active: true,
    image: "/images/products/mug.jpg",
  },
];

const stock = new Map<string, number>(products.map((product) => [product.id, product.stock]));
const orders = new Map<string, Order>();

export function listProducts(): Product[] {
  return products
    .filter((product) => product.active)
    .map((product) => ({ ...product, stock: stock.get(product.id) ?? 0 }));
}

export function getProduct(productIdOrSlug: string): Product | undefined {
  return listProducts().find(
    (product) => product.id === productIdOrSlug || product.slug === productIdOrSlug,
  );
}

function normalizeLines(lines: CartLine[]): CartLine[] {
  const merged = new Map<string, number>();

  for (const line of lines) {
    if (!Number.isInteger(line.quantity) || line.quantity < 1 || line.quantity > 20) {
      throw new Error("Quantity 1 से 20 के बीच होनी चाहिए.");
    }
    merged.set(line.productId, (merged.get(line.productId) ?? 0) + line.quantity);
  }

  return Array.from(merged, ([productId, quantity]) => ({ productId, quantity }));
}

function requireOrder(orderId: string): Order {
  const order = orders.get(orderId);
  if (!order) throw new Error("Order नहीं मिला.");
  return order;
}

export function createPendingOrder(lines: CartLine[]): Order {
  const normalized = normalizeLines(lines);
  const orderLines = normalized.map((line) => {
    const product = getProduct(line.productId);
    if (!product) throw new Error(`Product नहीं मिला: ${line.productId}`);

    const availableStock = stock.get(product.id) ?? 0;
    if (availableStock < line.quantity) {
      throw new Error(`${product.name} का stock पर्याप्त नहीं है.`);
    }

    return {
      productId: product.id,
      quantity: line.quantity,
      name: product.name,
      unitAmount: product.priceJPY,
    };
  });

  const now = new Date().toISOString();
  const order: Order = {
    id: crypto.randomUUID(),
    lines: orderLines,
    amountTotal: orderLines.reduce((sum, line) => sum + line.unitAmount * line.quantity, 0),
    status: "pending",
    reserved: false,
    createdAt: now,
    updatedAt: now,
  };

  orders.set(order.id, order);
  return order;
}

export function reserveOrderStock(orderId: string): Order {
  const order = requireOrder(orderId);
  if (order.reserved) return order;

  for (const line of order.lines) {
    const availableStock = stock.get(line.productId) ?? 0;
    if (availableStock < line.quantity) {
      throw new Error(`${line.name} का stock पर्याप्त नहीं है.`);
    }
  }

  for (const line of order.lines) {
    stock.set(line.productId, (stock.get(line.productId) ?? 0) - line.quantity);
  }

  order.reserved = true;
  order.updatedAt = new Date().toISOString();
  return order;
}

export function attachStripeSession(orderId: string, stripeSessionId: string): Order {
  const order = requireOrder(orderId);
  order.stripeSessionId = stripeSessionId;
  order.updatedAt = new Date().toISOString();
  return order;
}

export function fulfillPaidOrder(input: {
  orderId: string;
  stripeSessionId: string;
  customerEmail?: string;
}): Order {
  const order = requireOrder(input.orderId);
  if (order.status === "paid" || order.status === "shipped") return order;

  if (!order.reserved) reserveOrderStock(order.id);

  order.status = "paid";
  order.stripeSessionId = input.stripeSessionId;
  order.customerEmail = input.customerEmail;
  order.updatedAt = new Date().toISOString();
  return order;
}

export function markOrderShipped(orderId: string): Order {
  const order = requireOrder(orderId);
  if (order.status !== "paid") throw new Error("Only paid orders can be shipped.");
  order.status = "shipped";
  order.updatedAt = new Date().toISOString();
  return order;
}

export function cancelOrder(orderId: string, reason = "customer_canceled"): Order {
  const order = requireOrder(orderId);
  if (order.status === "canceled" || order.status === "refunded") return order;

  if (order.status === "pending" && order.reserved) {
    for (const line of order.lines) {
      stock.set(line.productId, (stock.get(line.productId) ?? 0) + line.quantity);
    }
    order.reserved = false;
  }

  order.status = "canceled";
  order.updatedAt = new Date().toISOString();
  console.info(`Order ${order.id} canceled: ${reason}`);
  return order;
}

export function markOrderRefunded(orderId: string): Order {
  const order = requireOrder(orderId);
  if (order.status !== "paid" && order.status !== "shipped") {
    throw new Error("Only paid or shipped orders can be refunded.");
  }
  order.status = "refunded";
  order.updatedAt = new Date().toISOString();
  return order;
}

export function listOrders(): Order[] {
  return Array.from(orders.values()).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}

इस code की review में quantity validation, stock shortage, repeated Webhook पर duplicate fulfillment, और pending order cancel होने पर stock restore होना जरूर देखें।

Product list और cart

Client cart subtotal दिखा सकता है, लेकिन final price तय नहीं कर सकता। Server को केवल product ID और quantity भेजें।

// src/components/product-grid-with-cart.tsx
"use client";

import { useMemo, useState } from "react";
import type { CartLine, Product } from "@/lib/store";

type CheckoutResponse = {
  url?: string;
  error?: string;
};

export function ProductGridWithCart({ products }: { products: Product[] }) {
  const [cart, setCart] = useState<CartLine[]>([]);
  const [loading, setLoading] = useState(false);

  const subtotal = useMemo(() => {
    return cart.reduce((sum, line) => {
      const product = products.find((item) => item.id === line.productId);
      return sum + (product?.priceJPY ?? 0) * line.quantity;
    }, 0);
  }, [cart, products]);

  function addToCart(productId: string) {
    setCart((current) => {
      const existing = current.find((line) => line.productId === productId);
      if (existing) {
        return current.map((line) =>
          line.productId === productId ? { ...line, quantity: line.quantity + 1 } : line,
        );
      }
      return [...current, { productId, quantity: 1 }];
    });
  }

  async function checkout() {
    try {
      setLoading(true);
      const response = await fetch("/api/checkout", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ lines: cart }),
      });
      const data = (await response.json()) as CheckoutResponse;

      if (!response.ok || !data.url) {
        throw new Error(data.error ?? "Checkout शुरू नहीं हो पाया.");
      }

      window.location.href = data.url;
    } catch (error) {
      alert(error instanceof Error ? error.message : "Checkout failed.");
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="grid gap-8 lg:grid-cols-[1fr_320px]">
      <div className="grid gap-6 sm:grid-cols-2">
        {products.map((product) => (
          <article key={product.id} className="rounded-lg border p-4">
            <img src={product.image} alt={product.name} className="aspect-square w-full object-cover" />
            <h2 className="mt-3 text-lg font-semibold">{product.name}</h2>
            <p className="mt-1 text-sm text-gray-600">{product.description}</p>
            <p className="mt-3 font-bold">JPY {product.priceJPY.toLocaleString()}</p>
            <p className="text-sm text-gray-500">Stock: {product.stock}</p>
            <button type="button" disabled={product.stock < 1} onClick={() => addToCart(product.id)} className="mt-4 w-full rounded bg-black px-4 py-2 text-white disabled:bg-gray-300">
              Cart में जोड़ें
            </button>
          </article>
        ))}
      </div>

      <aside className="h-fit rounded-lg border p-4">
        <h2 className="text-lg font-semibold">Cart</h2>
        {cart.length === 0 ? (
          <p className="mt-3 text-sm text-gray-500">Cart खाली है.</p>
        ) : (
          <ul className="mt-3 space-y-2">
            {cart.map((line) => {
              const product = products.find((item) => item.id === line.productId);
              return (
                <li key={line.productId} className="flex justify-between text-sm">
                  <span>{product?.name}</span>
                  <span>{line.quantity}</span>
                </li>
              );
            })}
          </ul>
        )}
        <p className="mt-4 font-bold">Subtotal: JPY {subtotal.toLocaleString()}</p>
        <button type="button" disabled={cart.length === 0 || loading} onClick={checkout} className="mt-4 w-full rounded bg-blue-600 px-4 py-2 text-white disabled:bg-gray-300">
          {loading ? "Redirecting..." : "Stripe Checkout पर जाएं"}
        </button>
      </aside>
    </div>
  );
}

Stripe Checkout API

Stripe पर redirect करने से पहले order बनाएं और stock reserve करें। Amount server-side product data से बनेगा। Internal order ID कोmetadataमें रखें, लेकिन personal data या card detail वहां न डालें।

// src/app/api/checkout/route.ts
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
import {
  attachStripeSession,
  createPendingOrder,
  getProduct,
  reserveOrderStock,
} from "@/lib/store";

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

export async function POST(request: NextRequest) {
  try {
    const { lines } = (await request.json()) as {
      lines: { productId: string; quantity: number }[];
    };

    const order = createPendingOrder(lines);
    reserveOrderStock(order.id);

    const session = await stripe.checkout.sessions.create({
      mode: "payment",
      line_items: order.lines.map((line) => {
        const product = getProduct(line.productId);
        if (!product) throw new Error(`Product नहीं मिला: ${line.productId}`);

        return {
          price_data: {
            currency: "jpy",
            product_data: {
              name: product.name,
              images: [`${process.env.NEXT_PUBLIC_APP_URL}${product.image}`],
              metadata: { productId: product.id },
            },
            unit_amount: product.priceJPY,
          },
          quantity: line.quantity,
        };
      }),
      success_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/cart?canceled=1`,
      shipping_address_collection: {
        allowed_countries: ["JP"],
      },
      metadata: {
        orderId: order.id,
      },
    });

    attachStripeSession(order.id, session.id);
    return NextResponse.json({ url: session.url }, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Checkout failed." },
      { status: 400 },
    );
  }
}
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
NEXT_PUBLIC_APP_URL=http://localhost:3000

मुख्य गलती है browser subtotal सेunit_amountबनाना। यह हमेशा server data से बने। साथ ही, Checkout Session expire होने पर reserved stock release करने की योजना रखें।

Webhook से order confirm करें

Success page fulfillment नहीं है। Customer payment के बाद site पर वापस न भी आए, तो order confirm होना चाहिए। Webhook source of truth है और handler idempotent होना चाहिए।

// src/app/api/stripe/webhook/route.ts
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
import { cancelOrder, fulfillPaidOrder } from "@/lib/store";

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

export async function POST(request: NextRequest) {
  const body = await request.text();
  const signature = request.headers.get("stripe-signature");

  if (!signature) {
    return NextResponse.json({ error: "Missing Stripe signature" }, { status: 400 });
  }

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!,
    );
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Invalid webhook" },
      { status: 400 },
    );
  }

  if (
    event.type === "checkout.session.completed" ||
    event.type === "checkout.session.async_payment_succeeded"
  ) {
    const session = event.data.object as Stripe.Checkout.Session;
    const orderId = session.metadata?.orderId;

    if (orderId && session.payment_status === "paid") {
      fulfillPaidOrder({
        orderId,
        stripeSessionId: session.id,
        customerEmail: session.customer_details?.email ?? undefined,
      });
    }
  }

  if (
    event.type === "checkout.session.expired" ||
    event.type === "checkout.session.async_payment_failed"
  ) {
    const session = event.data.object as Stripe.Checkout.Session;
    const orderId = session.metadata?.orderId;
    if (orderId) cancelOrder(orderId, event.type);
  }

  return NextResponse.json({ received: true });
}
stripe listen --forward-to localhost:3000/api/stripe/webhook

Review में signature verification,payment_status, async payment success/failure, stock restoration और idempotency देखें।

Admin, returns और cancellations

Admin screen सिर्फ देखने के लिए नहीं है। Operator को paid orders, shipping status, pending order cancellation और refund status देखना चाहिए। Production में authentication और role-based access जरूर जोड़ें।

// src/app/admin/orders/page.tsx
import { cancelOrder, listOrders, markOrderShipped } from "@/lib/store";

async function shipOrder(formData: FormData) {
  "use server";
  markOrderShipped(String(formData.get("orderId")));
}

async function cancelPendingOrder(formData: FormData) {
  "use server";
  cancelOrder(String(formData.get("orderId")), "admin_canceled");
}

export default function AdminOrdersPage() {
  const orders = listOrders();

  return (
    <main className="mx-auto max-w-5xl p-6">
      <h1 className="text-2xl font-bold">Orders</h1>
      <div className="mt-6 overflow-x-auto">
        <table className="w-full border-collapse text-sm">
          <thead>
            <tr className="border-b text-left">
              <th className="py-2">Order</th>
              <th className="py-2">Status</th>
              <th className="py-2">Amount</th>
              <th className="py-2">Email</th>
              <th className="py-2">Actions</th>
            </tr>
          </thead>
          <tbody>
            {orders.map((order) => (
              <tr key={order.id} className="border-b">
                <td className="py-3 font-mono text-xs">{order.id}</td>
                <td className="py-3">{order.status}</td>
                <td className="py-3">JPY {order.amountTotal.toLocaleString()}</td>
                <td className="py-3">{order.customerEmail ?? "-"}</td>
                <td className="flex gap-2 py-3">
                  <form action={shipOrder}>
                    <input type="hidden" name="orderId" value={order.id} />
                    <button type="submit" disabled={order.status !== "paid"} className="rounded bg-black px-3 py-1 text-white disabled:bg-gray-300">
                      Mark shipped
                    </button>
                  </form>
                  <form action={cancelPendingOrder}>
                    <input type="hidden" name="orderId" value={order.id} />
                    <button type="submit" disabled={order.status !== "pending"} className="rounded border px-3 py-1 disabled:text-gray-300">
                      Cancel
                    </button>
                  </form>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </main>
  );
}

Return policy automation से पहले तय करें। Shipping से पहले cancellation stock वापस कर सकती है। Shipping के बाद return में item condition check, resale, discount sale या discard का निर्णय चाहिए।

SEO और analytics

हर product page search traffic ला सकता है। Product name, use case, material, delivery, return policy, OGP image और canonical जोड़ें। Analytics में view_item, add_to_cart, begin_checkout और purchase event names consistent रखें।

// src/app/products/[slug]/metadata.ts
import type { Metadata } from "next";
import { getProduct } from "@/lib/store";

export async function generateMetadata({
  params,
}: {
  params: { slug: string };
}): Promise<Metadata> {
  const product = getProduct(params.slug);

  if (!product) {
    return {
      title: "Product not found",
      robots: { index: false, follow: false },
    };
  }

  const title = `${product.name} | ClaudeCodeLab Store`;
  const description = `${product.description} Price: JPY ${product.priceJPY.toLocaleString()}. Purchase से पहले stock, delivery और return conditions देखें.`;

  return {
    title,
    description,
    alternates: {
      canonical: `/products/${product.slug}`,
    },
    openGraph: {
      title,
      description,
      images: [product.image],
      type: "website",
    },
  };
}
// src/lib/analytics.ts
type CommerceEventName = "view_item" | "add_to_cart" | "begin_checkout" | "purchase";

type CommercePayload = {
  currency: "JPY";
  value: number;
  items: Array<{
    item_id: string;
    item_name: string;
    price: number;
    quantity: number;
  }>;
};

declare global {
  interface Window {
    dataLayer?: unknown[];
  }
}

export function trackCommerceEvent(name: CommerceEventName, payload: CommercePayload) {
  if (typeof window === "undefined") return;
  window.dataLayer = window.dataLayer ?? [];
  window.dataLayer.push({
    event: name,
    ecommerce: payload,
  });
}

तीन वास्तविक use cases

D2C launch में advanced search से पहले stock reservation, Checkout, Webhook और shipping admin जरूरी हैं। Social traffic एक साथ कई purchases ला सकता है।

Digital product store में physical shipping नहीं होती, लेकिन access fulfillment जरूरी है। Download link success page पर ही न दें; Webhook payment confirm करे, फिर access दें।

B2B ordering portal में quote और approval लग सकता है। Card payment के लिए Stripe Checkout रखें, पर admin में quote requested, invoice sent और payment confirmed जैसे statuses जोड़ें।

Use casePriorityRisk
D2C launchStock, Checkout, shippingSimultaneous purchases
Digital productAccess after Webhooksuccess_url पर depend न करें
B2B orderingAdmin, quote, statusHuman approval बच सकता है

Common failure cases

सबसे बड़ा failure हैsuccess_urlलोड होते ही order को paid मानना। Customer payment करके वापस न आए, फिर भी order process होना चाहिए। Webhook ही source of truth है।

दूसरा failure cart total पर भरोसा करना है। Browser JavaScript बदल सकता है। Server को price, discount, shipping और stock फिर से calculate करना चाहिए।

Inventory भी tricky है। Checkout शुरू होते ही reserve करेंगे तो abandoned sessions stock रोक देंगे। Payment के बाद ही deduct करेंगे तो oversell हो सकता है। Short reservation, expiration release और admin adjustment का संयोजन चाहिए।

Security में Stripe secret leak, Webhook signature skip, unauthenticated admin, और metadata में personal data डालना गंभीर गलती है।

Claude Code review prompt

इस e-commerce implementation की review करें।
Price tampering, duplicate stock reservation, Webhook retry पर duplicate fulfillment,
unpaid order shipping, cancellation पर stock restoration, admin permissions,
Stripe secret और webhook secret exposure, तथा metadata में personal data check करें।
हर issue के लिए file, function, reproduction steps और concrete fix दें।

Human verification में test card success, authentication failure, Checkout expiration, Webhook replay, shipping button double click और refund status display शामिल करें।

निष्कर्ष

Claude Code से मजबूत e-commerce foundation बन सकता है, अगर आप product list, cart, inventory, order state, Stripe Checkout, Webhook, admin, SEO, analytics, returns और cancellations को एक business flow की तरह design करें। Production quality के लिए payment Webhook से confirm करें, price और stock server पर calculate करें, और repeated events को safe बनाएं।

ClaudeCodeLab e-commerce prototype, Stripe Checkout integration, admin dashboard, SEO improvement और Claude Code training में मदद करता है। Current app, product rules और operational constraints साथ हों तो implementation plan जल्दी साफ होता है।

इस लेख का code आजमाते समय देखें किstripe listenevents receive करता है, test card सेcheckout.session.completedआता है, success page बंद करने पर भी order paid होता है, stock shortage Checkout Session को रोकती है, और admin buttons सिर्फ सही status में active होते हैं।

#Claude Code #e-commerce #Stripe #Next.js #TypeScript
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.