@comity/order 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/cjs/contracts/address-snapshot.js +3 -0
  4. package/dist/cjs/contracts/contact.js +3 -0
  5. package/dist/cjs/contracts/customer-snapshot.js +3 -0
  6. package/dist/cjs/contracts/item.js +3 -0
  7. package/dist/cjs/contracts/order-repository.js +3 -0
  8. package/dist/cjs/contracts/order.js +3 -0
  9. package/dist/cjs/contracts/payment-snapshot.js +3 -0
  10. package/dist/cjs/domain/order-transitions.js +52 -0
  11. package/dist/cjs/entities/order.js +459 -0
  12. package/dist/cjs/errors/index.js +6 -0
  13. package/dist/cjs/errors/order.js +39 -0
  14. package/dist/cjs/index.js +8 -0
  15. package/dist/cjs/repositories/index.js +6 -0
  16. package/dist/cjs/repositories/memory.js +69 -0
  17. package/dist/cjs/value-objects/order-id.js +70 -0
  18. package/dist/esm/contracts/address-snapshot.js +2 -0
  19. package/dist/esm/contracts/contact.js +2 -0
  20. package/dist/esm/contracts/customer-snapshot.js +2 -0
  21. package/dist/esm/contracts/item.js +2 -0
  22. package/dist/esm/contracts/order-repository.js +2 -0
  23. package/dist/esm/contracts/order.js +2 -0
  24. package/dist/esm/contracts/payment-snapshot.js +2 -0
  25. package/dist/esm/domain/order-transitions.js +48 -0
  26. package/dist/esm/entities/order.js +455 -0
  27. package/dist/esm/errors/index.js +2 -0
  28. package/dist/esm/errors/order.js +35 -0
  29. package/dist/esm/index.js +3 -0
  30. package/dist/esm/repositories/index.js +2 -0
  31. package/dist/esm/repositories/memory.js +65 -0
  32. package/dist/esm/value-objects/order-id.js +66 -0
  33. package/dist/types/contracts/address-snapshot.d.ts +35 -0
  34. package/dist/types/contracts/contact.d.ts +14 -0
  35. package/dist/types/contracts/customer-snapshot.d.ts +24 -0
  36. package/dist/types/contracts/item.d.ts +100 -0
  37. package/dist/types/contracts/order-repository.d.ts +82 -0
  38. package/dist/types/contracts/order.d.ts +108 -0
  39. package/dist/types/contracts/payment-snapshot.d.ts +36 -0
  40. package/dist/types/domain/order-transitions.d.ts +22 -0
  41. package/dist/types/entities/order.d.ts +168 -0
  42. package/dist/types/errors/index.d.ts +2 -0
  43. package/dist/types/errors/order.d.ts +44 -0
  44. package/dist/types/index.d.ts +9 -0
  45. package/dist/types/repositories/index.d.ts +1 -0
  46. package/dist/types/repositories/memory.d.ts +26 -0
  47. package/dist/types/value-objects/order-id.d.ts +50 -0
  48. package/package.json +102 -0
