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
| Property | Value |
|---|---|
| Entities | Product, ProductInfo, ProductIdentifier, SaleChannelProduct, default ProductVariant |
| Base path | /products (live spec: /v1/api/commerce/doc/openapi.json) |
| Aggregate | create/update product + children in one transaction |
| Async | EventBus 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:
| Method | Path | Purpose |
|---|---|---|
POST | /products/aggregate | Create product + ProductInfo + identifiers + sale channel links + default variant in one transaction |
PATCH | /products/{id}/aggregate | Update product + ProductInfo + sale channel links atomically |
Identifier resolution
GET /products/{id} resolves the {id} parameter by:
- Direct ID match
slugfield 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
| Method | Purpose |
|---|---|
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
{
// 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:
- Creates the
Productrow - Creates
ProductInfo(name, description in{ en, vi }) - Creates a
ProductIdentifierwith schemeSYSTEM - Creates
SaleChannelProductrows for each sale channel ID - Creates a default
ProductVariantvia the variant sync path - After commit, emits
product.aggregate.createdon the in-process EventBus (marks merchant onboardingPRODUCTstep; enqueues multi-merchant sync ifmerchantIds[]present)
updateAggregate() request
{
// 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| From | Allowed targets |
|---|---|
activated | deactivated, archived |
deactivated | activated, 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
cascadeProductDeletionisfalsein the merchant's deletion policy → HTTP 400 - If
cascadeProductDeletionistrue, variants are deleted first (subject to their own constraints)
Events
| Channel | Signal | When | Consumers |
|---|---|---|---|
| EventBus (in-process) | product.aggregate.created / .updated | after aggregate commit | onboarding-step marking + multi-merchant sync enqueue |
| CDC (Debezium) | public.Product, public.ProductInfo | on row write | Search (index), Taxation (tax-group provisioning) |
Commerce does not application-emit
product.created/product.updatedKafka topics — the Kafka component is producer-only and unused for these. See API Events.
Related Pages
| Page | Description |
|---|---|
| Commerce Overview | Service identity + catalog |
| Domain Model | Product field tables + invariants |
| API Events | CDC + EventBus + BullMQ surface |
| Variants | Product variant management |
| Categories | Category hierarchy |
| Sale Channels | Channel distribution |