
E Commerce
- 354 installs
- 19 repo stars
- Updated January 20, 2026
- miles990/claude-software-skills
e-commerce is a Claude agent skill that scaffolds storefront UX, cart and checkout flows, catalog pages, and payment hooks for developers building Shopify-like or custom headless commerce experiences.
About
e-commerce is an agent skill from miles990/claude-software-skills that guides Claude to scaffold online store experiences in code. Developers use it for storefront UX, shopping cart and checkout flows, product catalog pages, and payment integration hooks suited to Shopify-like platforms or custom headless commerce stacks. The skill emphasizes commerce user journeys rather than generic CRUD apps. Reach for it when bootstrapping a shop frontend, wiring checkout steps, or connecting catalog views to payment providers in a new or headless storefront project.
- Product catalog and PDP patterns
- Cart, checkout, and order summary UI
- Pricing, tax, and promo display
- Payment provider integration stubs
- Mobile-first merchandising layouts
E Commerce by the numbers
- 354 all-time installs (skills.sh)
- +3 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #698 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/miles990/claude-software-skills --skill e-commerceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 354 |
|---|---|
| repo stars | ★ 19 |
| Last updated | January 20, 2026 |
| Repository | miles990/claude-software-skills ↗ |
How do you scaffold a headless ecommerce storefront?
Scaffold storefront UX, cart and checkout flows, catalog pages, and payment hooks for Shopify-like or custom headless commerce experiences.
Who is it for?
Full-stack developers bootstrapping headless or custom ecommerce frontends with cart, checkout, catalog, and payment wiring.
Skip if: Teams needing ERP inventory sync, advanced fraud systems, or marketplace-only backend APIs without storefront UI.
When should I use this skill?
A task asks to scaffold an online store, shopping cart, checkout flow, product catalog, or payment hooks for ecommerce.
What you get
Storefront pages, cart and checkout flows, catalog views, and payment integration hook scaffolding.
- Storefront UI scaffolding
- Cart and checkout flows
- Payment hook integrations
Files
E-Commerce Development
Overview
Building e-commerce applications with shopping carts, payment processing, inventory management, and order fulfillment.
---
Shopping Cart
Cart State Management
interface CartItem {
productId: string;
variantId?: string;
quantity: number;
price: number;
name: string;
image: string;
}
interface Cart {
id: string;
items: CartItem[];
subtotal: number;
tax: number;
shipping: number;
total: number;
discountCode?: string;
discountAmount: number;
}
// Zustand cart store
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartStore {
cart: Cart;
addItem: (item: Omit<CartItem, 'quantity'>, quantity?: number) => void;
updateQuantity: (productId: string, quantity: number) => void;
removeItem: (productId: string) => void;
clearCart: () => void;
applyDiscount: (code: string) => Promise<void>;
}
const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
cart: createEmptyCart(),
addItem: (item, quantity = 1) => {
set((state) => {
const existingIndex = state.cart.items.findIndex(
(i) => i.productId === item.productId && i.variantId === item.variantId
);
const newItems = [...state.cart.items];
if (existingIndex >= 0) {
newItems[existingIndex].quantity += quantity;
} else {
newItems.push({ ...item, quantity });
}
return { cart: recalculateCart({ ...state.cart, items: newItems }) };
});
},
updateQuantity: (productId, quantity) => {
set((state) => {
if (quantity <= 0) {
return {
cart: recalculateCart({
...state.cart,
items: state.cart.items.filter((i) => i.productId !== productId),
}),
};
}
const newItems = state.cart.items.map((item) =>
item.productId === productId ? { ...item, quantity } : item
);
return { cart: recalculateCart({ ...state.cart, items: newItems }) };
});
},
removeItem: (productId) => {
set((state) => ({
cart: recalculateCart({
...state.cart,
items: state.cart.items.filter((i) => i.productId !== productId),
}),
}));
},
clearCart: () => set({ cart: createEmptyCart() }),
applyDiscount: async (code) => {
const discount = await validateDiscountCode(code);
set((state) => ({
cart: recalculateCart({
...state.cart,
discountCode: code,
discountAmount: discount.amount,
}),
}));
},
}),
{ name: 'cart-storage' }
)
);
function recalculateCart(cart: Cart): Cart {
const subtotal = cart.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const tax = subtotal * 0.1; // 10% tax
const shipping = subtotal > 100 ? 0 : 9.99;
const total = subtotal + tax + shipping - cart.discountAmount;
return { ...cart, subtotal, tax, shipping, total };
}---
Payment Processing
Stripe Integration
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
});
// Create checkout session
async function createCheckoutSession(cart: Cart, customerId?: string) {
const session = await stripe.checkout.sessions.create({
mode: 'payment',
customer: customerId,
line_items: cart.items.map((item) => ({
price_data: {
currency: 'usd',
product_data: {
name: item.name,
images: [item.image],
},
unit_amount: Math.round(item.price * 100),
},
quantity: item.quantity,
})),
discounts: cart.discountCode
? [{ coupon: cart.discountCode }]
: undefined,
shipping_address_collection: {
allowed_countries: ['US', 'CA', 'GB'],
},
success_url: `${process.env.APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/cart`,
metadata: {
cartId: cart.id,
},
});
return session;
}
// Create payment intent (for custom checkout)
async function createPaymentIntent(amount: number, customerId?: string) {
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount * 100),
currency: 'usd',
customer: customerId,
automatic_payment_methods: { enabled: true },
});
return {
clientSecret: paymentIntent.client_secret,
paymentIntentId: paymentIntent.id,
};
}
// Webhook handler
async function handleStripeWebhook(body: string, signature: string) {
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
await fulfillOrder(session);
break;
}
case 'payment_intent.succeeded': {
const paymentIntent = event.data.object as Stripe.PaymentIntent;
await handlePaymentSuccess(paymentIntent);
break;
}
case 'payment_intent.payment_failed': {
const paymentIntent = event.data.object as Stripe.PaymentIntent;
await handlePaymentFailure(paymentIntent);
break;
}
}
}React Stripe Elements
import { loadStripe } from '@stripe/stripe-js';
import {
Elements,
PaymentElement,
useStripe,
useElements,
} from '@stripe/react-stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY!);
function CheckoutForm({ clientSecret }: { clientSecret: string }) {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState<string | null>(null);
const [processing, setProcessing] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
setProcessing(true);
setError(null);
const { error: submitError } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/checkout/success`,
},
});
if (submitError) {
setError(submitError.message || 'Payment failed');
setProcessing(false);
}
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
{error && <div className="error">{error}</div>}
<button type="submit" disabled={!stripe || processing}>
{processing ? 'Processing...' : 'Pay Now'}
</button>
</form>
);
}
function CheckoutPage() {
const [clientSecret, setClientSecret] = useState('');
useEffect(() => {
fetch('/api/create-payment-intent', {
method: 'POST',
body: JSON.stringify({ amount: cart.total }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
if (!clientSecret) return <Loading />;
return (
<Elements
stripe={stripePromise}
options={{ clientSecret, appearance: { theme: 'stripe' } }}
>
<CheckoutForm clientSecret={clientSecret} />
</Elements>
);
}---
Inventory Management
interface Product {
id: string;
name: string;
sku: string;
price: number;
inventory: number;
lowStockThreshold: number;
variants: ProductVariant[];
}
interface ProductVariant {
id: string;
name: string;
sku: string;
price: number;
inventory: number;
attributes: Record<string, string>;
}
// Inventory operations with optimistic locking
async function reserveInventory(items: CartItem[]): Promise<boolean> {
return prisma.$transaction(async (tx) => {
for (const item of items) {
const product = await tx.product.findUnique({
where: { id: item.productId },
select: { inventory: true, version: true },
});
if (!product || product.inventory < item.quantity) {
throw new Error(`Insufficient inventory for ${item.name}`);
}
// Optimistic locking with version check
const updated = await tx.product.updateMany({
where: {
id: item.productId,
version: product.version,
inventory: { gte: item.quantity },
},
data: {
inventory: { decrement: item.quantity },
version: { increment: 1 },
},
});
if (updated.count === 0) {
throw new Error(`Concurrent modification for ${item.name}`);
}
}
return true;
});
}
// Release inventory (on order cancellation)
async function releaseInventory(orderId: string) {
const order = await prisma.order.findUnique({
where: { id: orderId },
include: { items: true },
});
await prisma.$transaction(
order.items.map((item) =>
prisma.product.update({
where: { id: item.productId },
data: { inventory: { increment: item.quantity } },
})
)
);
}
// Low stock alerts
async function checkLowStock() {
const lowStockProducts = await prisma.product.findMany({
where: {
inventory: { lte: prisma.product.fields.lowStockThreshold },
},
});
for (const product of lowStockProducts) {
await sendLowStockAlert(product);
}
}---
Order Management
enum OrderStatus {
PENDING = 'pending',
PAID = 'paid',
PROCESSING = 'processing',
SHIPPED = 'shipped',
DELIVERED = 'delivered',
CANCELLED = 'cancelled',
REFUNDED = 'refunded',
}
interface Order {
id: string;
userId: string;
status: OrderStatus;
items: OrderItem[];
subtotal: number;
tax: number;
shipping: number;
total: number;
shippingAddress: Address;
billingAddress: Address;
paymentIntentId: string;
trackingNumber?: string;
createdAt: Date;
updatedAt: Date;
}
// Create order from checkout session
async function fulfillOrder(session: Stripe.Checkout.Session) {
const order = await prisma.order.create({
data: {
userId: session.client_reference_id!,
status: OrderStatus.PAID,
paymentIntentId: session.payment_intent as string,
subtotal: session.amount_subtotal! / 100,
total: session.amount_total! / 100,
shippingAddress: JSON.parse(session.metadata!.shippingAddress),
items: {
create: JSON.parse(session.metadata!.items),
},
},
});
// Reserve inventory
await reserveInventory(order.items);
// Send confirmation email
await sendOrderConfirmation(order);
// Notify fulfillment system
await notifyFulfillment(order);
return order;
}
// Order status updates
async function updateOrderStatus(orderId: string, status: OrderStatus) {
const order = await prisma.order.update({
where: { id: orderId },
data: { status },
});
// Send notification
await sendOrderStatusUpdate(order);
return order;
}---
Product Catalog
// Product search with filters
async function searchProducts(params: {
query?: string;
category?: string;
minPrice?: number;
maxPrice?: number;
sortBy?: 'price' | 'name' | 'createdAt';
sortOrder?: 'asc' | 'desc';
page?: number;
limit?: number;
}) {
const {
query,
category,
minPrice,
maxPrice,
sortBy = 'createdAt',
sortOrder = 'desc',
page = 1,
limit = 20,
} = params;
const where: Prisma.ProductWhereInput = {
status: 'active',
...(query && {
OR: [
{ name: { contains: query, mode: 'insensitive' } },
{ description: { contains: query, mode: 'insensitive' } },
],
}),
...(category && { categoryId: category }),
...(minPrice && { price: { gte: minPrice } }),
...(maxPrice && { price: { lte: maxPrice } }),
};
const [products, total] = await Promise.all([
prisma.product.findMany({
where,
orderBy: { [sortBy]: sortOrder },
skip: (page - 1) * limit,
take: limit,
include: {
category: true,
images: true,
variants: true,
},
}),
prisma.product.count({ where }),
]);
return {
products,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
};
}---
Related Skills
- [[payment-processing]] - Payment systems
- [[backend]] - API development
- [[database]] - Data modeling
/**
* E-commerce Cart Schema Template
* Usage: Type definitions for shopping cart functionality
*/
// ===========================================
// Product Types
// ===========================================
export interface Product {
id: string;
name: string;
slug: string;
description: string;
price: number;
compareAtPrice?: number;
currency: string;
images: ProductImage[];
variants: ProductVariant[];
categories: string[];
tags: string[];
inventory: {
quantity: number;
trackInventory: boolean;
allowBackorder: boolean;
};
metadata: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
}
export interface ProductImage {
id: string;
url: string;
alt: string;
width: number;
height: number;
position: number;
}
export interface ProductVariant {
id: string;
sku: string;
name: string;
price: number;
compareAtPrice?: number;
options: VariantOption[];
inventory: number;
weight?: number;
dimensions?: {
length: number;
width: number;
height: number;
unit: 'cm' | 'in';
};
}
export interface VariantOption {
name: string; // e.g., "Size", "Color"
value: string; // e.g., "Large", "Blue"
}
// ===========================================
// Cart Types
// ===========================================
export interface Cart {
id: string;
userId?: string; // Optional for guest checkout
sessionId: string; // For guest carts
items: CartItem[];
subtotal: number;
discount: number;
tax: number;
shipping: number;
total: number;
currency: string;
couponCodes: string[];
metadata: Record<string, unknown>;
expiresAt: Date;
createdAt: Date;
updatedAt: Date;
}
export interface CartItem {
id: string;
productId: string;
variantId?: string;
name: string;
image?: string;
price: number;
quantity: number;
subtotal: number;
options: VariantOption[];
metadata: Record<string, unknown>;
}
// ===========================================
// Discount Types
// ===========================================
export interface Coupon {
id: string;
code: string;
type: 'percentage' | 'fixed' | 'free_shipping';
value: number;
minOrderAmount?: number;
maxDiscount?: number;
usageLimit?: number;
usedCount: number;
applicableTo: {
products?: string[];
categories?: string[];
all?: boolean;
};
startsAt: Date;
expiresAt?: Date;
isActive: boolean;
}
export interface AppliedDiscount {
couponId: string;
code: string;
type: Coupon['type'];
amount: number;
description: string;
}
// ===========================================
// Cart Operations
// ===========================================
export interface AddToCartInput {
productId: string;
variantId?: string;
quantity: number;
metadata?: Record<string, unknown>;
}
export interface UpdateCartItemInput {
itemId: string;
quantity: number;
}
export interface CartCalculation {
subtotal: number;
discounts: AppliedDiscount[];
discountTotal: number;
taxRate: number;
tax: number;
shippingMethod?: string;
shipping: number;
total: number;
}
// ===========================================
// Cart Service Interface
// ===========================================
export interface CartService {
// Cart CRUD
getCart(cartId: string): Promise<Cart | null>;
getCartBySession(sessionId: string): Promise<Cart | null>;
createCart(sessionId: string, userId?: string): Promise<Cart>;
deleteCart(cartId: string): Promise<void>;
// Items
addItem(cartId: string, input: AddToCartInput): Promise<Cart>;
updateItem(cartId: string, input: UpdateCartItemInput): Promise<Cart>;
removeItem(cartId: string, itemId: string): Promise<Cart>;
clearCart(cartId: string): Promise<Cart>;
// Coupons
applyCoupon(cartId: string, code: string): Promise<Cart>;
removeCoupon(cartId: string, code: string): Promise<Cart>;
// Calculation
calculateCart(cart: Cart): Promise<CartCalculation>;
}
// ===========================================
// Example Implementation
// ===========================================
export function calculateCartTotals(
items: CartItem[],
discounts: AppliedDiscount[] = [],
taxRate: number = 0,
shipping: number = 0
): CartCalculation {
// Calculate subtotal
const subtotal = items.reduce((sum, item) => sum + item.subtotal, 0);
// Calculate discount total
const discountTotal = discounts.reduce((sum, d) => sum + d.amount, 0);
// Calculate tax on discounted amount
const taxableAmount = Math.max(0, subtotal - discountTotal);
const tax = Math.round(taxableAmount * taxRate * 100) / 100;
// Calculate final total
const total = Math.max(0, taxableAmount + tax + shipping);
return {
subtotal,
discounts,
discountTotal,
taxRate,
tax,
shipping,
total,
};
}
export function applyPercentageDiscount(
subtotal: number,
percentage: number,
maxDiscount?: number
): number {
let discount = subtotal * (percentage / 100);
if (maxDiscount && discount > maxDiscount) {
discount = maxDiscount;
}
return Math.round(discount * 100) / 100;
}
export function validateCoupon(
coupon: Coupon,
cart: Cart
): { valid: boolean; error?: string } {
// Check if active
if (!coupon.isActive) {
return { valid: false, error: 'Coupon is not active' };
}
// Check dates
const now = new Date();
if (coupon.startsAt > now) {
return { valid: false, error: 'Coupon is not yet valid' };
}
if (coupon.expiresAt && coupon.expiresAt < now) {
return { valid: false, error: 'Coupon has expired' };
}
// Check usage limit
if (coupon.usageLimit && coupon.usedCount >= coupon.usageLimit) {
return { valid: false, error: 'Coupon usage limit reached' };
}
// Check minimum order
if (coupon.minOrderAmount && cart.subtotal < coupon.minOrderAmount) {
return {
valid: false,
error: `Minimum order amount is ${coupon.minOrderAmount}`,
};
}
// Check product applicability
if (!coupon.applicableTo.all) {
const hasApplicableItem = cart.items.some((item) => {
if (coupon.applicableTo.products?.includes(item.productId)) {
return true;
}
// Would need product categories here
return false;
});
if (!hasApplicableItem) {
return { valid: false, error: 'Coupon not applicable to cart items' };
}
}
return { valid: true };
}
/**
* E-commerce Checkout Flow Template
* Usage: Type definitions and utilities for checkout process
*/
import type { Cart, CartCalculation } from './cart-schema';
// ===========================================
// Customer Types
// ===========================================
export interface Customer {
id: string;
email: string;
firstName?: string;
lastName?: string;
phone?: string;
addresses: Address[];
defaultAddressId?: string;
metadata: Record<string, unknown>;
}
export interface Address {
id: string;
firstName: string;
lastName: string;
company?: string;
address1: string;
address2?: string;
city: string;
state: string;
postalCode: string;
country: string;
phone?: string;
isDefault: boolean;
}
export interface GuestCustomer {
email: string;
firstName: string;
lastName: string;
phone?: string;
acceptsMarketing: boolean;
}
// ===========================================
// Checkout Types
// ===========================================
export interface Checkout {
id: string;
cartId: string;
status: CheckoutStatus;
// Customer
customerId?: string;
guest?: GuestCustomer;
email: string;
// Addresses
shippingAddress?: Address;
billingAddress?: Address;
sameAsShipping: boolean;
// Shipping
shippingMethod?: ShippingMethod;
shippingRate?: ShippingRate;
// Payment
paymentMethod?: PaymentMethod;
paymentIntent?: string;
// Totals
calculation: CartCalculation;
// Metadata
notes?: string;
metadata: Record<string, unknown>;
// Timestamps
createdAt: Date;
updatedAt: Date;
completedAt?: Date;
}
export type CheckoutStatus =
| 'pending'
| 'address'
| 'shipping'
| 'payment'
| 'review'
| 'processing'
| 'completed'
| 'failed'
| 'abandoned';
// ===========================================
// Shipping Types
// ===========================================
export interface ShippingMethod {
id: string;
name: string;
carrier: string;
description: string;
estimatedDays: {
min: number;
max: number;
};
}
export interface ShippingRate {
methodId: string;
name: string;
price: number;
currency: string;
estimatedDelivery: string;
}
export interface ShippingZone {
id: string;
name: string;
countries: string[];
states?: string[];
postalCodes?: string[];
methods: ShippingMethod[];
}
// ===========================================
// Payment Types
// ===========================================
export type PaymentMethod =
| 'card'
| 'paypal'
| 'apple_pay'
| 'google_pay'
| 'bank_transfer'
| 'cash_on_delivery';
export interface PaymentDetails {
method: PaymentMethod;
provider: string; // e.g., 'stripe', 'paypal'
intentId?: string; // Payment intent ID
last4?: string; // Last 4 digits of card
brand?: string; // Card brand
expiryMonth?: number;
expiryYear?: number;
}
export interface PaymentResult {
success: boolean;
transactionId?: string;
error?: string;
requiresAction?: boolean;
actionUrl?: string;
}
// ===========================================
// Order Types
// ===========================================
export interface Order {
id: string;
orderNumber: string;
checkoutId: string;
customerId?: string;
status: OrderStatus;
// Customer info
email: string;
shippingAddress: Address;
billingAddress: Address;
// Items
items: OrderItem[];
// Shipping
shippingMethod: string;
shippingCost: number;
trackingNumber?: string;
trackingUrl?: string;
// Payment
paymentMethod: PaymentMethod;
paymentStatus: PaymentStatus;
transactionId?: string;
// Totals
subtotal: number;
discount: number;
tax: number;
shipping: number;
total: number;
currency: string;
// Metadata
notes?: string;
metadata: Record<string, unknown>;
// Timestamps
createdAt: Date;
updatedAt: Date;
paidAt?: Date;
fulfilledAt?: Date;
}
export interface OrderItem {
id: string;
productId: string;
variantId?: string;
sku: string;
name: string;
image?: string;
price: number;
quantity: number;
subtotal: number;
options: Array<{ name: string; value: string }>;
}
export type OrderStatus =
| 'pending'
| 'confirmed'
| 'processing'
| 'shipped'
| 'delivered'
| 'cancelled'
| 'refunded';
export type PaymentStatus =
| 'pending'
| 'authorized'
| 'captured'
| 'failed'
| 'refunded'
| 'partially_refunded';
// ===========================================
// Checkout Service Interface
// ===========================================
export interface CheckoutService {
// Checkout lifecycle
createCheckout(cartId: string): Promise<Checkout>;
getCheckout(checkoutId: string): Promise<Checkout | null>;
updateCheckout(checkoutId: string, data: Partial<Checkout>): Promise<Checkout>;
abandonCheckout(checkoutId: string): Promise<void>;
// Steps
setCustomerInfo(checkoutId: string, customer: GuestCustomer | string): Promise<Checkout>;
setShippingAddress(checkoutId: string, address: Address): Promise<Checkout>;
setBillingAddress(checkoutId: string, address: Address): Promise<Checkout>;
setShippingMethod(checkoutId: string, methodId: string): Promise<Checkout>;
// Shipping
getShippingRates(checkoutId: string): Promise<ShippingRate[]>;
// Payment
createPaymentIntent(checkoutId: string, method: PaymentMethod): Promise<string>;
processPayment(checkoutId: string, paymentDetails: PaymentDetails): Promise<PaymentResult>;
// Complete
completeCheckout(checkoutId: string): Promise<Order>;
}
// ===========================================
// Checkout Validation
// ===========================================
export interface ValidationError {
field: string;
message: string;
}
export function validateAddress(address: Partial<Address>): ValidationError[] {
const errors: ValidationError[] = [];
if (!address.firstName?.trim()) {
errors.push({ field: 'firstName', message: 'First name is required' });
}
if (!address.lastName?.trim()) {
errors.push({ field: 'lastName', message: 'Last name is required' });
}
if (!address.address1?.trim()) {
errors.push({ field: 'address1', message: 'Address is required' });
}
if (!address.city?.trim()) {
errors.push({ field: 'city', message: 'City is required' });
}
if (!address.postalCode?.trim()) {
errors.push({ field: 'postalCode', message: 'Postal code is required' });
}
if (!address.country?.trim()) {
errors.push({ field: 'country', message: 'Country is required' });
}
return errors;
}
export function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
export function validateCheckoutStep(
checkout: Checkout,
step: CheckoutStatus
): ValidationError[] {
const errors: ValidationError[] = [];
switch (step) {
case 'address':
if (!checkout.email || !validateEmail(checkout.email)) {
errors.push({ field: 'email', message: 'Valid email is required' });
}
break;
case 'shipping':
if (!checkout.shippingAddress) {
errors.push({ field: 'shippingAddress', message: 'Shipping address is required' });
} else {
errors.push(...validateAddress(checkout.shippingAddress));
}
break;
case 'payment':
if (!checkout.shippingMethod) {
errors.push({ field: 'shippingMethod', message: 'Shipping method is required' });
}
if (!checkout.sameAsShipping && !checkout.billingAddress) {
errors.push({ field: 'billingAddress', message: 'Billing address is required' });
}
break;
case 'review':
if (!checkout.paymentMethod) {
errors.push({ field: 'paymentMethod', message: 'Payment method is required' });
}
break;
}
return errors;
}
// ===========================================
// Order Number Generation
// ===========================================
export function generateOrderNumber(prefix: string = 'ORD'): string {
const timestamp = Date.now().toString(36).toUpperCase();
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
return `${prefix}-${timestamp}-${random}`;
}
// ===========================================
// Checkout State Machine
// ===========================================
export const CHECKOUT_TRANSITIONS: Record<CheckoutStatus, CheckoutStatus[]> = {
pending: ['address', 'abandoned'],
address: ['shipping', 'abandoned'],
shipping: ['payment', 'address', 'abandoned'],
payment: ['review', 'shipping', 'abandoned'],
review: ['processing', 'payment', 'abandoned'],
processing: ['completed', 'failed'],
completed: [],
failed: ['pending'],
abandoned: ['pending'],
};
export function canTransition(
from: CheckoutStatus,
to: CheckoutStatus
): boolean {
return CHECKOUT_TRANSITIONS[from]?.includes(to) ?? false;
}
E-commerce Templates
Type definitions and utilities for e-commerce functionality.
Files
| Template | Purpose |
|---|---|
cart-schema.ts | Cart, product, and discount types |
checkout-flow.ts | Checkout, order, and payment types |
Usage
Cart Management
import {
Cart,
CartItem,
AddToCartInput,
calculateCartTotals,
applyPercentageDiscount,
validateCoupon,
} from './cart-schema';
// Add item to cart
const input: AddToCartInput = {
productId: 'prod_123',
variantId: 'var_456',
quantity: 2,
};
// Calculate totals
const calculation = calculateCartTotals(
cart.items,
appliedDiscounts,
0.08, // 8% tax
5.99 // shipping
);
// Apply coupon
const discount = applyPercentageDiscount(
cart.subtotal,
20, // 20% off
50 // max $50 discount
);Checkout Flow
import {
Checkout,
CheckoutStatus,
validateAddress,
validateCheckoutStep,
canTransition,
generateOrderNumber,
} from './checkout-flow';
// Validate address
const errors = validateAddress(shippingAddress);
if (errors.length > 0) {
// Handle validation errors
}
// Check step validation
const stepErrors = validateCheckoutStep(checkout, 'shipping');
// Generate order number
const orderNumber = generateOrderNumber('ORD');
// => "ORD-M5K8X2-AB3C"
// Check state transitions
if (canTransition(checkout.status, 'payment')) {
// Proceed to payment
}Data Models
Cart Structure
Cart
├── items[]
│ ├── productId
│ ├── variantId
│ ├── quantity
│ └── subtotal
├── couponCodes[]
├── subtotal
├── discount
├── tax
├── shipping
└── totalCheckout Flow
pending → address → shipping → payment → review → processing → completed
↘ failedOrder Statuses
pending → confirmed → processing → shipped → delivered
↘ cancelled
↘ refundedKey Types
Product Variant
interface ProductVariant {
id: string;
sku: string;
name: string;
price: number;
options: [{ name: "Size", value: "Large" }];
inventory: number;
}Discount Types
percentage- % off orderfixed- Fixed amount offfree_shipping- Free shipping
Payment Methods
card- Credit/debit cardpaypal- PayPalapple_pay- Apple Paygoogle_pay- Google Paybank_transfer- Bank transfercash_on_delivery- COD
Integration Notes
With Stripe
// Create payment intent
const intent = await stripe.paymentIntents.create({
amount: Math.round(checkout.calculation.total * 100),
currency: 'usd',
metadata: { checkoutId: checkout.id },
});With Inventory
// Reserve inventory on checkout
async function reserveInventory(items: CartItem[]) {
for (const item of items) {
await db.product.update({
where: { id: item.productId },
data: { inventory: { decrement: item.quantity } },
});
}
}Tax Calculation
// Use tax service (e.g., TaxJar, Avalara)
const taxRate = await taxService.getRateForAddress(shippingAddress);Related skills
FAQ
What does the e-commerce skill scaffold?
The e-commerce skill scaffolds storefront UX, cart and checkout flows, catalog pages, and payment hooks for Shopify-like platforms or custom headless commerce applications.
Is e-commerce for backend-only APIs?
The e-commerce skill focuses on storefront UX and checkout journeys with payment hooks. It is not aimed at ERP inventory sync or marketplace backend-only services without UI.