@@ -0,0 +1,35 @@
1
+ import { BaseError } from "@comity/primitives/errors";
2
+ const REASON_MESSAGES = {
3
+ invalid_quantity: "Invalid order item quantity",
4
+ invalid_item: "Order item not found",
5
+ invalid_status_transition: "Invalid order status transition",
6
+ shipping_destination_immutable: "Shipping destination cannot be changed in the current order status",
7
+ ambiguous_shipping_destination: "Order must contain exactly one shipping destination",
8
+ };
9
+ const REASON_HTTP_STATUS = {
10
+ invalid_quantity: 400,
11
+ invalid_item: 400,
12
+ invalid_status_transition: 409,
13
+ shipping_destination_immutable: 409,
14
+ ambiguous_shipping_destination: 409,
15
+ };
16
+ /**
17
+ * Order operation error with typed reasons.
18
+ */
19
+ export class OrderError extends BaseError {
20
+ /** Error code. */
21
+ code;
22
+ /**
23
+ * @param reason - The reason for the order error.
24
+ * @param meta - Additional metadata for the error.
25
+ */
26
+ constructor(reason, meta) {
27
+ super(REASON_MESSAGES[reason], {
28
+ httpStatus: REASON_HTTP_STATUS[reason],
29
+ ...meta,
30
+ reason,
31
+ });
32
+ this.code = `order:${reason}`;
33
+ }
34
+ }
35
+ //# sourceMappingURL=order.js.map
@@ -0,0 +1,3 @@
1
+ export { Order } from "./entities/order.js";
2
+ export { OrderId } from "./value-objects/order-id.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ export { MemoryOrderRepository } from "./memory.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,65 @@
1
+ import { success } from "@comity/primitives/result";
2
+ /**
3
+ * In-memory implementation of `OrderRepository` for testing and development purposes.
4
+ *
5
+ * Note: This implementation is not suitable for production use as it does not persist
6
+ * sessions and is not shared across multiple instances of the application.
7
+ */
8
+ export class MemoryOrderRepository {
9
+ /** Internal storage keyed by tenant + order ID */
10
+ #orders = new Map();
11
+ #makeKey(tenant, id) {
12
+ return `${tenant.toString()}\u0000${id.toString()}`;
13
+ }
14
+ /**
15
+ * @inheritdoc
16
+ */
17
+ async getById(id, ctx) {
18
+ const order = this.#orders.get(this.#makeKey(ctx.tenant, id));
19
+ if (!order) {
20
+ return success(null);
21
+ }
22
+ return success(order);
23
+ }
24
+ /**
25
+ * @inheritdoc
26
+ */
27
+ async save(order, ctx) {
28
+ this.#orders.set(this.#makeKey(ctx.tenant, order.id), order);
29
+ return success(undefined);
30
+ }
31
+ /**
32
+ * @inheritdoc
33
+ */
34
+ async search(criteria, ctx) {
35
+ const tenantPrefix = `${ctx.tenant.toString()}\u0000`;
36
+ const all = [...this.#orders.entries()]
37
+ .filter(([key]) => key.startsWith(tenantPrefix))
38
+ .map(([, order]) => order);
39
+ let filtered = all;
40
+ if (criteria?.status !== undefined) {
41
+ filtered = filtered.filter((o) => o.status === criteria.status);
42
+ }
43
+ const limit = criteria?.limit ?? filtered.length;
44
+ const offset = criteria?.offset ?? 0;
45
+ const items = filtered.slice(offset, offset + limit).map((o) => ({
46
+ id: o.id,
47
+ status: o.status,
48
+ createdAt: o.createdAt,
49
+ updatedAt: o.updatedAt,
50
+ items: o.items,
51
+ price: o.price,
52
+ channelId: o.channelId,
53
+ ...(o.customer !== undefined ? { customer: o.customer } : {}),
54
+ ...(o.addresses !== undefined ? { addresses: o.addresses } : {}),
55
+ ...(o.payments !== undefined ? { payments: o.payments } : {}),
56
+ ...(o.meta !== undefined ? { meta: o.meta } : {}),
57
+ }));
58
+ const total = filtered.length;
59
+ return success({
60
+ items,
61
+ total,
62
+ });
63
+ }
64
+ }
65
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1,66 @@
1
+ import { failure, success } from "@comity/primitives/result";
2
+ import { InvalidIdentifierError } from "@comity/primitives/errors";
3
+ /**
4
+ * OrderId is a value object that represents the unique identifier of an order.
5
+ *
6
+ * @remarks
7
+ * OrderId is globally unique. The current implementation generates UUIDs.
8
+ * The repository uses a composite key of `TenantId + OrderId` as its physical
9
+ * storage namespace, but the OrderId itself is globally unique and not scoped
10
+ * to a tenant.
11
+ */
12
+ export class OrderId {
13
+ #value;
14
+ /**
15
+ * Creates an OrderId from an identifier string.
16
+ *
17
+ * The Value Object is always created in a valid state; an empty or
18
+ * whitespace-only value yields an `empty` failure instead of throwing.
19
+ *
20
+ * @param value - The value of the order ID.
21
+ *
22
+ * @returns The OrderId, or an `empty` error when the value is empty or
23
+ * whitespace-only.
24
+ */
25
+ static create(value) {
26
+ if (value.trim().length === 0) {
27
+ return failure(new InvalidIdentifierError("empty", {
28
+ details: { kind: "OrderId" },
29
+ }));
30
+ }
31
+ return success(new OrderId(value));
32
+ }
33
+ /**
34
+ * @param value - The value of the order ID.
35
+ */
36
+ constructor(value) {
37
+ this.#value = value;
38
+ }
39
+ /**
40
+ * Returns the underlying string value of the order ID.
41
+ *
42
+ * @returns The underlying identifier.
43
+ */
44
+ get value() {
45
+ return this.#value;
46
+ }
47
+ /**
48
+ * Checks if this OrderId is equal to another OrderId.
49
+ *
50
+ * @param other - The other OrderId to compare with.
51
+ *
52
+ * @returns True if the OrderIds are equal, false otherwise.
53
+ */
54
+ equals(other) {
55
+ return this.#value === other.toString();
56
+ }
57
+ /**
58
+ * Returns a string representation of the order ID.
59
+ *
60
+ * @returns The string representation of the order ID.
61
+ */
62
+ toString() {
63
+ return this.#value;
64
+ }
65
+ }
66
+ //# sourceMappingURL=order-id.js.map
@@ -0,0 +1,35 @@
1
+ import type { Instant } from "@comity/primitives/time";
2
+ /**
3
+ * Role an address plays within a purchase.
4
+ */
5
+ export type OrderAddressRole = "shipping" | "billing";
6
+ /**
7
+ * Immutable historical fact about an address used by an order.
8
+ *
9
+ * A plain, owned representation of an address as it was at purchase time. It
10
+ * is NOT `Address` from `@comity/address` and it is not a reference to the
11
+ * address module: once the order is created it MUST NOT depend on the address
12
+ * module for its content.
13
+ *
14
+ * A single generic type describes every address; the {@link role} discriminates
15
+ * between shipping and billing. Identical addresses are represented once per
16
+ * role rather than as a dedicated `ShippingAddress`/`BillingAddress` shape.
17
+ */
18
+ export interface OrderAddressSnapshot {
19
+ /** Reference to the address identifier, if still known. */
20
+ readonly addressId?: string;
21
+ /** The role of the address within the purchase. */
22
+ readonly role: OrderAddressRole;
23
+ /** Free-form address lines, e.g. street address. */
24
+ readonly lines: ReadonlyArray<string>;
25
+ /** The city of the address. */
26
+ readonly city: string;
27
+ /** The administrative area (e.g. state or province), if any. */
28
+ readonly administrativeArea?: string;
29
+ /** The postal code of the address. */
30
+ readonly postalCode: string;
31
+ /** The country code of the address. */
32
+ readonly countryCode: string;
33
+ /** The timestamp when the address fact was captured. */
34
+ readonly capturedAt: Instant;
35
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Historical contact method associated with an order purchaser.
3
+ *
4
+ * A plain, owned representation of a customer contact at purchase time. It
5
+ * mirrors the shape of the customer module's contact concept without
6
+ * importing any customer type: `@comity/order` stays independent of
7
+ * `@comity/customer`.
8
+ */
9
+ export interface OrderContact {
10
+ /** The type of contact, e.g. "email" or "phone". */
11
+ readonly type: string;
12
+ /** The contact value. */
13
+ readonly value: string;
14
+ }
@@ -0,0 +1,24 @@
1
+ import type { Instant } from "@comity/primitives/time";
2
+ import type { OrderContact } from "./contact.js";
3
+ /**
4
+ * Immutable historical fact about the buyer of an order.
5
+ *
6
+ * A plain, owned representation of the customer as it was at purchase time.
7
+ * It is NOT `Customer` from `@comity/customer` and it is not a reference to
8
+ * the customer module: once the order is created it MUST NOT depend on the
9
+ * customer for its content. Historical order rendering, customer support, and
10
+ * invoice generation rely exclusively on this record.
11
+ *
12
+ * Only identity and display facts are preserved. Operational customer state
13
+ * (preferences, lifecycle metadata) is not copied.
14
+ */
15
+ export interface OrderCustomerSnapshot {
16
+ /** Reference to the customer identifier, if still known. */
17
+ readonly customerId?: string;
18
+ /** Display name of the buyer. */
19
+ readonly displayName: string;
20
+ /** Buyer contact channels, e.g. email or phone. */
21
+ readonly contacts?: ReadonlyArray<OrderContact>;
22
+ /** The timestamp when the customer fact was captured. */
23
+ readonly capturedAt: Instant;
24
+ }
@@ -0,0 +1,100 @@
1
+ import type { Price } from "@comity/pricing";
2
+ /**
3
+ * Product attribute snapshot associated with an order item.
4
+ *
5
+ * A frozen copy of a product attribute at purchase time. It mirrors
6
+ * `ProductAttribute` from `@comity/catalog` without importing catalog types:
7
+ * the order stays independent of the catalog read model.
8
+ */
9
+ export interface OrderProductAttribute {
10
+ /** Attribute code. */
11
+ readonly code: string;
12
+ /** Display name of the attribute. */
13
+ readonly label?: string;
14
+ /** Attribute value. */
15
+ readonly value: string | number | boolean;
16
+ }
17
+ /**
18
+ * Selected option snapshot for an order item (e.g. size = "M", color = "blue").
19
+ *
20
+ * A frozen copy of a chosen product option at purchase time. It mirrors
21
+ * `ProductOptionSelection` from `@comity/catalog` without importing catalog
22
+ * types; the `label` is preserved so the selection stays renderable without
23
+ * the catalog.
24
+ */
25
+ export interface OrderProductOption {
26
+ /** Option code. */
27
+ readonly code: string;
28
+ /** Display name of the option. */
29
+ readonly label?: string;
30
+ /** Selected option value. */
31
+ readonly value: string;
32
+ }
33
+ /**
34
+ * Product variant snapshot associated with an order item.
35
+ *
36
+ * A frozen copy of the purchased product variant at purchase time. It mirrors
37
+ * `ProductVariant` from `@comity/catalog` without importing catalog types and
38
+ * carries no pricing or inventory — those belong to their owning modules.
39
+ */
40
+ export interface OrderVariantSnapshot {
41
+ /** Variant identifier. */
42
+ readonly id: string;
43
+ /** Stock Keeping Unit of the variant. */
44
+ readonly sku?: string;
45
+ /** Display name of the variant. */
46
+ readonly name?: string;
47
+ /** Variant attribute snapshots. */
48
+ readonly attributes?: ReadonlyArray<OrderProductAttribute>;
49
+ /** Selected option snapshots of the variant. */
50
+ readonly options?: ReadonlyArray<OrderProductOption>;
51
+ }
52
+ /**
53
+ * Immutable product snapshot associated with an order item.
54
+ *
55
+ * A self-contained representation of the product as it was at purchase time.
56
+ * It is NOT `ProductProjection` from `@comity/catalog` and it is NOT a
57
+ * reference to the catalog: once the order is created it MUST NOT depend on
58
+ * the catalog for its content. Historical order rendering, customer support,
59
+ * invoice generation, and returns workflows rely exclusively on this record.
60
+ */
61
+ export interface OrderProductSnapshot {
62
+ /** Reference to the catalog product identifier, if still known. */
63
+ readonly productId?: string;
64
+ /** Stock Keeping Unit. */
65
+ readonly sku: string;
66
+ /** Product name. */
67
+ readonly name: string;
68
+ /** The purchased variant, if the product is variant-based. */
69
+ readonly variant?: OrderVariantSnapshot;
70
+ /** Product attribute snapshots. */
71
+ readonly attributes?: ReadonlyArray<OrderProductAttribute>;
72
+ /** Selected option snapshots. */
73
+ readonly options?: ReadonlyArray<OrderProductOption>;
74
+ /** Free-form metadata (e.g. image URL, page URL). */
75
+ readonly metadata?: Readonly<Record<string, unknown>>;
76
+ }
77
+ /**
78
+ * Order line item.
79
+ *
80
+ * Embedded value structure of the Order aggregate: it has no independent
81
+ * identity, lifecycle, or repository.
82
+ */
83
+ export interface OrderItem {
84
+ /** Unique item identifier within the order. */
85
+ readonly id: string;
86
+ /** Associated product snapshot. */
87
+ readonly product: OrderProductSnapshot;
88
+ /** Requested quantity. */
89
+ readonly quantity: number;
90
+ /**
91
+ * Full line price (subtotal, applied modifiers, total).
92
+ *
93
+ * `@comity/pricing` is the single owner of price composition data: price
94
+ * modifiers are accessed exclusively through `price.modifiers`. The order
95
+ * stores the immutable `Price` value and never duplicates modifier storage.
96
+ */
97
+ readonly price: Price;
98
+ /** Custom metadata. */
99
+ readonly meta?: Record<string, unknown>;
100
+ }
@@ -0,0 +1,82 @@
1
+ import type { RepositoryError } from "@comity/primitives/errors";
2
+ import type { Result } from "@comity/primitives/result";
3
+ import type { TenantId } from "@comity/organization";
4
+ import type { Order } from "../entities/order.js";
5
+ import type { OrderId } from "../value-objects/order-id.js";
6
+ import type { OrderState, OrderStatus } from "./order.js";
7
+ /**
8
+ * Minimal search/filter criteria for orders.
9
+ */
10
+ export interface OrderSearchCriteria {
11
+ /** Filter orders by lifecycle status. */
12
+ readonly status?: OrderStatus | undefined;
13
+ /** The maximum number of results to return. */
14
+ readonly limit?: number | undefined;
15
+ /** The offset for paginated results. */
16
+ readonly offset?: number | undefined;
17
+ }
18
+ /**
19
+ * Result of listing or searching orders.
20
+ */
21
+ export interface OrderSearchResult {
22
+ /** The matching orders. */
23
+ readonly items: ReadonlyArray<OrderState>;
24
+ /** Total number of matching orders. */
25
+ readonly total: number;
26
+ }
27
+ /**
28
+ * Context for order repository operations.
29
+ *
30
+ * The tenant provides the isolation boundary for the operation.
31
+ * It is not stored on the order itself — tenant is an operational
32
+ * context, not a business fact of the order.
33
+ */
34
+ export interface OrderRepositoryContext {
35
+ /** The tenant identifier for isolation. */
36
+ readonly tenant: TenantId;
37
+ }
38
+ /**
39
+ * Order repository contract.
40
+ *
41
+ * @remarks
42
+ * This contract is the persistence boundary only. Domain behavior (status
43
+ * transitions, item mutations) lives on the `Order` entity; orchestration
44
+ * belongs to application/domain services.
45
+ *
46
+ * @remarks
47
+ * Not found is `null`, never an error. Orders are not physically deleted: the
48
+ * terminal lifecycle states are `cancelled` and `fulfilled`, so no `remove`
49
+ * operation is exposed.
50
+ */
51
+ export interface OrderRepository {
52
+ /**
53
+ * Retrieve an order by identifier.
54
+ *
55
+ * @param id - Order ID.
56
+ * @param ctx - Repository context containing tenant for isolation.
57
+ *
58
+ * @returns Order entity or null if not found.
59
+ */
60
+ getById(id: OrderId, ctx: OrderRepositoryContext): Promise<Result<Order | null, RepositoryError>>;
61
+ /**
62
+ * Persist an order.
63
+ *
64
+ * @param order - Order to persist.
65
+ * @param ctx - Repository context containing tenant for isolation.
66
+ *
67
+ * @remarks
68
+ * Implementations MUST treat this as upsert: if the order id already
69
+ * exists the stored order is overwritten, otherwise a new order is
70
+ * created.
71
+ */
72
+ save(order: Order, ctx: OrderRepositoryContext): Promise<Result<void, RepositoryError>>;
73
+ /**
74
+ * Search orders matching the given criteria.
75
+ *
76
+ * @param criteria - Search criteria.
77
+ * @param ctx - Repository context containing tenant for isolation.
78
+ *
79
+ * @returns Matching orders with a total count.
80
+ */
81
+ search(criteria: OrderSearchCriteria | undefined, ctx: OrderRepositoryContext): Promise<Result<OrderSearchResult, RepositoryError>>;
82
+ }
@@ -0,0 +1,108 @@
1
+ import type { Price } from "@comity/pricing";
2
+ import type { Instant } from "@comity/primitives/time";
3
+ import type { OrderId } from "../value-objects/order-id.js";
4
+ import type { ChannelId } from "@comity/organization";
5
+ import type { OrderAddressSnapshot } from "./address-snapshot.js";
6
+ import type { OrderCustomerSnapshot } from "./customer-snapshot.js";
7
+ import type { OrderItem, OrderProductSnapshot } from "./item.js";
8
+ import type { OrderPaymentSnapshot } from "./payment-snapshot.js";
9
+ /**
10
+ * Order status.
11
+ *
12
+ * An order starts in {@link OrderStatus."draft"} and progresses through a linear lifecycle.
13
+ * Each transition is explicit; the status cannot move backwards.
14
+ *
15
+ * Transitions:
16
+ * - `draft` → `pending` (submit)
17
+ * - `pending` → `confirmed` (confirm)
18
+ * - `confirmed` → `fulfilled` (fulfill)
19
+ * - `draft | pending | confirmed` → `cancelled` (cancel)
20
+ *
21
+ * `fulfilled` and `cancelled` are terminal.
22
+ */
23
+ export type OrderStatus = "draft" | "pending" | "confirmed" | "fulfilled" | "cancelled";
24
+ /**
25
+ * Business data of an order, independent of identity and lifecycle metadata.
26
+ */
27
+ export interface OrderData {
28
+ /** Order items. */
29
+ readonly items: ReadonlyArray<OrderItem>;
30
+ /** Order price. `@comity/pricing` owns price composition: modifiers are accessed through `price.modifiers`. */
31
+ readonly price: Price;
32
+ /** The commercial channel through which the order was placed. */
33
+ readonly channelId: ChannelId;
34
+ /** Historical buyer fact, captured at purchase time. */
35
+ readonly customer?: OrderCustomerSnapshot;
36
+ /** Historical address facts (shipping/billing), captured at purchase time. */
37
+ readonly addresses?: ReadonlyArray<OrderAddressSnapshot>;
38
+ /** Historical payment facts, attached after processing. */
39
+ readonly payments?: ReadonlyArray<OrderPaymentSnapshot>;
40
+ /** Custom metadata. */
41
+ readonly meta?: Record<string, unknown>;
42
+ }
43
+ /**
44
+ * The persistent state of an existing order.
45
+ */
46
+ export interface OrderState extends OrderData {
47
+ /** Unique order identifier. */
48
+ readonly id: OrderId;
49
+ /** Order lifecycle status. */
50
+ readonly status: OrderStatus;
51
+ /** Creation timestamp. */
52
+ readonly createdAt: Instant;
53
+ /** Last update timestamp. */
54
+ readonly updatedAt: Instant;
55
+ }
56
+ /**
57
+ * Immutable point-in-time snapshot of an existing order.
58
+ */
59
+ export type OrderSnapshot = Readonly<OrderState & {
60
+ /** The timestamp when the snapshot was captured. */
61
+ readonly capturedAt: Instant;
62
+ }>;
63
+ /**
64
+ * Data required to create a new order.
65
+ *
66
+ * The identifier and the initial status are managed by the entity: an order
67
+ * without a supplied status starts as {@link OrderStatus."draft"}. Lifecycle
68
+ * timestamps are optional so the same contract supports both creation and
69
+ * hydration from persisted state (ADR-001).
70
+ */
71
+ export type OrderCreate = OrderData & {
72
+ /** Order lifecycle status, used to restore persisted state during hydration. */
73
+ readonly status?: OrderStatus;
74
+ /** The timestamp when the order was created. */
75
+ readonly createdAt?: Instant;
76
+ /** The timestamp when the order was last updated. */
77
+ readonly updatedAt?: Instant;
78
+ };
79
+ /**
80
+ * Partial mutation data for an order.
81
+ *
82
+ * Status transitions are not part of the update contract: they must pass
83
+ * through the entity's domain methods (`submit`, `confirm`, `fulfill`,
84
+ * `cancel`). Items are mutated through `addItem`/`removeItem`/
85
+ * `updateItemQuantity`, which protect the order's invariants.
86
+ */
87
+ export interface OrderUpdate {
88
+ /** Order price. Modifiers are updated through `price`; see `@comity/pricing`. */
89
+ readonly price?: Price;
90
+ /** Custom metadata. */
91
+ readonly meta?: Record<string, unknown>;
92
+ }
93
+ /**
94
+ * Input for adding an item to the order.
95
+ *
96
+ * The item price is supplied by the caller as a complete `Price` value:
97
+ * price computation belongs to `@comity/pricing` orchestration, not to the
98
+ * Order aggregate. The product data is an owned snapshot mapped by the caller
99
+ * from `ProductProjection`: the Order never depends on `@comity/catalog`.
100
+ */
101
+ export interface OrderItemInput {
102
+ /** Product snapshot to add to the order. */
103
+ readonly product: OrderProductSnapshot;
104
+ /** Quantity to add. */
105
+ readonly quantity: number;
106
+ /** Full line price (subtotal, applied modifiers, total). */
107
+ readonly price: Price;
108
+ }
@@ -0,0 +1,36 @@
1
+ import type { Instant } from "@comity/primitives/time";
2
+ import type { Money } from "@comity/pricing";
3
+ /**
4
+ * Outcome of a payment at the time the snapshot was captured.
5
+ *
6
+ * Mirrors the confirmed `@comity/payment` lifecycle without importing any
7
+ * payment type: the order records the factual outcome only.
8
+ */
9
+ export type OrderPaymentStatus = "authorized" | "captured" | "failed" | "cancelled";
10
+ /**
11
+ * Immutable historical fact about the payment of an order.
12
+ *
13
+ * A plain, owned representation of the payment outcome. It is NOT a payment
14
+ * entity and it is not a reference to `@comity/payment`: once the snapshot is
15
+ * attached the order MUST NOT depend on the payment module for its content.
16
+ *
17
+ * Only reconciling facts are preserved. The order never coordinates or
18
+ * recollects a payment; it only records the outcome the Application Layer
19
+ * attaches after processing.
20
+ */
21
+ export interface OrderPaymentSnapshot {
22
+ /** Reference to the payment identifier managed by the payment module. */
23
+ readonly paymentId?: string;
24
+ /** The charged amount. */
25
+ readonly amount: Money;
26
+ /** The payment outcome at capture time. */
27
+ readonly status: OrderPaymentStatus;
28
+ /** The payment provider identifier, if known. */
29
+ readonly provider?: string;
30
+ /** The provider-side reference (e.g. transaction/authorization token). */
31
+ readonly reference?: string;
32
+ /** The timestamp when the payment was authorized, if known. */
33
+ readonly authorizedAt?: Instant;
34
+ /** The timestamp when the payment was captured, if known. */
35
+ readonly capturedAt?: Instant;
36
+ }
@@ -0,0 +1,22 @@
1
+ import type { Result } from "@comity/primitives/result";
2
+ import type { OrderStatus } from "../contracts/order.js";
3
+ import { OrderError } from "../errors/order.js";
4
+ /**
5
+ * Checks whether a status transition is legal.
6
+ *
7
+ * @param from - Source status.
8
+ * @param to - Target status.
9
+ *
10
+ * @returns True if the transition is allowed, false otherwise.
11
+ */
12
+ export declare function canTransition(from: OrderStatus, to: OrderStatus): boolean;
13
+ /**
14
+ * Applies a status transition.
15
+ *
16
+ * @param from - Source status.
17
+ * @param to - Target status.
18
+ *
19
+ * @returns The target status, or an `invalid_status_transition` error when the
20
+ * transition is not allowed.
21
+ */
22
+ export declare function transitionOrderStatus(from: OrderStatus, to: OrderStatus): Result<OrderStatus, OrderError>;