@trudevlabs/storekit
v1.0.0
Published
Framework-agnostic e-commerce core: cart, pricing, promotions, checkout flow
Maintainers
Readme
@trudevlabs/storekit
Framework-agnostic e-commerce core: cart, pricing, promotions, checkout flow
Works with any frontend (React, Vue, Angular) and any backend (Node, Express, WordPress, Shopify, custom REST). No framework dependencies. All amounts in cents (integer) for precision.
Node < 18:
createRestProviderrequiresfetch. Passfetchin options or usenode-fetch.
Install
npm install @trudevlabs/storekitQuick Start
import { createCart, addItem, getCartSummary, createCheckout } from '@trudevlabs/storekit';
const cart = createCart({ currency: 'USD' });
addItem(cart, { id: 'prod123', name: 'Widget', price: 2999, qty: 2 });
const summary = getCartSummary(cart);
console.log(summary.total); // 5998 (cents)
const checkout = createCheckout(cart);
checkout.setShippingAddress({ city: 'NYC', country: 'US' });
checkout.setPaymentMethod('stripe');
const order = checkout.toOrder();Cart
const { createCart, addItem, removeItem, updateQuantity, clearCart } = require('@trudevlabs/storekit');
const cart = createCart({ currency: 'USD' });
// Add items - price in cents (2999 = $29.99). Use priceInDollars: true for dollars
addItem(cart, { id: 'prod123', name: 'Widget', price: 2999, qty: 2 });
addItem(cart, { id: 'prod456', price: 1499 });
// Update
updateQuantity(cart, 'prod123', 1);
// Remove
removeItem(cart, 'prod456');
// Clear
clearCart(cart);Pricing & Summary
const { getCartSummary, formatAmount } = require('@trudevlabs/storekit');
const summary = getCartSummary(cart, {
taxRate: 0.08,
shippingFlatRate: 599,
shippingFreeAbove: 5000,
});
// summary: { subtotal, discount, tax, shipping, total, items, currency }
console.log(formatAmount(summary.total)); // "$64.99"Promotions
const { createPromo, applyPromo, removePromo, PROMO_TYPES } = require('@trudevlabs/storekit');
// Create promo codes
const SAVE10 = createPromo({
code: 'SAVE10',
type: PROMO_TYPES.PERCENT,
value: 10,
minPurchase: 2000,
maxDiscount: 5000,
});
const FLAT5 = createPromo({
code: 'FLAT5',
type: PROMO_TYPES.FIXED,
value: 500,
});
const registry = { SAVE10, FLAT5 };
// Apply
applyPromo(cart, 'SAVE10', registry);
applyPromo(cart, SAVE10, registry);
// Remove
removePromo(cart, 'SAVE10');Promo Types
| Type | Options | Description |
|------|---------|-------------|
| percent | value, minPurchase, maxDiscount | % off |
| fixed | value | Fixed amount off |
| bogo | buyQty, getQty | Buy X get Y free |
| free_shipping | - | Waives shipping |
Checkout Flow
const { createCheckout, CHECKOUT_STEPS } = require('@trudevlabs/storekit');
const checkout = createCheckout(cart, {
steps: [CHECKOUT_STEPS.CART, CHECKOUT_STEPS.SHIPPING, CHECKOUT_STEPS.PAYMENT, CHECKOUT_STEPS.REVIEW],
});
// Navigation
checkout.getStep(); // 'cart'
checkout.next(); // → shipping
checkout.prev(); // → cart
checkout.goToStep(2); // → payment
// Data
checkout.setShippingAddress({ street: '123 Main', city: 'NYC', zip: '10001', country: 'US' });
checkout.setBillingAddress({ ... });
checkout.setPaymentMethod('stripe');
// Summary (includes promos, tax, shipping)
const summary = checkout.getSummary({
taxRate: 0.08,
shippingFlatRate: 599,
});
// Create order
const order = checkout.toOrder('ord_123');
// { id, cartId, items, subtotal, discount, tax, shipping, total, shippingAddress, ... }Storage – persist cart (browser, Node)
const { createLocalStorageAdapter, createCommerce, loadCart } = require('@trudevlabs/storekit');
// localStorage (browser)
const storage = createLocalStorageAdapter('my_cart');
// Or custom adapter (React Native, AsyncStorage, etc.)
const customStorage = {
get: () => AsyncStorage.getItem('cart').then(JSON.parse),
set: (data) => AsyncStorage.setItem('cart', JSON.stringify(data)),
remove: () => AsyncStorage.removeItem('cart'),
};
const session = createCommerce({ storage });
await session.loadFromStorage(); // restore (sync or async storage)
await session.addItem({ id: 'p1', name: 'Widget', price: 2999 });Provider – connect to your backend
const { createRestProvider, createCommerce } = require('@trudevlabs/storekit');
// Custom REST API (Node, Express, etc.)
const provider = createRestProvider('https://api.yoursite.com', {
headers: { Authorization: 'Bearer token' },
});
const session = createCommerce({ provider, storage });
await session.loadFromProvider(cartId); // load cart from backend
await session.addItem({ id: 'prod1', price: 2999 }); // syncs to backendProvider API contract
Your backend should expose:
| Endpoint | Method | Purpose |
|----------|--------|---------|
| /products | GET | List products |
| /products/:id | GET | Single product |
| /carts | GET/POST | Get/create cart |
| /carts/:id | PUT | Update cart |
| /orders | POST | Create order |
| /promos | GET | Promo codes |
| /shipping/rates | POST | Shipping rates |
Backend Examples
Node.js / Express
Backend – Express API that @trudevlabs/storekit calls:
// server.js
const express = require('express');
const app = express();
app.use(express.json());
const carts = new Map(); // or use a database
app.get('/api/products', (req, res) => {
// Return products from DB: { id, name, price (cents), sku, images }
res.json([{ id: '1', name: 'Widget', price: 2999, sku: 'WID-01', images: [] }]);
});
app.get('/api/carts/:id', (req, res) => {
const cart = carts.get(req.params.id);
res.json(cart ? { id: cart.id, items: cart.items, currency: 'USD', promoCodes: cart.promoCodes } : { items: [] });
});
app.post('/api/carts', (req, res) => {
const cart = { id: `cart_${Date.now()}`, ...req.body, items: req.body.items || [], promoCodes: req.body.promoCodes || [] };
carts.set(cart.id, cart);
res.json(cart);
});
app.put('/api/carts/:id', (req, res) => {
carts.set(req.params.id, req.body);
res.json(req.body);
});
app.post('/api/orders', (req, res) => {
const order = { id: `ord_${Date.now()}`, ...req.body, createdAt: new Date().toISOString() };
// Save to DB, process payment, etc.
res.json(order);
});
app.listen(3000);Frontend – connect with createRestProvider:
const { createRestProvider, createCommerce, createLocalStorageAdapter } = require('@trudevlabs/storekit');
const provider = createRestProvider('http://localhost:3000/api');
const storage = createLocalStorageAdapter('cart');
const session = createCommerce({ provider, storage });
// Load products, add to cart
const products = await provider.getProducts();
await session.addItem({ id: products[0].id, name: products[0].name, price: products[0].price });Custom REST API
Use createRestProvider with any API that matches the contract:
const { createRestProvider, createCommerce } = require('@trudevlabs/storekit');
const provider = createRestProvider('https://api.yoursite.com/v1', {
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'X-Store-Id': 'store_123',
},
});
const session = createCommerce({ provider });
// Fetch products
const products = await provider.getProducts({ category: 'electronics', page: 1 });
const product = await provider.getProduct('prod_abc');
// Cart syncs to backend automatically
await session.addItem({ id: product.id, name: product.name, price: product.price, qty: 1 });
await session.loadFromProvider(session.cart.id);
// Create order
const checkout = require('@trudevlabs/storekit').createCheckout(session.cart);
checkout.setShippingAddress({ city: 'NYC', country: 'US', zip: '10001' });
const order = checkout.toOrder();
const created = await provider.createOrder(order);WooCommerce
WooCommerce uses a different API shape. Create a provider adapter:
const { createProvider, normalizeProduct, createCommerce } = require('@trudevlabs/storekit');
const WOO_SITE = 'https://yoursite.com';
const WOO_KEY = 'ck_xxx'; // Consumer key
const WOO_SECRET = 'cs_xxx'; // Consumer secret
const auth = Buffer.from(`${WOO_KEY}:${WOO_SECRET}`).toString('base64');
async function request(path, options = {}) {
const res = await fetch(`${WOO_SITE}/wp-json/wc/v3${path}`, {
...options,
headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/json', ...options.headers },
});
if (!res.ok) throw new Error(`WooCommerce: ${res.status}`);
return res.json();
}
const woocommerceProvider = createProvider({
getProducts: async (query = {}) => {
const params = new URLSearchParams(query).toString();
const data = await request(`/products${params ? '?' + params : ''}`);
return Array.isArray(data) ? data.map((p) => normalizeProduct({
id: String(p.id),
name: p.name,
price: parseFloat(p.price) * 100,
sku: p.sku,
images: p.images?.map((i) => i.src) || [],
})) : [];
},
getProduct: async (id) => {
const p = await request(`/products/${id}`);
return normalizeProduct({ id: String(p.id), name: p.name, price: parseFloat(p.price) * 100, sku: p.sku, images: p.images?.map((i) => i.src) || [] });
},
getCart: async (cartId) => null, // WooCommerce uses session cookies; implement if using headless
saveCart: async (cart) => cart, // Persist client-side or via custom endpoint
createOrder: async (order) => {
const payload = {
line_items: order.items.map((i) => ({ product_id: parseInt(i.id), quantity: i.qty })),
shipping: order.shippingAddress ? { address_1: order.shippingAddress.street, city: order.shippingAddress.city, state: order.shippingAddress.state, postcode: order.shippingAddress.zip, country: order.shippingAddress.country } : {},
};
return request('/orders', { method: 'POST', body: JSON.stringify(payload) });
},
});
const session = createCommerce({ provider: woocommerceProvider });
const products = await woocommerceProvider.getProducts({ per_page: 10 });
await session.addItem({ id: products[0].id, name: products[0].name, price: products[0].price });Shopify
Use Shopify Storefront API (GraphQL) or REST. Example with REST Admin API:
const { createProvider, normalizeProduct, createCommerce } = require('@trudevlabs/storekit');
const SHOP = 'your-store.myshopify.com';
const TOKEN = 'shpat_xxx'; // Storefront or Admin API token
async function shopifyRequest(path, options = {}) {
const url = `https://${SHOP}/admin/api/2024-01${path}`;
const res = await fetch(url, {
...options,
headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', ...options.headers },
});
if (!res.ok) throw new Error(`Shopify: ${res.status}`);
return res.json();
}
const shopifyProvider = createProvider({
getProducts: async (query = {}) => {
const limit = query.limit || 10;
const data = await shopifyRequest(`/products.json?limit=${limit}`);
const products = data.products || [];
return products.map((p) => normalizeProduct({
id: String(p.id),
name: p.title,
price: Math.round(parseFloat(p.variants?.[0]?.price || 0) * 100),
sku: p.variants?.[0]?.sku,
images: p.images?.map((i) => i.src) || [],
}));
},
getProduct: async (id) => {
const data = await shopifyRequest(`/products/${id}.json`);
const p = data.product;
return normalizeProduct({
id: String(p.id),
name: p.title,
price: Math.round(parseFloat(p.variants?.[0]?.price || 0) * 100),
sku: p.variants?.[0]?.sku,
images: p.images?.map((i) => i.src) || [],
});
},
getCart: async () => null,
saveCart: async (cart) => cart,
createOrder: async (order) => {
// Shopify: create draft order or use Checkout API
const lineItems = order.items.map((i) => ({ variant_id: parseInt(i.id), quantity: i.qty }));
const data = await shopifyRequest('/draft_orders.json', {
method: 'POST',
body: JSON.stringify({ draft_order: { line_items: lineItems } }),
});
return data.draft_order;
},
});
const session = createCommerce({ provider: shopifyProvider });
const products = await shopifyProvider.getProducts({ limit: 20 });
await session.addItem({ id: products[0].id, name: products[0].name, price: products[0].price, qty: 1 });Shopify Storefront API: For public storefronts, use the Storefront API (
/storefront-api) with a Storefront access token and adapt the GraphQL queries to fetch products and create checkouts.
Events – lifecycle hooks
const { on, createCommerce } = require('@trudevlabs/storekit');
on('cart:itemAdded', ({ cart, item }) => console.log('Added', item.name));
on('cart:itemRemoved', ({ itemId }) => {});
on('cart:updated', () => {});
on('cart:synced', ({ cart }) => {});
on('checkout:stepChange', ({ step, index }) => {});
on('checkout:completed', ({ order }) => {});
on('order:created', ({ order }) => analytics.track('purchase', order));
// Note: on/off use a global emitter (shared across sessions). Use createEventEmitter() for isolated listeners.
const session = createCommerce({ storage });
await session.addItem({ id: 'p1', name: 'Widget', price: 2999 }); // fires cart:itemAddedFull Example (React/Vue/Angular + any backend)
const commerce = require('@trudevlabs/storekit');
// 1. Cart
const cart = commerce.createCart({ currency: 'USD' });
commerce.addItem(cart, { id: 'prod1', name: 'T-Shirt', price: 2500, qty: 2 });
commerce.addItem(cart, { id: 'prod2', name: 'Hat', price: 1500 });
// 2. Promo
const promo = commerce.createPromo({ code: 'SAVE10', type: 'percent', value: 10 });
commerce.applyPromo(cart, promo);
// 3. Summary
const summary = commerce.getCartSummary(cart, {
taxRate: 0.08,
shippingFlatRate: 599,
shippingFreeAbove: 10000,
});
console.log(commerce.formatAmount(summary.total));
// 4. Checkout
const checkout = commerce.createCheckout(cart);
checkout.setShippingAddress({ city: 'NYC', country: 'US' });
checkout.setPaymentMethod('stripe');
const order = checkout.toOrder();
// 5. Send order to your backend / Stripe / etc.
fetch('/api/orders', { method: 'POST', body: JSON.stringify(order) });Architecture
┌─────────────────────────────────────────────────────┐
│ React / Vue / Angular (any frontend) │
└─────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────┐
│ createCommerce({ storage, provider }) │
│ Events: cart:*, checkout:*, order:* │
└─────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
localStorage Custom REST Shopify/WooCommerce
sessionStorage (your API) (future adapters)API Reference
| Function | Description |
|----------|-------------|
| createCart(options) | Create empty cart (pure) |
| addItem(cart, item) | Add product. price in cents (2999 = $29.99). Use priceInDollars: true for dollars |
| createCommerce({ storage, provider }) | Integrated session with persist + sync |
| loadCart(storage) | Load cart from storage adapter |
| loadCartFromProvider(provider, cartId) | Load cart from backend |
| createLocalStorageAdapter(key) | Browser localStorage |
| createRestProvider(baseUrl, opts) | REST API adapter |
| on(event, handler) | Subscribe to events |
| createCheckout(cart, options) | Checkout flow |
| formatAmount(cents, currency) | Format for display |
License
MIT
