@mercurjs/docs 2.3.8-canary.2 → 2.3.8-canary.4

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 (114) hide show
  1. package/content/learn/architecture.mdx +166 -263
  2. package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
  3. package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
  4. package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
  5. package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
  6. package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
  7. package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
  8. package/content/platform/attribute/overview.mdx +37 -33
  9. package/content/platform/attribute/reference/data-models.mdx +53 -38
  10. package/content/platform/attribute/reference/events.mdx +47 -24
  11. package/content/platform/attribute/reference/links.mdx +98 -27
  12. package/content/platform/attribute/reference/service.mdx +71 -32
  13. package/content/platform/attribute/reference/workflows.mdx +158 -28
  14. package/content/platform/catalog/concepts/master-products.mdx +39 -37
  15. package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
  16. package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
  17. package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
  18. package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
  19. package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
  20. package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
  21. package/content/platform/catalog/overview.mdx +39 -35
  22. package/content/platform/catalog/reference/data-models.mdx +154 -43
  23. package/content/platform/catalog/reference/events.mdx +41 -25
  24. package/content/platform/catalog/reference/links.mdx +196 -28
  25. package/content/platform/catalog/reference/service.mdx +57 -35
  26. package/content/platform/catalog/reference/workflows.mdx +124 -30
  27. package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
  28. package/content/platform/commission/concepts/rule-matching.mdx +53 -63
  29. package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
  30. package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
  31. package/content/platform/commission/guides/create-a-rate.mdx +101 -53
  32. package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
  33. package/content/platform/commission/overview.mdx +43 -35
  34. package/content/platform/commission/reference/data-models.mdx +59 -45
  35. package/content/platform/commission/reference/events.mdx +28 -34
  36. package/content/platform/commission/reference/links.mdx +42 -33
  37. package/content/platform/commission/reference/service.mdx +103 -28
  38. package/content/platform/commission/reference/workflows.mdx +101 -27
  39. package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
  40. package/content/platform/offer/concepts/shipping.mdx +28 -29
  41. package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
  42. package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
  43. package/content/platform/offer/guides/create-an-offer.mdx +112 -60
  44. package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
  45. package/content/platform/offer/overview.mdx +39 -32
  46. package/content/platform/offer/reference/data-models.mdx +77 -47
  47. package/content/platform/offer/reference/events.mdx +44 -18
  48. package/content/platform/offer/reference/links.mdx +92 -40
  49. package/content/platform/offer/reference/service.mdx +66 -31
  50. package/content/platform/offer/reference/workflows.mdx +146 -37
  51. package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
  52. package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
  53. package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
  54. package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
  55. package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
  56. package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
  57. package/content/platform/order-group/overview.mdx +42 -32
  58. package/content/platform/order-group/reference/data-models.mdx +48 -31
  59. package/content/platform/order-group/reference/events.mdx +29 -17
  60. package/content/platform/order-group/reference/links.mdx +90 -29
  61. package/content/platform/order-group/reference/service.mdx +55 -31
  62. package/content/platform/order-group/reference/workflows.mdx +83 -24
  63. package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
  64. package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
  65. package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
  66. package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
  67. package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
  68. package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
  69. package/content/platform/payout/overview.mdx +43 -34
  70. package/content/platform/payout/reference/data-models.mdx +63 -36
  71. package/content/platform/payout/reference/events.mdx +56 -30
  72. package/content/platform/payout/reference/links.mdx +52 -22
  73. package/content/platform/payout/reference/service.mdx +72 -33
  74. package/content/platform/payout/reference/workflows.mdx +80 -19
  75. package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
  76. package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
  77. package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
  78. package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
  79. package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
  80. package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
  81. package/content/platform/product-edit/overview.mdx +41 -34
  82. package/content/platform/product-edit/reference/data-models.mdx +73 -47
  83. package/content/platform/product-edit/reference/events.mdx +39 -29
  84. package/content/platform/product-edit/reference/links.mdx +32 -24
  85. package/content/platform/product-edit/reference/service.mdx +45 -26
  86. package/content/platform/product-edit/reference/workflows.mdx +173 -31
  87. package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
  88. package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
  89. package/content/platform/review/concepts/the-review-model.mdx +52 -45
  90. package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
  91. package/content/platform/review/guides/create-a-review.mdx +109 -39
  92. package/content/platform/review/guides/moderate-a-review.mdx +83 -37
  93. package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
  94. package/content/platform/review/overview.mdx +43 -33
  95. package/content/platform/review/reference/data-models.mdx +41 -27
  96. package/content/platform/review/reference/events.mdx +55 -37
  97. package/content/platform/review/reference/links.mdx +80 -22
  98. package/content/platform/review/reference/service.mdx +61 -33
  99. package/content/platform/review/reference/workflows.mdx +80 -24
  100. package/content/platform/store/concepts/lifecycle.mdx +68 -44
  101. package/content/platform/store/concepts/store-entity.mdx +45 -27
  102. package/content/platform/store/concepts/team.mdx +56 -35
  103. package/content/platform/store/guides/create-a-store.mdx +102 -35
  104. package/content/platform/store/guides/manage-the-team.mdx +122 -32
  105. package/content/platform/store/guides/moderate-a-store.mdx +107 -38
  106. package/content/platform/store/overview.mdx +41 -34
  107. package/content/platform/store/reference/data-models.mdx +132 -57
  108. package/content/platform/store/reference/events.mdx +42 -17
  109. package/content/platform/store/reference/links.mdx +67 -49
  110. package/content/platform/store/reference/service.mdx +105 -33
  111. package/content/platform/store/reference/workflows.mdx +304 -24
  112. package/content/references/panel-extensions/layouts.mdx +169 -0
  113. package/llms.txt +112 -111
  114. package/package.json +1 -1
