Core
The @commercejs/core package is the Commerce.js orchestration engine. It wires adapters, payment providers, notifications, analytics, events, and webhooks into a single createCommerce() entry point. It also provides orchestrator factories for multi-adapter composition.
Installation
pnpm add @commercejs/core @commercejs/types
Quick Start
import { createCommerce } from '@commercejs/core'
import { SallaAdapter } from '@commercejs/adapter-salla'
import { TapPaymentProvider } from '@commercejs/payment-tap'
const commerce = createCommerce({
adapter: new SallaAdapter({ token: '...' }),
payments: {
tap: new TapPaymentProvider({ secretKey: '...' }),
},
defaultPayment: 'tap',
})
// Capability-checked catalog call
const products = await commerce.getProducts({ query: 'shirt' })
// Payment via registered provider
const session = await commerce.createPayment({
amount: 99.99,
currency: 'SAR',
})
Configuration
The CommerceConfig accepts these options:
| Option | Type | Required | Description |
|---|---|---|---|
adapter | CommerceAdapter | Yes | Platform adapter instance (Salla, Medusa, etc.) |
payments | Record<string, PaymentProvider> | No | Payment providers keyed by ID |
defaultPayment | string | No | Default payment provider ID |
webhooks | WebhookEndpoint[] | No | Outbound webhook endpoints |
notifications | Record<string, NotificationProvider> | No | Notification providers keyed by ID |
notificationRules | NotificationRule[] | No | Event → channel → provider dispatch rules |
analytics | AnalyticsProvider[] | No | Analytics providers (all receive all events) |
fetch | typeof fetch | No | Custom fetch for webhooks (edge runtimes, testing) |
sign | (payload, secret) => string | No | HMAC signing for webhook payloads |
Capability Routing
Every domain method checks the adapter's capabilities array before calling through. If the adapter doesn't support a domain, a CommerceError with code NOT_SUPPORTED (HTTP 501) is thrown.
// Check before calling
if (commerce.hasCapability('wishlist')) {
const wishlist = await commerce.getWishlist()
}
// Or handle the error
try {
await commerce.getWishlist()
} catch (err) {
if (err.code === 'NOT_SUPPORTED') {
console.log('This adapter does not support wishlists')
}
}
Supported Domains
catalog · cart · checkout · orders · customers · wishlist · reviews · store · promotions · returns · brands · countries · locations · wholesale · auctions · rentals · gift-cards
Event Bus
Every state-changing operation emits a typed event. Subscribe to events for analytics, side effects, or plugin logic.
commerce.events.on('order.created', ({ order }) => {
sendConfirmationEmail(order)
})
commerce.events.on('payment.confirmed', ({ session }) => {
updateInventory(session)
})
// Wildcard — listen to everything
commerce.events.onAny((event, data) => {
analytics.track(event, data)
})
// One-time listener
commerce.events.once('cart.created', ({ cart }) => {
console.log('First cart created:', cart.id)
})
Event Catalog
| Event | Payload |
|---|---|
product.viewed | { product } |
cart.created | { cart } |
cart.item.added | { cart, productId, quantity } |
cart.item.updated | { cart, itemId, quantity } |
cart.item.removed | { cart, itemId } |
order.created | { order } |
order.cancelled | { order } |
payment.created | { session } |
payment.confirmed | { session } |
payment.failed | { session, error? } |
payment.refunded | { session, amount } |
customer.logged_in | { customer } |
customer.registered | { customer } |
customer.updated | { customer } |
checkout.started | { cartId } |
checkout.completed | { order, session } |
return.created | { returnRequest } |
return.cancelled | { returnRequest } |
Multi-Provider Payments
Register multiple payment providers and select one per transaction:
const commerce = createCommerce({
adapter,
payments: {
tap: new TapPaymentProvider({ secretKey: '...' }),
stripe: new StripePaymentProvider({ secretKey: '...' }),
},
defaultPayment: 'tap',
})
// Use default provider
await commerce.createPayment({ amount: 50, currency: 'SAR' })
// Override per-call
await commerce.createPayment({ amount: 50, currency: 'USD' }, 'stripe')
// Confirm and refund
await commerce.confirmPayment('sess_123', 'tap')
await commerce.refundPayment({ sessionId: 'sess_123', amount: 25 }, 'tap')
Webhook Dispatcher
Register external endpoints to receive commerce events as HTTP POST requests with JSON payloads, retry logic, and optional HMAC signing.
const commerce = createCommerce({
adapter,
webhooks: [
{
id: 'crm',
url: 'https://crm.example.com/webhook',
events: ['order.created', 'customer.registered'],
secret: 'whsec_...',
},
{
id: 'analytics',
url: 'https://analytics.example.com/hook',
events: ['*'], // Wildcard — all events
},
],
sign: async (payload, secret) => {
const encoder = new TextEncoder()
const key = await crypto.subtle.importKey(
'raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'],
)
const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(payload))
return btoa(String.fromCharCode(...new Uint8Array(sig)))
},
})
Webhook Delivery
Each webhook POST includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Commerce-Event | Event name (e.g., order.created) |
X-Commerce-Delivery | Unique delivery ID |
X-Commerce-Signature | HMAC signature (if sign and secret are set) |
Failed deliveries are retried with exponential backoff (1s → 2s → 4s). 4xx responses are not retried.
Cleanup
Call destroy() to remove all event listeners and webhook subscriptions:
commerce.destroy()
Orchestrator Factories
The core package exports three factory functions for composing domains from multiple adapter sources.
createOrchestrator()
Creates a CommerceOrchestrator from universal domains and an optional domain map:
import { createOrchestrator } from '@commercejs/core'
const orchestrator = createOrchestrator({
name: 'my-store',
catalog: myCatalogAdapter,
store: myStoreAdapter,
domains: {
cart: myCartAdapter,
orders: myOrderAdapter,
},
})
orchestrator.supports('cart') // true
orchestrator.supports('wishlist') // false
orchestrator.domain('cart') // CartAdapter instance
createCompositeOrchestrator()
Composes domains from multiple adapter sources into a single orchestrator:
import { createCompositeOrchestrator } from '@commercejs/core'
const orchestrator = createCompositeOrchestrator({
name: 'hybrid-store',
providers: {
catalog: shopifyAdapter,
store: shopifyAdapter,
cart: platformAdapter,
customers: crmAdapter,
},
})
withPlatformFallback()
Wraps an orchestrator with a fallback that fills missing domains:
import { withPlatformFallback } from '@commercejs/core'
// sallaAdapter supports catalog + store
// platformAdapter fills in cart, checkout, orders, customers...
const orchestrator = withPlatformFallback(sallaAdapter, platformAdapter)
orchestrator.supports('cart') // true (from platformAdapter)
Universal domains (catalog, store) always come from the primary orchestrator.
Notification Providers
Notification providers subscribe to commerce events and auto-dispatch through configured channels:
import type { NotificationProvider } from '@commercejs/types'
const resend: NotificationProvider = {
id: 'resend',
name: 'Resend',
channels: ['email'],
send: async (channel, message) => {
await resendClient.emails.send({
from: 'store@example.com',
to: message.to,
subject: message.subject,
html: message.html,
})
return { success: true }
},
}
const commerce = createCommerce({
adapter,
notifications: { resend },
notificationRules: [
{
event: 'order.created',
channel: 'email',
provider: 'resend',
template: 'order_confirmation',
buildMessage: (payload) => ({
to: payload.order.customer.email,
subject: `Order #${payload.order.id} confirmed`,
data: { order: payload.order },
}),
},
],
})
Notification failures are non-fatal — they're caught silently to avoid disrupting commerce operations.
Analytics Auto-Tracking
Analytics providers automatically receive all commerce events:
import type { AnalyticsProvider } from '@commercejs/types'
const ga4: AnalyticsProvider = {
id: 'ga4',
name: 'Google Analytics 4',
track: (event, properties) => gtag('event', event, properties),
identify: (userId, traits) => gtag('set', 'user_properties', { user_id: userId, ...traits }),
page: (name, properties) => gtag('event', 'page_view', { page_title: name, ...properties }),
}
const commerce = createCommerce({
adapter,
analytics: [ga4],
})
// ga4.track() is called automatically for every commerce event
// (order.created, cart.item.added, payment.confirmed, etc.)
Analytics failures are non-fatal — they never block commerce operations.