关于 commerce-js-integration
面向构建无头商店的 JavaScript 开发者,介绍 Commerce.js SDK 如何通过 REST API 管理商品、购物车、结账和订单,并以 promise API 简化集成。
主要功能
- 01通过 SDK 获取和展示商品
- 02创建购物车并管理商品项
- 03生成结账令牌并捕获订单
- 04读取商品变体及选项
- 05查询客户订单
适用场景
- 01为静态网站添加电商功能
- 02在 React、Vue 或 Svelte 项目中构建商店
- 03为 MVP 或概念验证实现购物流程
- 04在前端集成商品、购物车和结账
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill commerce-js-integration安装后在 Claude Code 中输入/commerce-js-integration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/cart-management-with-react-context-and-v/criteria.jsontask.mdevals/checkout-flow-with-token-generation-and-/criteria.jsontask.mdevals/product-catalog-rendering-with-safe-html/criteria.jsontask.md详细介绍
Commerce.js 集成
概述
Commerce.js(Chec)是一个无头商务平台,提供 JavaScript SDK 来封装其 REST API,以管理产品、购物车、结账和订单。它面向希望在任何网站或 JavaScript 框架中添加商务功能,同时无需应对完整电商平台复杂性的开发者。该 SDK 通过简洁的、基于 Promise 的 API 处理产品获取、购物车生命周期、结账令牌创建和订单捕获。
何时使用此技能
- 想要快速为静态网站或简单的 React/Vue/Svelte 项目添加电商功能时
- 不需要复杂的后端,并希望使用托管的商务 API,而无需编写服务器端代码时
- 构建作品集项目、MVP 或概念验证无头商店时
- 产品目录规模较小(少于 1,000 件产品),且不需要自定义履约逻辑时
- 想要使用简单的 SDK,而无需管理完整的 Shopify 或 commercetools 环境时
前提条件和平台说明
此技能面向自定义/无头店面(Node.js、Python 或类似后端)。代码示例使用 TypeScript/Node.js,可适配任何技术栈。
Shopify:Shopify Hydrogen 是 Shopify 的无头框架。使用 Shopify 作为商务后端搭配自定义前端,或将 Shopify 与其他同类最佳服务混用时,适用 MACH/可组合模式。 WooCommerce:WooCommerce 可通过其 REST API 和 WPGraphQL 充当无头后端。当将前端与 WordPress 解耦时,适用这些模式。 Magento:Magento 的 GraphQL API 和 PWA Studio 支持无头架构。在 MACH 技术栈中将 Magento 用作后端服务时,适用这些可组合模式。
你需要:
- Node.js 18+(或适配为你的后端语言)
- Stripe 账户和 API 密钥
- 电子邮件发送服务(SendGrid、AWS SES 或 Postmark)
核心说明
-
安装 SDK 并初始化客户端
npm install @chec/commerce.jsimport Commerce from '@chec/commerce.js'; // Public key is safe to expose in the browser const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY, true); // true = debug mode在 Chec Dashboard 的 Developer → API keys 中获取你的公开 API 密钥。
-
获取并显示产品
// Fetch all products const {data: products} = await commerce.products.list({ limit: 20, page: 1, sort_by: 'created', sort_direction: 'desc', }); // Fetch a single product by permalink (slug) const product = await commerce.products.retrieve('my-product-slug', {type: 'permalink'}); // Products include structured data console.log({ id: product.id, name: product.name, price: product.price.formatted_with_symbol, // "$29.99" description: product.description, // HTML string from Chec CMS image: product.image?.url, variants: product.variants, // Size, color options });React 组件示例:
import {useEffect, useState} from 'react'; import Commerce from '@chec/commerce.js'; const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY!); export function ProductGrid() { const [products, setProducts] = useState<any[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { commerce.products.list() .then(({data}) => setProducts(data)) .finally(() => setLoading(false)); }, []); if (loading) return <div>Loading...</div>; return ( <div className="grid grid-cols-3 gap-6"> {products.map(product => ( <div key={product.id} className="border rounded-lg p-4"> <img src={product.image?.url} alt={product.name} className="w-full h-48 object-cover" /> <h2 className="mt-2 font-semibold">{product.name}</h2> <p className="text-gray-600">{product.price.formatted_with_symbol}</p> <AddToCartButton productId={product.id} /> </div> ))} </div> ); } -
管理购物车
Commerce.js 会自动将购物车 ID 存储在 localStorage 中:
// Get or create the current cart const cart = await commerce.cart.retrieve(); // Add a product to the cart const updatedCart = await commerce.cart.add(productId, quantity, { // Optional: specify variant selections variantId: 'variant_id_here', optionId: 'option_id_here', }); // Update line item quantity await commerce.cart.update(lineItemId, {quantity: 3}); // Remove a line item await commerce.cart.remove(lineItemId); // Empty the cart await commerce.cart.empty(); // Refresh the cart const refreshedCart = await commerce.cart.refresh();使用 React Context 管理购物车状态:
// context/cart-context.tsx import {createContext, useContext, useState, useEffect} from 'react'; import Commerce from '@chec/commerce.js'; const commerce = new Commerce(process.env.NEXT_PUBLIC_CHEC_PUBLIC_KEY!); const CartContext = createContext<any>(null); export function CartProvider({children}: {children: React.ReactNode}) { const [cart, setCart] = useState<any>(null); useEffect(() => { commerce.cart.retrieve().then(setCart); }, []); const addToCart = async (productId: string, quantity = 1) => { const {cart: updated} = await commerce.cart.add(productId, quantity); setCart(updated); }; const removeFromCart = async (lineItemId: string) => { const {cart: updated} = await commerce.cart.remove(lineItemId); setCart(updated); }; return ( <CartContext.Provider value={{cart, addToCart, removeFromCart}}> {children} </CartContext.Provider> ); } export const useCart = () => useContext(CartContext); -
生成结账令牌并捕获订单
// Step 1: Generate a checkout token from the cart const checkoutToken = await commerce.checkout.generateToken(cart.id, {type: 'cart'}); // Step 2: Get live checkout information (shipping options, taxes) const checkoutData = await commerce.checkout.getLive(checkoutToken.id); // Step 3: Capture the order const order = await commerce.checkout.capture(checkoutToken.id, { customer: { firstname: 'Jane', lastname: 'Doe', email: '[email protected]', }, shipping: { name: 'Jane Doe', street: '123 Main Street', town_city: 'San Francisco', county_state: 'US-CA', postal_zip_code: '94103', country: 'US', }, fulfillment: { shipping_method: checkoutData.shipping.available_options[0].id, }, payment: { gateway: 'stripe', stripe: { payment_method_id: stripePaymentMethodId, // from Stripe.js }, }, }); console.log(`Order #${order.customer_reference} placed!`); // Clear the cart after successful order await commerce.cart.refresh(); -
处理产品变体
const product = await commerce.products.retrieve(productId); // Variants are organized as variant groups (e.g., "Size") with options (e.g., "S", "M", "L") product.variants.forEach(variantGroup => { console.log(`Variant group: ${variantGroup.name}`); variantGroup.options.forEach(option => { console.log(` - ${option.name}: +${option.price.formatted_with_symbol}`); }); }); // When adding to cart, provide the full variant selection const selections = { [sizeVariantGroupId]: selectedSizeOptionId, [colorVariantGroupId]: selectedColorOptionId, }; await commerce.cart.add(product.id, 1, selections); -
列出并显示订单
// Requires a customer JWT (obtained via commerce.customer.login) const customerToken = await commerce.customer.login(email, password); const {data: orders} = await commerce.orders.getAllForCustomer({ customer_token: customerToken.token, }); orders.forEach(order => { console.log({ reference: order.customer_reference, status: order.status, total: order.order_value.formatted_with_symbol, items: order.order.line_items.map(item => item.product_name), }); });
示例
完整的 Next.js 产品页面
// app/products/[permalink]/page.tsx
import Commerce from '@chec/commerce.js';
import DOMPurify from 'isomorphic-dompurify';
// Server-side: use secret key to fetch product at build time
const commerce = new Commerce(process.env.CHEC_SECRET_KEY!);
export async function generateStaticParams() {
const {data: products} = await commerce.products.list({limit: 200});
return products.map((p: any) => ({permalink: p.permalink}));
}
export default async function ProductPage({params}: {params: {permalink: string}}) {
const product = await commerce.products.retrieve(params.permalink, {type: 'permalink'});
// Sanitize HTML from Chec CMS before rendering
const safeDescription = DOMPurify.sanitize(product.description ?? '');
return (
<main>
<img src={product.image?.url} alt={product.name} />
<h1>{product.name}</h1>
{/* Sanitized HTML rendered safely */}
<div dangerouslySetInnerHTML={{__html: safeDescription}} />
<p className="text-xl font-bold">{product.price.formatted_with_symbol}</p>
</main>
);
}
购物车商品数量徽标
export function CartBadge() {
const {cart} = useCart();
const totalItems = cart?.total_unique_items ?? 0;
return (
<button className="relative">
<ShoppingCartIcon />
{totalItems > 0 && (
<span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
{totalItems}
</span>
)}
</button>
);
}
最佳实践
- 在客户端使用公开密钥,在服务器上使用密钥——公开密钥只能读取产品和管理购物车;密钥还可以管理订单,因此不适合暴露在浏览器代码中
- 只初始化一次
Commerce客户端——在模块级常量中创建单个实例并导入;避免在每次渲染时创建新实例 - 处理用于订单自动化的 Webhook 事件——在控制面板中配置 Chec Webhook,以便在下单、更新订单或退款时向服务器发送 POST 请求
- 结账时使用结账令牌,而不是购物车 ID——
generateToken会创建有时限且专用于结账的令牌;始终将此令牌传给capture,切勿传入原始购物车 ID - 订单捕获后刷新购物车——成功下单后调用
commerce.cart.refresh()会创建一个新的空购物车;旧购物车 ID 将失效 - 渲染前清理产品描述中的 HTML——产品描述是来自 Chec CMS 的 HTML;渲染前务必使用 DOMPurify(
isomorphic-dompurify可同时用于 Node.js 和浏览器环境)进行处理 - 为结账捕获实现错误处理——使用 try/catch 包裹
commerce.checkout.capture,并将commerce.error代码映射为用户易懂的提示信息
常见问题
| 问题 | 解决方案 |
|---|---|
Commerce is not a constructor | 确保使用 import Commerce from '@chec/commerce.js' 导入(默认导入,而非命名导入) |
| 页面加载后购物车未保留 | Commerce.js 会将购物车存储在 localStorage 的 chec_cart_id 中;请确保 localStorage 可用且未被阻止 |
| 结账令牌已过期 | 结账令牌会在 7 天后过期;开始结账前,请立即从购物车生成新令牌 |
| 未应用变体选项 | 将选项作为第三个参数传递给 commerce.cart.add():commerce.cart.add(id, qty, {variantGroupId: optionId}) |
| 未配置支付网关 | 在 Chec Dashboard 的 Setup → Payment gateways 下启用并配置支付网关(Stripe、Square 等) |
相关技能
- @jamstack-storefront
- @shopify-hydrogen
- @secure-checkout
- @webhook-architecture