@@ -1,59 +1,62 @@
1
1
  ---
2
- title: "Computed totals"
2
+ title: "How order group totals are computed"
3
3
  sidebarTitle: "Computed totals"
4
- description: "Why an order group's seller count and total are derived at query time, not stored."
4
+ description: "Why seller_count and total are summed from the child orders at read time, and how child order statuses are derived."
5
5
  ---
6
6
 
7
- This page covers how an order group's `seller_count` and `total` are calculated
8
- and why they aren't persisted.
7
+ An order group never stores its own total. The `seller_count` and `total` fields are computed from the child orders each time you read the group, and the read workflows derive each child order’s payment and fulfillment status the same way. This keeps the group in step with its orders after edits, returns, and refunds, with no denormalized column to keep in sync.
9
8
 
10
- ## Computed, not stored
9
+ ## Computed fields hold no value
11
10
 
12
- Two fields on the `OrderGroup` model are marked `computed()`. They never hold a
13
- value in the `order_group` row.
11
+ Both fields are declared with `.computed()` on the model, so the `order_group` table has no column for them:
14
12
 
15
13
  ```ts
16
- const OrderGroup = model.define("order_group", {
17
- // ...
18
- seller_count: model.number().computed(),
19
- total: model.bigNumber().computed(),
20
- })
14
+ seller_count: model.number().computed(),
15
+ total: model.bigNumber().computed(),
21
16
  ```
22
17
 
23
- `seller_count` is the number of distinct sellers with a child order in the group.
24
- `total` is the sum of those child orders' current totals. Both are resolved
25
- by aggregating across the group's linked orders each time the group is read,
26
- rather than being written once at creation.
18
+ You can’t write them through `createOrderGroups` or `updateOrderGroups`. They only exist on the records the Seller module reads back.
27
19
 
28
- <Note>
29
- Storing these values would immediately go stale: child orders can be refunded,
30
- returned, or canceled after the group is created, changing both the total and,
31
- effectively, the active seller set.
32
- </Note>
20
+ ## How the Seller module fills them
33
21
 
34
- ## How they're aggregated
22
+ The Seller module reads groups through a custom `OrderGroupRepository`. Its query joins each group to its child orders and sums them:
35
23
 
36
- When you read a group, the Seller module's order-group repository joins the group
37
- to its child orders (and each order to its seller and order summary) and folds
38
- them up:
24
+ - **`seller_count`**: the number of distinct sellers linked to the group’s orders through `order_order_seller_seller`
25
+ - **`total`**: the sum of `current_order_total` from each order’s summary at its current version, or `0` for a group with no orders
39
26
 
40
- - `seller_count`: a distinct count of the linked sellers
41
- - `total`: the sum of each child order's current order total
27
+ Every list, count, and retrieve call on order groups runs through this repository, including `query.graph` on the `order_group` entity. Deleted orders and deleted links are left out.
42
28
 
43
29
  ```ts
44
- const orderGroup = await sellerModuleService.retrieveOrderGroup("og_123")
45
- // orderGroup.seller_count -> e.g. 3
46
- // orderGroup.total -> e.g. 24900 (sum across child orders)
30
+ const sellerModuleService = container.resolve(MercurModules.SELLER)
31
+
32
+ const group = await sellerModuleService.retrieveOrderGroup(
33
+ "og_01JD8Q1234567"
34
+ )
47
35
  ```
48
36
 
37
+ For a group with a 47.00 order from ACME and a 40.00 order from GLOBEX, `group.seller_count` is `2` and `group.total` is `87`. The repository converts `total` to a JavaScript `number`, which matches the `OrderGroupDTO` type.
38
+
39
+ ## Filters pick groups, not orders
40
+
41
+ Order-level filters such as `seller_id`, `status`, `payment_status`, and `sales_channel_id` decide which groups match. They don’t trim the sums. A group that matches `seller_id: "sel_01JD8Q1234567"` still reports the `seller_count` and `total` of every order in it.
42
+
49
43
  <Tip>
