Packages

@prood/commerce

Server-only data layer wrapping the commerce adapter, payment providers, storage, and caching.

@prood/commerce is the server-only data layer used by apps/api. It wraps the configured CommerceAdapter, instantiates payment and storage providers, handles tenant scoping, and manages Next.js cache tags.

Installation

Workspace package. Available from the source repo for now.

This package is "server-only" — it cannot be imported in client components.

Usage

Only apps/api should import @prood/commerce directly. Other apps use @prood/api-client.

ts
import {
  getProducts,
  createCart,
  placeOrder,
  getAdmin,
  withTenant,
  getTenantPaymentProvider,
  verifyPaymentWebhook,
} from '@prood/commerce'

Catalog functions

ts
// Cached with per-tenant tags
const products = await getProducts(orgId, { query: 'shirt', page: 1, limit: 20 })
const product = await getProduct(orgId, 'prod_abc')
const categories = await getCategories(orgId)
const store = await getStoreInfo(orgId)

Cache tags: products-{orgId}, categories-{orgId}, store-{orgId}.

Cart functions

ts
const cart = await createCart(orgId)
const updated = await addCartItem(orgId, cartId, { productId, variantId, quantity: 1 })
await applyCartCoupon(orgId, cartId, 'SAVE10') // API and MCP only: stores have no coupon field
const order = await placeOrder(orgId, cartId)

Admin functions

ts
const admin = getAdmin(orgId)
const products = await admin.listProducts({ page: 1 })
const created = await admin.createProduct(input)
await admin.fulfillOrder(orderId, { trackingNumber: 'TRK123' })
await admin.recordRefund(orderId, 'Customer request') // records a refund already sent; moves no money
const stats = await admin.getDashboardStats()

Payment providers

ts
// Build provider with the tenant's stored config
const { provider } = await getTenantPaymentProvider(orgId, 'stripe')

// Verify webhook with tenant's stored secret
const result = await verifyPaymentWebhook(payload, signature, 'stripe', orgId)

The provider factory reads from the encrypted integration_config table. There are no environment-level provider defaults: a store with no stored credentials has no provider.

Storage

ts
import { uploadForTenant, tenantStorageDirectory } from '@prood/commerce'

const result = await uploadForTenant(orgId, {
  file: buffer,
  filename: 'product-image.jpg',
  contentType: 'image/jpeg',
  directory: tenantStorageDirectory(orgId, 'products', productId),
})
// result.url → CDN URL
// result.key → org/{orgId}/products/{productId}/...

Encryption

Integration credentials are encrypted at rest:

ts
import { encryptConfig, decryptConfig } from '@prood/commerce'

const encrypted = encryptConfig({ secretKey: 'sk_live_...' })
// → { secretKey: 'enc:v1:...' }

const decrypted = decryptConfig(encrypted)
// → { secretKey: 'sk_live_...' }

Uses AES-256-GCM with INTEGRATION_ENCRYPTION_KEY. The key is required.

Tenant scoping

All functions accept orgId as the first parameter and call withTenant() internally:

ts
export async function getProducts(orgId: string, params: SearchParams) {
  return withTenant(orgId, () => adapter.getProducts(params))
}

Configuration

Read via getCommerceConfig():

SettingEnv varDefault
DatabaseDATABASE_URLNone. Required

Module structure

Drag to pan · Scroll to zoom