Skip to content

Product Variants

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

A ProductVariant represents a specific sellable configuration of a product (e.g. size, color). Each variant has its own ProductInfo, identifiers (SKU, barcode), optional pricing data, and MetaLinks (images).

Overview

PropertyValue
EntitiesProductVariant, ProductInfo, ProductIdentifier, MetaLink
Base path/product-variants (live spec: /v1/api/commerce/doc/openapi.json)
Structural typeProductVariant.type — see Domain Model §4.1
Pricingcatalog stores pricing data; computation lives in @nx/pricing

Entity Model

See Domain Model §3.2. Field tables are maintained there.

REST Endpoints

Full reference: live OpenAPI at /v1/api/commerce/doc/openapi.json. Custom routes:

MethodPathPurpose
POST/product-variants/aggregateCreate variant + info + identifiers + MetaLinks
PATCH/product-variants/{id}/aggregateUpdate variant + info + identifiers atomically

Identifier resolution

GET /product-variants/{id} resolves by:

  1. Direct ID match
  2. slug field match

ProductVariantService

File: src/services/product-variant.service.ts

Methods

MethodPurpose
findByIdentifier({ identifier, filter?, transaction? })Resolve by ID or slug
createAggregate({ data, transaction? })Create variant + info + identifiers + MetaLinks
updateByIdProductVariantAggregate({ id, data, transaction? })Update variant + info + identifiers

createAggregate() request

typescript
{
  // ProductVariantInsertSchema fields
  productId: string;
  status?: string;

  // Aggregate-specific
  info: ProductInfoInsertSchema;          // required — { name: { en, vi }, description: { en, vi } }
  pricing?: PricingSchema;                // optional — variant-level pricing
  metaLinks?: MetaLinkUpdateSchema[];     // optional — images/assets (upserted by ID)
  barcode?: string;                       // optional — creates ProductIdentifier (scheme: BARCODE)
  sku?: string;                           // optional — creates ProductIdentifier (scheme: SKU)
}

What it does:

  1. Creates the ProductVariant row with metadata { merchantId, categoryId } from parent Product
  2. Creates ProductInfo for the variant
  3. Creates ProductIdentifier with scheme SYSTEM (auto-generated)
  4. If sku provided: creates ProductIdentifier with scheme SKU
  5. If barcode provided: creates ProductIdentifier with scheme BARCODE
  6. Upserts MetaLink records if provided

updateByIdProductVariantAggregate() request

typescript
{
  // ProductVariantUpdateSchema fields (partial)
  info?: ProductInfoInsertSchema;     // partial update
  pricing?: PricingSchema;
  barcode?: string;                   // upserts BARCODE identifier
  sku?: string;                       // upserts SKU identifier
}

Events

ChannelSignalWhenConsumers
CDC (Debezium)public.ProductVarianton row writeInventory (seed InventoryItem for STOCKABLE_SET types), Pricing (fare init from pricing data), Search (index)

Commerce does not application-emit product-variant.created/.updated Kafka topics — the variant-update Kafka enqueue (_enqueueProductVariantUpdated) is commented out in product-variant.service.ts. Propagation is CDC-driven. See API Events.

Deletion

Handled by DeletionPolicyService.deleteProductVariantById():

ConditionBehavior
Variant has sale history (orders)HTTP 400 — archive instead
Last variant of the product + canDeleteLastVariant=falseHTTP 409
OtherwiseDeleted

There is no generateCombinations(), cartesianProduct(), or automatic variant generation endpoint. Variants are created one at a time via the aggregate endpoint.

PageDescription
Commerce OverviewService identity + catalog
Domain ModelVariant field tables + ProductVariantTypes
ProductsParent product management
Pricing in CommerceVariant pricing data flow
ADR-0003Variant type discriminator model

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