50
- Because `total` is a `bigNumber`, group totals stay precise no matter how many
51
- child orders and currencies contribute to the aggregate.
44
+ To show one seller’s share of a group, read the group’s `orders` and sum the ones that belong to that seller. The list workflow’s `sellerId` input trims `orders` for you.
52
45
  </Tip>
53
46
 
54
- ## Aggregated child statuses
47
+ ## Child order statuses
48
+
49
+ `getOrderGroupDetailWorkflow` and `getOrderGroupsListWorkflow` set `payment_status` and `fulfillment_status` on every child order they return. Medusa’s `Order` model doesn’t store these, so the workflows compute them from the order’s data:
50
+
51
+ | Status | Derived from | Possible values |
52
+ | --- | --- | --- |
53
+ | `payment_status` | The order’s payment collections: its own once the payment is split, the cart’s shared one before | `not_paid`, `awaiting`, `authorized`, `partially_authorized`, `captured`, `partially_captured`, `refunded`, `partially_refunded`, `canceled`, `requires_action` |
54
+ | `fulfillment_status` | The `packed_at`, `shipped_at`, `delivered_at`, and `canceled_at` timestamps of its fulfillments, plus unfulfilled item quantities | `not_fulfilled`, `fulfilled`, `partially_fulfilled`, `shipped`, `partially_shipped`, `delivered`, `partially_delivered`, `canceled` |
55
+
56
+ The group has no status of its own. Read the child statuses to show where each seller’s part of the purchase stands.
57
+
58
+ ## Related
55
59
 
