Skip to content

Products

Feature deep dive. Service identity, components, and async surface live in Commerce Overview and API Events.

Products are the core catalog entity. Each product has associated ProductInfo (multilingual name/description), ProductIdentifier records, and connections to sale channels via SaleChannelProduct.

Overview

PropertyValue
EntitiesProduct, ProductInfo, ProductIdentifier, SaleChannelProduct, default ProductVariant
Base path/products (live spec: /v1/api/commerce/doc/openapi.json)
Aggregatecreate/update product + children in one transaction
AsyncEventBus product.aggregate.{created,updated} → optional multi-merchant sync; CDC public.Product → search/taxation

Entity Model

See Domain Model §3.1. Field tables are maintained there; this page covers operations.

REST Endpoints

Full reference: live OpenAPI at /v1/api/commerce/doc/openapi.json. The custom routes worth calling out:

MethodPathPurpose
POST/products/aggregateCreate product + ProductInfo + identifiers + sale channel links + default variant in one transaction
PATCH/products/{id}/aggregateUpdate product + ProductInfo + sale channel links atomically

Identifier resolution

GET /products/{id} resolves the {id} parameter by:

  1. Direct ID match
  2. slug field match

This is handled by ProductService.findByIdentifier().

Merchant-scoped access

For non-admin users, find() and count() filter products by the merchant IDs the user has access to (via PolicyDefinitionRepository Casbin policies). Admin/super-admin users see all products.

ProductService

File: src/services/product.service.ts

Methods

MethodPurpose
findByIdentifier({ identifier, filter?, transaction? })Resolve by ID or slug
updateById({ id, data, transaction? })Update with transaction support
batchCreate({ data })Batch insert products
createAggregate({ data, transaction? })Create product + info + identifiers + sale channels + default variant
updateAggregate({ id, data, transaction? })Update product + info + sale channels

createAggregate() request

typescript
{
  // ProductInsertSchema fields
  name: string;
  status?: string;            // default: 'activated'
  categoryId?: string;
  merchantId: string;
  taxGroupId?: string;
  // ... other product fields

  // Aggregate-specific
  info: ProductInfoInsertSchema;   // required — multilingual name/description
  saleChannelIds?: string[];       // optional — link to sale channels
  slug?: string;                   // optional — auto-generated if omitted
}

What it does:

  1. Creates the Product row
  2. Creates ProductInfo (name, description in { en, vi })
  3. Creates a ProductIdentifier with scheme SYSTEM
  4. Creates SaleChannelProduct rows for each sale channel ID
  5. Creates a default ProductVariant via the variant sync path
  6. After commit, emits product.aggregate.created on the in-process EventBus (marks merchant onboarding PRODUCT step; enqueues multi-merchant sync if merchantIds[] present)

updateAggregate() request

typescript
{
  // ProductUpdateSchema fields (partial)
  info?: ProductInfoUpdateSchema;
  saleChannelIds?: string[];
}

After commit, emits product.aggregate.updated on the in-process EventBus (enqueues multi-merchant sync if applicable). Downstream propagation to search/taxation happens via CDC on public.Product, not an application-level Kafka emit — see API Events.

Status Transitions

ACTIVATED ←→ DEACTIVATED
    ↓              ↓
  ARCHIVED     ARCHIVED
FromAllowed targets
activateddeactivated, archived
deactivatedactivated, archived
archived(none — terminal)

Archived products cannot be modified via updateById().

Deletion

Product deletion is handled by DeletionPolicyService.deleteProductById():

  • If the product has sale history (orders), deletion is blocked (HTTP 400) — archive instead
  • If the product has variants and cascadeProductDeletion is false in the merchant's deletion policy → HTTP 400
  • If cascadeProductDeletion is true, variants are deleted first (subject to their own constraints)

Events

ChannelSignalWhenConsumers
EventBus (in-process)product.aggregate.created / .updatedafter aggregate commitonboarding-step marking + multi-merchant sync enqueue
CDC (Debezium)public.Product, public.ProductInfoon row writeSearch (index), Taxation (tax-group provisioning)

Commerce does not application-emit product.created/product.updated Kafka topics — the Kafka component is producer-only and unused for these. See API Events.

PageDescription
Commerce OverviewService identity + catalog
Domain ModelProduct field tables + invariants
API EventsCDC + EventBus + BullMQ surface
VariantsProduct variant management
CategoriesCategory hierarchy
Sale ChannelsChannel distribution

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.