engineering docs

Commerce database

commerce menangani state belanja dan transaksi. Product/service hanya direferensikan melalui ID dan snapshot.

Aggregate transaksi

Order memiliki orderNumber unik dalam organization, status order/payment/fulfillment, totals, items, payments, invoices, dan shipments. Totals harus dihitung server-side dari line items, bukan dipercaya dari client.

Lifecycle

Cart -> Order draft -> confirmed -> processing -> completed
                         |                    -> cancelled
Payment: unpaid -> pending -> paid / failed / refunded
Fulfillment: unfulfilled -> partially_fulfilled -> fulfilled

CartItem dan OrderItem menyimpan quantity, unit price, subtotal, serta product/service snapshot. Validasi product atau service aktif pada catalog sebelum membuat item.

Customer, invoice, payment, shipment

Semua model memiliki organizationId. Payment harus memakai idempotency key untuk mencegah double charge. Invoice dan Shipment memakai nomor unik per organization. Status transition harus finite-state dan dicatat dengan actor serta request id.

Karena transaksi ini hidup pada satu database commerce tetapi reference berasal dari runner/catalog, lakukan preflight existence checks sebelum menulis dan gunakan retry/compensation untuk workflow lintas database.

Architecture

Relasi domain yang saya kunci:

                         CATALOG
                            │
              ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
              │             │             │
           Product       Variant      Service
              │             │             │
              ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                            │
                       scalar IDs
                            │
                            ā–¼
                         COMMERCE
                            │
             ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
             │              │              │
           Customer        Cart           Order
                            │              │
                         CartItem       OrderItem
                                           │
                         ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
                         │                 │              │
                      Invoice           Payment        Shipment
                         │                 │              │
                    InvoiceItem    PaymentAllocation  ShipmentItem
                                           │
                                        Refund

Catalog tetap authoritative untuk catalog data. Commerce authoritative untuk transaction data.


Relasi

Catalog - Commerce

Catalog memiliki:

Product
ProductVariant
ServiceDefinition
Service

dan bahkan secara eksplisit memisahkan:

ServiceDefinition
    = catalog definition
 
Service
    = operational service/work record

Maka Commerce cukup:

serviceId String?

pada:

CartItem
OrderItem
InvoiceItem

Jika transaksi berkaitan dengan sebuah service, Commerce menunjuk ke Service di Catalog.

Historical truth tetap disimpan:

serviceSnapshot ServiceSnapshot?

Dengan demikian boundary menjadi sangat bersih.


Perlu diingat bahwa Product dan Variant adalah scalar reference

Misalnya:

productId String?
variantId String?

bukan:

product Product
variant ProductVariant

Karena Catalog dan Commerce adalah database berbeda.

Cross-database references remain scalar IDs.


InvoiceItem tidak sekadar menggunakan OrderItem

Order
 └── OrderItem
 
Invoice
 └── InvoiceItem

karena:

Order
= commercial transaction
 
Invoice
= financial document

Satu Order dapat menghasilkan beberapa Invoice.

Misalnya:

Order 10.000.000
 
Invoice #1
DP              3.000.000
 
Invoice #2
Remaining       7.000.000

Ini tidak bisa dimodelkan dengan baik kalau Invoice hanya mirror Order.


Payment Allocation adalah bagian yang sangat penting

Payment
     │
     ā–¼
PaymentAllocation
     │
     ā–¼
Invoice

Sehingga:

Payment A = 500.000
Payment B = 300.000
Payment C = 200.000
 
Invoice = 1.000.000

semuanya bisa dialokasikan.

Dan juga:

Payment = 1.000.000
 
Invoice A = 600.000
Invoice B = 400.000

juga valid.

Ini membuat Commerce siap untuk:

  • DP
  • installment
  • split payment
  • retry payment
  • multiple invoices
  • partial payment
  • enterprise billing

Refund bukan sekadar status

Refund

sebagai entity tersendiri.

Contoh:

Payment = 1.000.000
 
  Refund #1 = 200.000
  Refund #2 = 100.000
  Refund #3 = 150.000
 
    total refunded = 450.000

Payment dapat berada pada:

partially_refunded

dan akhirnya:

refunded

Shipment Item mendukung partial fulfillment

Misalnya:

Order
 ā”œā”€ā”€ Product A Ɨ 10
 └── Product B Ɨ 5

Shipment 1:

A Ɨ 10
B Ɨ 2

Shipment 2:

B Ɨ 3

Maka:

Order.fulfillmentStatus
        │
        ā”œā”€ā”€ partially_fulfilled
        │
        └── fulfilled

Snapshot bukan sekadar Json

type ProductSnapshot {
  productId
  variantId
  name
  sku
  barcode
  code
  ...
  snapshot Json?
}

Jadi ada dua lapisan:

  • Frequently queried historical fields
name
sku
barcode
code
  • Extension/raw historical data
snapshot Json

Promotion adalah milik Catalog

Order hanya memiliki:

appliedPromotionIds String[] @db.ObjectId

dan:

discountSnapshots DiscountSnapshot[]

Artinya:

Catalog
 └── Promotion
       │
       ā–¼
Commerce
 ā”œā”€ā”€ promotionId
 └── historical discount snapshot

Commerce tidak perlu memiliki Promotion engine.

Ini menjaga konsistenzi dengan Catalog schema.


Customer berada di Commerce

Catalog:

Product
Variant
ServiceDefinition
Service
Price
Promotion

Commerce:

Customer
Cart
Order
Invoice
Payment
Refund
Shipment

karena purchase history harus diperoleh dari:

Customer
   ↓
Order
   ↓
OrderItem

3-Level Historical Truth

  • Level 1 — Reference
productId
variantId
serviceId
promotionId

Untuk traceability dan analytics.

  • Level 2 — Typed snapshot
ProductSnapshot
ServiceSnapshot
DiscountSnapshot
TaxSnapshot
AddressSnapshot

Untuk historical business truth.

  • Level 3 — Extension snapshot
snapshot Json
metadata Json

Untuk data tambahan yang tidak layak menjadi typed core field.


Status dipisahkan

Order memiliki tiga state axis:

OrderStatus
 
OrderPaymentStatus
 
OrderFulfillmentStatus

Contoh:

status              = processing
paymentStatus       = paid
fulfillmentStatus   = partially_fulfilled

atau:

status              = completed
paymentStatus       = partially_refunded
fulfillmentStatus   = fulfilled

Boundary

  • Catalog + Commerce:
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    CATALOG                          │
│                                                     │
│ Product                                             │
│ ProductVariant                                      │
│ ProductComponent                                    │
│ Category                                            │
│ Brand                                               │
│ ServiceDefinition                                   │
│ Service                                             │
│ ServiceDefinitionComponent                          │
│ PriceList                                           │
│ Price                                               │
│ Promotion                                           │
│ PromotionRule                                       │
│ PromotionTarget                                     │
│ PromotionBenefit                                    │
│ PromotionUsage                                      │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                         │
             scalar references + snapshots
                         │
                         ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    COMMERCE                         │
│                                                     │
│ Customer                                            │
│                                                     │
│ Cart ─────────────── CartItem                       │
│                                                     │
│ Order ───────────── OrderItem                       │
│   │                                                 │
│   ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Invoice ───── InvoiceItem        │
│   │                    │                            │
│   │                    └── PaymentAllocation        │
│   │                              │                  │
│   ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Payment ā”€ā”€ā”€ā”€ā”€ā”€ā”˜                  │
│   │                    │                            │
│   │                    └──────── Refund             │
│   │                                                 │
│   └─────────────── Shipment ──── ShipmentItem       │
│                                                     │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