56
- The retrieve and list workflows layer the same idea onto child orders. They
57
- derive each order's `payment_status` and `fulfillment_status` from its payment
58
- collections and fulfillments at read time, so the group reflects the live state
59
- of every seller's slice without any denormalized status column.
60
+ - [Retrieve a group with its child order statuses](/platform/order-group/guides/retrieve-an-order-group)
61
+ - [Filters the repository understands](/platform/order-group/reference/service#filters-and-sorting)
62
+ - [The `OrderGroup` fields](/platform/order-group/reference/data-models)
@@ -1,61 +1,63 @@
1
1
  ---
2
- title: "Order splitting"
2
+ title: "How checkout splits a cart into orders"
3
3
  sidebarTitle: "Order splitting"
4
- description: "How a multi-seller cart becomes a group of independent per-seller orders."
4
+ description: "How completeCartWithSplitOrdersWorkflow turns one multi-seller cart into one order per seller under a group."
5
5
  ---
6
6
 
7
- This page covers how a single cart spanning multiple sellers is split into
8
- per-seller orders at checkout.
7
+ Order splitting is what happens when a shopper completes a cart that holds offers from several sellers. Mercur replaces Medusa’s single-order checkout with `completeCartWithSplitOrdersWorkflow`, which creates one order per seller and wraps them in an order group. Understanding the split tells you where each seller’s items, shipping, promotions, and payment end up.
9
8
 
10
- ## Why carts are split
9
+ ## Items follow the offer’s seller
11
10
 
12
- A marketplace cart can hold offers from many different sellers. Each seller
13
- fulfills, ships, and settles independently, so a single combined order would be
14
- impossible to operate. Instead, on completion the cart is divided along seller
15
- boundaries. Every seller with items in the cart gets its own `Order`, and all of
16
- those orders are attached to one parent `OrderGroup`.
11
+ The workflow groups line items by `item.offer.seller_id`. Sellers sell against a shared master catalog through offers, so the offer, not the product, ties a line item to a seller. Every seller with at least one item gets one child order.
17
12
 
18
- The split is driven by the `completeCartWithSplitOrdersWorkflow`. It groups the
19
- cart's line items by the seller behind each offer, and builds one order per
20
- seller from that seller's items and shipping methods.
13
+ The split enforces two rules before it creates anything:
14
+
15
+ - **Every item has a seller**: `validateSellerCartItemsStep` throws `INVALID_DATA` when any line item has no offer with a `seller_id`
16
+ - **Every seller’s shipping is covered**: `validateSellerCartShippingStep` throws when items that require shipping have no matching shipping method
17
+
18
+ Each child order gets the seller’s line items plus the shipping methods whose shipping option belongs to that seller. Addresses, email, currency, locale, region, and sales channel are copied from the cart.
19
+
20
+ ## Promotions stay with their seller
21
+
22
+ Promotion adjustments are split by scope. A promotion linked to a seller only keeps its adjustments on that seller’s items and shipping, and the workflow links the promotion to that seller’s child order. A marketplace promotion with no seller keeps its adjustments on every order it touched, and the workflow links it to no order.
23
+
24
+ ## What the split creates
25
+
26
+ The workflow creates everything for the group in one run:
27
+
28
+ 1. Create the `OrderGroup` with the cart’s `customer_id` and `cart_id`.
29
+ 2. Create one `Order` per seller, with status `pending` unless the `setOrderStatus` hook says otherwise.
30
+ 3. Link each order to the group, its seller, and the cart, and link new customers to the sellers they bought from.
31
+ 4. Mirror each cart line item’s offer link onto the new order line item.
32
+ 5. Reserve inventory for offers that manage inventory, and register promotion usage.
33
+ 6. Set `completed_at` on the cart and emit `order.placed` and `order_group.created`.
34
+ 7. Authorize the payment session, split a captured payment, and refresh commission lines per order.
21
35
 
22
36
  ```ts
23
37
  import { completeCartWithSplitOrdersWorkflow } from "@mercurjs/core/workflows"
24
38
 
25
39
  const { result } = await completeCartWithSplitOrdersWorkflow(container).run({
26
- input: { cart_id: "cart_123" },
40
+ input: { cart_id: "cart_01JD8Q1234567" },
27
41
  })
28
- // result.order_group_id -> the parent group
29
42
  ```
30
43
 
31
- <Note>
32
- Items are grouped by `item.offer.seller_id`. Sellers list against the shared
33
- master catalog through **offers**, so the offer, not the product, is what
34
- ties a line item to a seller.
35
- </Note>
44
+ The run returns `{ order_group_id }`. Mercur’s cart completion route and its `processPaymentWorkflow` both run this workflow, so a webhook-driven checkout also produces a group.
36
45
 
37
- ## What the split produces
46
+ ## Payment stays on the cart until capture
38
47
 
39
- For each seller in the cart the workflow, in one transaction:
48
+ The child orders share the cart’s payment collection until the payment is captured. Medusa allows a payment collection to belong to one order only, so the split doesn’t link the orders to it. Once the payment is captured, `splitCapturedCartPaymentWorkflow` gives each order a payment collection of its own for its share. It runs inside checkout for providers that capture on authorization, and from the `payment.captured` subscriber for the rest.
40
49
 
41
- - creates a child `Order` with that seller's line items and shipping methods
42
- - links each order to the group (`order_group_order`) and to its seller (`order_order_seller_seller`)
43
- - mirrors the line-item → offer links onto the new order lines
44
- - splits payment captures proportionally across the child orders
45
- - reserves offer-scoped inventory and refreshes commission lines per order
50
+ ## One group per cart
46
51
 
47
- Once every child order is created, the workflow emits `order.placed` for the
48
- orders and `order_group.created` for the group.
52
+ The split runs at most once per cart. The workflow takes a lock on the cart id, then looks for an existing group with the same `cart_id`. If it finds one, it skips the split and returns that group’s id, so a retried request or a duplicate webhook can’t create a second set of orders.
49
53
 
50
- <Tip>
51
- Promotions are attributed per seller: a seller-scoped promotion links only to
52
- that seller's child order, while marketplace-wide promotions are applied as
53
- cart adjustments and belong to no single order.
54
- </Tip>
54
+ <Warning>
55
+ Child orders don’t carry the cart’s credit lines yet. The split creates every order with an empty `credit_lines` list.
56
+ </Warning>
55
57
 
56
- ## Independent child orders
58
+ ## Related
57
59
 
58
- After the split, each child order lives its own life. Fulfillment, returns, and
59
- refunds are handled per order, so one seller can ship while another is still
60
- preparing, without affecting the rest of the group. The group remains the
61
- single reference the shopper uses to see the purchase as a whole.
60
+ - [Run the split from your own code](/platform/order-group/guides/split-a-cart)
61
+ - [The workflow’s input, hooks, and events](/platform/order-group/reference/workflows#completecartwithsplitordersworkflow)
62
+ - [How offers tie items to sellers](/platform/offer/concepts/what-is-an-offer)
63
+ - [Commission lines per order](/platform/commission/concepts/order-commission-lines)
@@ -1,21 +1,16 @@
1
1
  ---
2
- title: "The order group"
2
+ title: "What an order group is"
3
3
  sidebarTitle: "The order group"
4
- description: "The aggregate record over a multi-seller purchase, its display id, and cart link."
4
+ description: "The record that ties a multi-seller purchase together: its display id, its customer, its cart, and its child orders."
5
5
  ---
6
6
 
7
- This page covers the order group record and how it aggregates the per-seller
8
- orders created from a single cart.
7
+ An order group is the record that represents one checkout across several sellers. Sellers can’t share an order, because each one ships, refunds, and gets paid on their own, so Mercur gives every seller a child order and keeps the purchase together in a group. You use the group whenever you need the shopper’s view of a purchase: an order history page, a confirmation email, or an operator’s order list.
9
8
 
10
- ## Order group
9
+ ## The OrderGroup record
11
10
 
12
- An order group is the shopper-facing wrapper over a multi-seller purchase. It is
13
- represented by the `OrderGroup` data model (table `order_group`, id prefix
14
- `og`). When a cart containing offers from more than one seller is completed, the
15
- cart is split into one child order per seller. All of those orders are attached
16
- to a single group.
11
+ The group is the `OrderGroup` data model in the Seller module, stored in the `order_group` table with the id prefix `og`. It holds four stored fields and two computed ones:
17
12
 
18
- ```ts
13
+ ```ts title="packages/core/src/modules/seller/models/order-group.ts"
19
14
  const OrderGroup = model.define("order_group", {
20
15
  id: model.id({ prefix: "og" }).primaryKey(),
21
16
  display_id: model.autoincrement(),
@@ -26,39 +21,51 @@ const OrderGroup = model.define("order_group", {
26
21
  })
27
22
  ```
28
23
 
29
- The group carries a human-readable `display_id`, an auto-incrementing integer,
30
- so shoppers and operators can reference the purchase without exposing the
31
- internal id. `customer_id` records who placed it. `seller_count` and `total`
32
- are computed at read time. See [Computed totals](/platform/order-group/concepts/computed-totals).
24
+ Each field has one job:
25
+
26
+ - **`display_id`**: an auto-incrementing integer, so people can quote a purchase without the internal id
27
+ - **`customer_id`**: the customer who completed the cart, or `null` when the cart has no customer
28
+ - **`cart_id`**: the cart the group was created from
29
+ - **`seller_count` and `total`**: summed from the child orders when you read the group, covered in [Computed totals](/platform/order-group/concepts/computed-totals)
33
30
 
34
31
  <Note>
35
- A group is created even for a single-seller cart, so every completed
36
- marketplace order has exactly one parent group regardless of how many sellers
37
- it spans.
32
+ Checkout creates a group for every completed cart, even when the cart has items from one seller. Every marketplace order therefore has exactly one parent group.
38
33
  </Note>
39
34
 
40
35
  ## The cart link
41
36
 
42
- Each group holds a `cart_id` pointing back to the cart it was created from. This
43
- is exposed as a **read-only** link to the Cart module. The cart is frozen
44
- (`completed_at` is set) the moment the split runs, so the reference is a
45
- historical record, not something you write through.
37
+ The `cart_id` field backs a read-only link to Medusa’s Cart module, so you can expand `cart.*` from a group. Checkout sets `completed_at` on the cart in the same run that creates the group, and the cart doesn’t change after that. Treat `cart_id` as a stable pointer to the basket the shopper paid for.
46
38
 
47
39
  ```ts
48
- const { data: groups } = await query.graph({
40
+ const { data: [group] } = await query.graph({
49
41
  entity: "order_group",
50
- fields: ["id", "display_id", "cart.id", "orders.id"],
42
+ fields: ["id", "display_id", "cart.id", "cart.email"],
43
+ filters: { id: "og_01JD8Q1234567" },
51
44
  })
52
45
  ```
53
46
 
54
- <Tip>
55
- Because the cart is immutable after checkout, the `cart_id` is safe to treat as
56
- a stable audit pointer to the exact basket the shopper paid for.
57
- </Tip>
47
+ The query loads one group and the cart it came from. Because the link is read-only, you can’t create or dismiss it with `link.create`: it always follows the `cart_id` column.
58
48
 
59
49
  ## Child orders
60
50
 
61
- The group doesn't store line items itself. Those live on the child `Order`
62
- records, linked through the `order_group_order` table. Loading a group's
63
- `orders.*` gives you each seller's slice, each with its own fulfillment, payment,
64
- returns, and refunds.
51
+ A group holds no line items. The items live on the child `Order` records, one per seller, connected through the `order_group_order` link table. Each child order is a regular Medusa order with its own items, shipping methods, fulfillments, returns, and refunds:
52
+
53
+ - **Seller**: each child order links to the seller who fulfills it
54
+ - **Line items**: each order line item links to the offer it was bought from
55
+ - **Cart**: each child order also links back to the completed cart
56
+
57
+ ```ts
58
+ const { data: [group] } = await query.graph({
59
+ entity: "order_group",
60
+ fields: ["id", "orders.id", "orders.seller.name", "orders.status"],
61
+ filters: { id: "og_01JD8Q1234567" },
62
+ })
63
+ ```
64
+
65
+ The query returns the group with each child order and the seller behind it. To go from an order to its group, query the `order_group_order` link entity by `order_id`.
66
+
67
+ ## Related
68
+
69
+ - [How checkout splits a cart into child orders](/platform/order-group/concepts/order-splitting)
70
+ - [How `seller_count` and `total` are computed](/platform/order-group/concepts/computed-totals)
71
+ - [Every link around an order group](/platform/order-group/reference/links)
@@ -1,71 +1,111 @@
1
1
  ---
2
- title: "List order groups"
2
+ title: "List and filter order groups"
3
3
  sidebarTitle: "List order groups"
4
- description: "Page through order groups and scope them to a seller with getOrderGroupsListWorkflow."
4
+ description: "Page through order groups, filter them, and scope them to one seller with getOrderGroupsListWorkflow."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to list order groups from server code, page
8
- through the results, and optionally scope them to a single seller.
9
-
10
- Mercur exposes a `getOrderGroupsListWorkflow` that returns groups with their
11
- aggregated child orders and a total count for pagination.
12
-
13
- ## Run the workflow
14
-
15
- ```ts title="src/api/custom/order-groups/route.ts"
16
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
17
- import { getOrderGroupsListWorkflow } from "@mercurjs/core/workflows"
18
-
19
- export async function GET(req: MedusaRequest, res: MedusaResponse) {
20
- const { result } = await getOrderGroupsListWorkflow(req.scope).run({
21
- input: {
22
- fields: ["id", "display_id", "total", "seller_count"],
23
- variables: {
24
- skip: 0,
25
- take: 20,
26
- order: { created_at: "DESC" },
27
- },
28
- },
29
- })
30
-
31
- res.json({
32
- order_groups: result.rows,
33
- count: result.metadata?.count ?? 0,
34
- })
35
- }
36
- ```
7
+ List order groups from server code when you build an order history, a report, or a seller-facing view of purchases.
37
8
 
38
- The workflow returns `{ rows, metadata }`, where `metadata` carries the `count`,
39
- `skip`, and `take` you need to drive pagination.
9
+ ## List groups in an API route
40
10
 
41
- <Note>
42
- Filters go inside `variables`: the group repository understands `id`,
43
- `customer_id`, `seller_id`, `status`, `sales_channel_id`, `created_at`,
44
- `updated_at`, and a free-text `q` (matched against group id and customer id).
45
- Order-level filters (`seller_id`, `status`, `sales_channel_id`) only decide
46
- which groups match. A matching group's `seller_count` and `total` still
47
- describe every order in the group, as covered in
48
- [Computed totals](/platform/order-group/concepts/computed-totals).
49
- </Note>
11
+ <Steps>
12
+ <Step title="Run the list workflow">
13
+ Pass the fields you want and put filters and pagination in `variables`.
14
+
15
+ ```ts title="src/api/admin/purchases/route.ts"
16
+ import type {
17
+ MedusaRequest,
18
+ MedusaResponse,
19
+ } from "@medusajs/framework/http"
20
+ import { getOrderGroupsListWorkflow } from "@mercurjs/core/workflows"
21
+
22
+ export async function GET(req: MedusaRequest, res: MedusaResponse) {
23
+ const { result } = await getOrderGroupsListWorkflow(req.scope).run({
24
+ input: {
25
+ fields: ["id", "display_id", "total", "seller_count"],
26
+ variables: { skip: 0, take: 20, order: { display_id: "DESC" } },
27
+ },
28
+ })
29
+
30
+ res.json({
31
+ order_groups: result.rows,
32
+ count: result.metadata?.count ?? 0,
33
+ })
34
+ }
35
+ ```
36
+
37
+ The workflow returns `{ rows, metadata }`. `metadata` carries `count`, `skip`, and `take` for pagination. Every row includes its child `orders` with `payment_status` and `fulfillment_status`.
38
+ </Step>
39
+ <Step title="Add filters">
40
+ Put filter keys next to the pagination keys in `variables`:
41
+
42
+ ```ts
43
+ variables: {
44
+ customer_id: "cus_01JD8Q1234567",
45
+ payment_status: ["captured", "partially_captured"],
46
+ created_at: { $gte: "2026-10-01T00:00:00Z" },
47
+ take: 20,
48
+ }
49
+ ```
50
+
51
+ This returns the customer’s groups from October 2026 that have at least one captured or partially captured child order.
52
+ </Step>
53
+ </Steps>
54
+
55
+ ## Filters you can use
56
+
57
+ The Seller module reads groups through a custom repository that understands a fixed set of top-level keys:
58
+
59
+ - **Group filters**: `id`, `customer_id`, `cart_id`, as a string or an array
60
+ - **Order filters**: `seller_id`, `status`, `payment_status`, `sales_channel_id`, which match a group when at least one child order matches
61
+ - **Dates**: `created_at` and `updated_at`, with `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte`, `$in`, `$nin`, `$like`, `$ilike`
62
+ - **Search**: `q`, which matches the group id or customer id, and a display id such as `#G98`, `G98`, or `98` on the group or a child order
63
+
64
+ You can sort by `display_id`, `created_at`, or `updated_at`. Without an `order`, groups come back newest first.
65
+
66
+ <Warning>
67
+ The repository ignores keys it doesn’t know, including `$and` and `$or`. Pass `id` as a plain string or array, not `{ $in: [...] }`. If you need two id constraints, intersect them yourself before you call the workflow.
68
+ </Warning>
50
69
 
51
- ## Scope to a seller
70
+ ## Scope groups to one seller
52
71
 
53
- Pass a `sellerId` to get a vendor's slice. The workflow filters each group's
54
- child orders down to that seller, so vendors only ever see their own orders
55
- within a group.
72
+ To show a seller only their part of each purchase, filter the groups and trim their orders:
56
73
 
57
74
  ```ts
58
- await getOrderGroupsListWorkflow(req.scope).run({
75
+ const sellerId = "sel_01JD8Q1234567"
76
+
77
+ const { result } = await getOrderGroupsListWorkflow(req.scope).run({
59
78
  input: {
60
- fields: ["id", "display_id"],
61
- variables: { seller_id: "sel_123", take: 20 },
62
- sellerId: "sel_123",
79
+ fields: ["id", "display_id", "orders.total"],
80
+ variables: { seller_id: sellerId, take: 20 },
81
+ sellerId,
63
82
  },
64
83
  })
65
84
  ```
66
85
 
67
- <Tip>
68
- Admin surfaces call this workflow with no `sellerId` for platform-wide
69
- visibility. Vendor surfaces pass the resolved seller so both the query and the
70
- returned child orders stay scoped to that store.
71
- </Tip>
86
+ The two inputs do different jobs. `variables.seller_id` keeps only the groups where the seller has an order. `sellerId` removes the other sellers’ orders from each returned group. `sellerId` also accepts an array of seller ids.
87
+
88
+ <Note>
89
+ `seller_count` and `total` still describe the whole group when you scope it. Sum the trimmed `orders` to get the seller’s share.
90
+ </Note>
91
+
92
+ ## List groups without child orders
93
+
94
+ When you only need group rows, call the Seller module service. It skips the child order expansion and the status work:
95
+
96
+ ```ts
97
+ const sellerModuleService = req.scope.resolve(MercurModules.SELLER)
98
+
99
+ const [groups, count] = await sellerModuleService.listAndCountOrderGroups(
100
+ { customer_id: "cus_01JD8Q1234567" },
101
+ { take: 20, order: { created_at: "DESC" } }
102
+ )
103
+ ```
104
+
105
+ `MercurModules` comes from `@mercurjs/types`. The rows still include the computed `seller_count` and `total`.
106
+
107
+ ## Next steps
108
+
109
+ - [Retrieve one group with its child orders](/platform/order-group/guides/retrieve-an-order-group)
110
+ - [Why filters don’t change the totals](/platform/order-group/concepts/computed-totals#filters-pick-groups-not-orders)
111
+ - [`getOrderGroupsListWorkflow` reference](/platform/order-group/reference/workflows#getordergroupslistworkflow)
@@ -1,63 +1,114 @@
1
1
  ---
2
- title: "Retrieve an order group"
2
+ title: "Retrieve an order group with its child orders"
3
3
  sidebarTitle: "Retrieve an order group"
4
- description: "Load an order group and its aggregated child orders with getOrderGroupDetailWorkflow."
4
+ description: "Load one order group, its child orders, and their payment and fulfillment status with getOrderGroupDetailWorkflow."
5
5
  ---
6
6
 
7
- In this guide, you'll learn how to load a single order group together with its
8
- child orders from your own server code.
7
+ Load one order group from server code when you build an order page, an email, or an export that needs the whole purchase.
9
8
 
10
- Mercur exposes a `getOrderGroupDetailWorkflow` that fetches the group, expands
11
- its child orders, and derives each order's payment and fulfillment status. Run it
12
- from any place that has access to the Medusa container.
9
+ ## Before you begin
13
10
 
14
- ## Run the workflow
11
+ You need the following:
15
12
 
16
- ```ts title="src/api/custom/order-group/[id]/route.ts"
17
- import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
18
- import { getOrderGroupDetailWorkflow } from "@mercurjs/core/workflows"
13
+ - **A completed multi-seller cart**: at least one order group in your database, for example from a storefront checkout
14
+ - **The group id**: an id with the `og` prefix, such as `og_01JD8Q1234567`
19
15
 
20
- export async function GET(req: MedusaRequest, res: MedusaResponse) {
21
- const { result: order_group } = await getOrderGroupDetailWorkflow(
22
- req.scope
23
- ).run({
24
- input: {
25
- order_group_id: req.params.id,
26
- fields: ["id", "display_id", "total", "seller_count", "cart_id"],
27
- },
28
- })
16
+ ## Load the group in an API route
29
17
 
30
- res.json({ order_group })
31
- }
32
- ```
18
+ <Steps>
19
+ <Step title="Create the route">
20
+ Run `getOrderGroupDetailWorkflow` with the group id and the fields you want back.
21
+
22
+ ```ts title="src/api/admin/purchases/[id]/route.ts"
23
+ import type {
24
+ MedusaRequest,
25
+ MedusaResponse,
26
+ } from "@medusajs/framework/http"
27
+ import { getOrderGroupDetailWorkflow } from "@mercurjs/core/workflows"
28
+
29
+ export async function GET(req: MedusaRequest, res: MedusaResponse) {
30
+ const { result: order_group } = await getOrderGroupDetailWorkflow(
31
+ req.scope
32
+ ).run({
33
+ input: {
34
+ order_group_id: req.params.id,
35
+ fields: ["id", "display_id", "total", "seller_count", "cart_id"],
36
+ },
37
+ })
38
+
39
+ res.json({ order_group })
40
+ }
41
+ ```
42
+
43
+ The workflow adds the child order fields it needs on top of yours, so `order_group.orders` is always populated. Each order carries `payment_status` and `fulfillment_status`.
44
+ </Step>
45
+ <Step title="Add the child order fields you display">
46
+ Ask for extra order fields with the `orders.` prefix.
47
+
48
+ ```ts
49
+ const fields = [
50
+ "id",
51
+ "display_id",
52
+ "total",
53
+ "orders.display_id",
54
+ "orders.total",
55
+ "orders.seller.name",
56
+ ]
57
+ ```
33
58
 
34
- The workflow always expands the group's child orders regardless of the `fields`
35
- you pass, so `order_group.orders` is populated with each seller's slice.
59
+ The workflow always returns `orders.id`, `orders.status`, `orders.currency_code`, and `orders.items.*`. Everything else on the order comes from your list.
60
+ </Step>
61
+ <Step title="Handle a missing group">
62
+ The workflow throws a `NOT_FOUND` error when no group has the id. Inside an API route, Medusa’s error handler turns it into a 404 response, so you don’t need to check the result.
63
+ </Step>
64
+ </Steps>
65
+
66
+ ## Keep payment collections and fulfillments in the response
67
+
68
+ The workflow loads every child order’s payment collections and fulfillments to compute the statuses, then drops them from the result. To keep them, include a field that contains `payment_collections` or `fulfillments`:
69
+
70
+ ```ts
71
+ fields: [
72
+ "id",
73
+ "orders.payment_collections.id",
74
+ "orders.fulfillments.id",
75
+ ]
76
+ ```
36
77
 
37
- <Note>
38
- The workflow only fetches heavy relations when you ask for them: include a
39
- `payment_collections` field to get per-order `payment_status`, and a
40
- `fulfillments` field to get `fulfillment_status`. Otherwise those collections
41
- are stripped from the response to keep it lean.
42
- </Note>
78
+ Before the payment is captured, `payment_collections` holds the cart’s shared collection. After capture, it holds the order’s own collection. Fields that start with `orders.cart` keep the child orders’ `cart` in the result too.
43
79
 
44
80
  ## Follow the cart link
45
81
 
46
- The group's read-only `cart_id` points back to the immutable cart it came from.
47
- Expand it through the module link when you need the original basket:
82
+ Expand the read-only cart link to get the basket the group came from:
48
83
 
49
84
  ```ts
50
- await getOrderGroupDetailWorkflow(req.scope).run({
51
- input: {
52
- order_group_id: req.params.id,
53
- fields: ["id", "cart.id", "cart.email", "orders.id", "orders.total"],
54
- },
85
+ fields: ["id", "cart.id", "cart.email", "cart.completed_at"]
86
+ ```
87
+
88
+ The cart is completed and frozen, so these values match what the shopper paid for.
89
+
90
+ ## Find the group for an order
91
+
92
+ When you start from a child order id, look up the group through the `order_group_order` link entity, then run the workflow with its id:
93
+
94
+ ```ts
95
+ const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
96
+
97
+ const { data: [link] } = await query.graph({
98
+ entity: "order_group_order",
99
+ fields: ["order_group_id"],
100
+ filters: { order_id: "order_01JD8Q1234568" },
55
101
  })
56
102
  ```
57
103
 
104
+ `ContainerRegistrationKeys` comes from `@medusajs/framework/utils`. `link` is `undefined` when the order isn’t part of a group.
105
+
58
106
  <Tip>
59
- Prefer the workflow over reading the record directly. It does the child-order
60
- status aggregation for you. The raw
61
- [service method](/platform/order-group/reference/service) returns only the
62
- group row and its computed totals.
107
+ Use the workflow rather than `retrieveOrderGroup` when you need child orders. The service method returns the group row and its computed totals only, without orders or statuses.
63
108
  </Tip>
109
+
110
+ ## Next steps
111
+
112
+ - [List and filter order groups](/platform/order-group/guides/list-order-groups)
113
+ - [How the child order statuses are derived](/platform/order-group/concepts/computed-totals#child-order-statuses)
114
+ - [`getOrderGroupDetailWorkflow` reference](/platform/order-group/reference/workflows#getordergroupdetailworkflow)