@mercurjs/docs 2.3.8-canary.3 → 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 (113) 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/llms.txt +111 -111
  113. package/package.json +1 -1
@@ -1,32 +1,93 @@
1
1
  ---
2
- title: "Workflows"
2
+ title: "Payout workflows"
3
3
  sidebarTitle: "Workflows"
4
- description: "Payout account, onboarding, payout, and webhook workflows."
4
+ description: "The 4 payout workflows and 6 payout steps exported from @mercurjs/core/workflows, with their inputs, steps, and hooks."
5
5
  ---
6
6
 
7
- This reference lists the workflows for the Payout domain. Import them from
8
- `@mercurjs/core/workflows` and run them against the Medusa container.
7
+ The Payout module ships 4 workflows in `packages/core/src/workflows/payout`. Import them, and their steps, from `@mercurjs/core/workflows`. None of them emits a custom event. Medusa’s data-model events still fire for the records they write, as listed in [Events](/platform/payout/reference/events).
9
8
 
10
- ## Account & onboarding workflows
9
+ ## createPayoutAccountWorkflow
11
10
 
12
- | Workflow | Input | Purpose |
11
+ Creates a payout account for a seller through the provider and links it to the seller. Workflow id `create-payout-account-for-seller`.
12
+
13
+ | Input | Type | Description |
14
+ | --- | --- | --- |
15
+ | `seller_id` | `string` | Seller to create the account for |
16
+ | `data` | `Record<string, unknown>`, optional | Passed to the provider; Stripe Connect needs `country` |
17
+ | `context` | `PayoutProviderContext`, optional | Provider context, such as `idempotency_key` |
18
+
19
+ Steps, in order:
20
+
21
+ 1. `validate-seller-payout-account`: fails with `Seller already has a payout account` when a `seller_payout_account` row exists
22
+ 2. `create-payout-account`: calls the service’s `createPayoutAccount`
23
+ 3. `create-remote-links`: links the seller and the account
24
+
25
+ Returns the `PayoutAccountDTO`. Exposes no hooks.
26
+
27
+ ## createOnboardingWorkflow
28
+
29
+ Creates or updates the onboarding record for a payout account through the provider. Workflow id `create-onboarding`.
30
+
31
+ | Input | Type | Description |
32
+ | --- | --- | --- |
33
+ | `account_id` | `string` | The `pacc` id |
34
+ | `data` | `Record<string, unknown>`, optional | Merged over the account’s `data`; Stripe Connect needs `refresh_url` and `return_url` |
35
+ | `context` | `PayoutProviderContext`, optional | Provider context, stored on the record |
36
+
37
+ It runs one step, `create-onboarding`, and returns the `OnboardingDTO`. Exposes no hooks.
38
+
39
+ ## createPayoutWorkflow
40
+
41
+ Pays the seller’s share of one order through the provider. Workflow id `create-payout`, exported as `createPayoutWorkflowId`.
42
+
43
+ ```ts
44
+ const { result } = await createPayoutWorkflow(container).run({
45
+ input: { order_id: "order_01JD8Q1234567" },
46
+ })
47
+ ```
48
+
49
+ Steps, in order:
50
+
51
+ 1. `fetch-order`: loads `total`, `currency_code`, `seller.payout_account`, items, and shipping methods
52
+ 2. `fetch-commission-lines`: loads the `commission_line` rows for those items and shipping methods
53
+ 3. `create-payout-step`: creates the payout for `total` minus the commission sum, with the order id as `context.idempotency_key`
54
+ 4. `create-remote-links`: links the payout to the seller
55
+
56
+ It throws `Seller does not have a payout account` when the seller has none. Returns the `PayoutDTO`.
57
+
58
+ | Hook | Payload | Use it to |
59
+ | --- | --- | --- |
60
+ | `payoutCreated` | `{ order_id, payout_id }` | Link the payout to the order, notify the seller, or sync a ledger |
61
+
62
+ ## processPayoutForWebhookWorkflow
63
+
64
+ Applies a parsed provider webhook to an account or a payout. Workflow id `process-payout-for-webhook`, exported as `processPayoutForWebhookWorkflowId`. Its input is a `PayoutWebhookResult`: an `action` and an optional `data.id`.
65
+
66
+ | Action | Step name | Effect |
13
67
  | --- | --- | --- |
14
- | `createPayoutAccountWorkflow` | `{ seller_id, data?, context? }` | Create a payout account, call the provider, link it to the store |
15
- | `createOnboardingWorkflow` | `{ account_id, data?, context? }` | Create or update the account's onboarding record via the provider |
68
+ | `account.activated` | `activate-payout-account` | Account `status` to `active` |
69
+ | `account.restricted` | `restrict-payout-account` | Account `status` to `restricted` |
70
+ | `account.rejected` | `reject-payout-account` | Account `status` to `rejected` |
71
+ | `payout.processing` | `processing-payout` | Payout `status` to `processing` |
72
+ | `payout.paid` | `paid-payout` | Payout `status` to `paid` |
73
+ | `payout.failed` | `failed-payout` | Payout `status` to `failed` |
74
+ | `payout.canceled` | `canceled-payout` | Payout `status` to `canceled` |
16
75
 
17
- ## Payout workflows
76
+ Each branch runs only when `data.id` is set. `not_supported` runs no step. Returns nothing and exposes no hooks.
18
77
 
19
- | Workflow | Input | Purpose |
78
+ ## Steps
79
+
80
+ You can compose these steps into your own workflows:
81
+
82
+ | Step | Id | Compensation |
20
83
  | --- | --- | --- |
21
- | `createPayoutWorkflow` | `{ order_id }` | Compute the seller's share (total − commission) and initiate a transfer |
22
- | `processPayoutForWebhookWorkflow` | `PayoutWebhookResult` | Apply a provider webhook to account or payout status |
84
+ | `validateSellerPayoutAccountStep` | `validate-seller-payout-account` | None |
85
+ | `createPayoutAccountStep` | `create-payout-account` | None |
86
+ | `createOnboardingStep` | `create-onboarding` | None |
87
+ | `createPayoutStep` | `create-payout-step` | None |
88
+ | `updatePayoutAccountStep` | `update-payout-account-step` | Restores the previous status |
89
+ | `updatePayoutStep` | `update-payout-step` | Restores the previous status |
23
90
 
24
91
  <Note>
25
- `createPayoutWorkflow` reads the order with its seller, payout account, and
26
- commission lines, then uses the order id as the transfer's idempotency key so
27
- a re-run never double-pays.
92
+ The create steps have no compensation. If a later step fails, the account, onboarding record, or payout stays, and the provider-side object stays too. The service only deletes a new row when the provider call itself throws.
28
93
  </Note>
29
-
30
- To work with records directly instead of through a workflow, see the
31
- [Service reference](/platform/payout/reference/service). For the events that
32
- drive these workflows, see the [Event reference](/platform/payout/reference/events).
@@ -1,66 +1,73 @@
1
1
  ---
2
- title: "Change actions"
2
+ title: "What a change action records and how it applies"
3
3
  sidebarTitle: "Change actions"
4
- description: "The typed actions that make up a change and the details they carry."
4
+ description: "The ProductChangeAction types, the details each one carries, and the order they apply in."
5
5
  ---
6
6
 
7
- In this document, you'll learn how the individual operations inside a change are
8
- modeled.
7
+ A change action is one operation inside a product change: set a field, add a variant, detach an attribute, delete the product. Splitting a change into typed actions lets an operator see exactly what a seller asked for, and lets Mercur apply each operation through the right Medusa workflow when the change is confirmed.
9
8
 
10
- ## Product change action
9
+ ## The action record
11
10
 
12
- Each operation inside a change is a `ProductChangeAction` (table
13
- `product_change_action`, id prefix `prodchact`). An action belongs to a parent
14
- `ProductChange`, targets a `product_id`, and names the operation in its `action`
15
- field. The operation's payload lives in the `details` JSON, and an `applied`
16
- boolean records whether it has already been written to the product.
11
+ A `ProductChangeAction` (id prefix `prodchact`) belongs to a parent change through `product_change_id` and targets a `product_id`. Its `action` field names the operation, and its `details` JSON holds the payload. The `applied` boolean starts `false` and flips to `true` once the action has been written to the product.
12
+
13
+ Actions also carry an auto-incrementing `ordering`, plus `reference` and `reference_id` columns. Mercur’s workflows don’t set the reference columns, so you can use them to point an action at a record of your own.
17
14
 
18
15
  ```ts
19
16
  const action = {
20
- product_id: "prod_123",
21
- action: "UPDATE",
22
- details: { field: "title", value: "New title" },
17
+ product_id: "prod_01JD8Q1234567",
18
+ action: ProductChangeActionType.UPDATE,
19
+ details: { field: "title", value: "Linen shirt" },
23
20
  }
24
21
  ```
25
22
 
26
- Actions carry an autoincrementing `ordering` so a change with several operations
27
- applies them deterministically.
23
+ This is the shape you pass to `stageProductChangeWorkflow` in its `actions` array. `ProductChangeActionType` is exported from `@mercurjs/types`.
28
24
 
29
25
  ## Action types
30
26
 
31
- The `action` field is one of the `ProductChangeActionType` values. Each type
32
- reads a different shape out of `details`:
27
+ The `action` field takes one of the 11 `ProductChangeActionType` values. The edit workflows also store the previous value in `details`, so a reviewer can see the before and after:
33
28
 
34
- | Action | `details` shape | Applies |
29
+ | Action | `details` written by Mercur | Applies |
35
30
  | --- | --- | --- |
36
- | `UPDATE` | `{ field, value }` | A single product field update |
37
- | `STATUS_CHANGE` | `{ status }` | A product status change |
38
- | `VARIANT_ADD` | `{ variant }` | Create a variant |
39
- | `VARIANT_UPDATE` | `{ variant_id, fields }` | Update a variant (scalars + image links) |
40
- | `VARIANT_REMOVE` | `{ variant_id }` | Delete a variant |
41
- | `ATTRIBUTE_ADD` | `{ attribute }` | Attach a product attribute |
42
- | `ATTRIBUTE_UPDATE` | `{ update }` | Change an attached attribute |
43
- | `ATTRIBUTE_REMOVE` | `{ attribute_id }` | Detach an attribute |
44
- | `PRODUCT_ADD` | None | Record a product creation in the trail |
45
- | `PRODUCT_DELETE` | None | Delete the product |
46
- | `CHANGE_REQUESTED` | `{ message }` | Record an operator revision request |
47
-
48
- <Note>
49
- `CHANGE_REQUESTED` mutates nothing. It's an audit-only marker for a revision
50
- request. The operator's message rides on both the action's `details.message`
51
- and the parent change's `external_note`. See
52
- [Status & auto-confirm](/platform/product-edit/concepts/status-and-auto-confirm).
53
- </Note>
31
+ | `UPDATE` | `{ field, value, previous_value }` | Sets one product field |
32
+ | `STATUS_CHANGE` | `{ status, previous_status }` | Sets the product status |
33
+ | `VARIANT_ADD` | `{ variant }` | Creates a variant |
34
+ | `VARIANT_UPDATE` | `{ variant_id, fields, previous_fields }` | Updates a variant and its image links |
35
+ | `VARIANT_REMOVE` | `{ variant_id }` | Deletes a variant |
36
+ | `ATTRIBUTE_ADD` | `{ attribute, attribute_id, previous_value }` | Attaches an attribute |
37
+ | `ATTRIBUTE_UPDATE` | `{ update, attribute_id, value, previous_value }` | Changes an attached attribute |
38
+ | `ATTRIBUTE_REMOVE` | `{ attribute_id, previous_value }` | Detaches an attribute |
39
+ | `PRODUCT_DELETE` | `{}` | Deletes the product and its offers |
40
+ | `PRODUCT_ADD` | `{ status, attribute_ids }` | Nothing: records the product’s creation |
41
+ | `CHANGE_REQUESTED` | `{ message }` | Nothing: records a revision request |
42
+
43
+ `PRODUCT_ADD` and `CHANGE_REQUESTED` only appear on audit changes, which Mercur stores with `applied: true`. See [The change pipeline](/platform/product-edit/concepts/change-pipeline#audit-changes).
44
+
45
+ ## What the edit workflows diff
46
+
47
+ The edit workflows stage an action only when your input differs from the product. Arrays of relations compare by id or URL, ignoring order, so resending the same categories stages nothing:
48
+
49
+ - **Product fields**: `title`, `subtitle`, `handle`, `description`, `discountable`, `is_giftcard`, `thumbnail`, `images`, `material`, `weight`, `length`, `height`, `width`, `hs_code`, `mid_code`, `origin_country`, `type_id`, `collection_id`, `categories`, `tags`, `sales_channels`, `metadata`
50
+ - **Status**: a `status` key in the update becomes a `STATUS_CHANGE` action instead of an `UPDATE`
51
+ - **Variants**: `manage_inventory` is skipped on update, and `images` takes `{ add, remove }` image ids
52
+ - **Attributes**: an add the product already holds, or a remove of an attribute it doesn’t hold, stages nothing
53
+
54
+ A key outside the product field list is ignored by `productEditUpdateProductWorkflow`.
54
55
 
55
56
  ## How actions apply
56
57
 
57
- When a change is confirmed, its **not-yet-applied** actions are bucketed by type
58
- and dispatched to the matching Medusa workflows in one pass: product updates,
59
- variant creates/updates/deletes, and the attribute batch. Each action is then
60
- flipped to `applied: true` so a re-run never applies it twice.
58
+ When a change is confirmed, `applyProductChangeActionsWorkflow` loads its actions with `applied: false`, groups them by type, and runs each group once. The groups run in this order:
59
+
60
+ 1. `STATUS_CHANGE` and `UPDATE`, merged per product into one `updateProductsWorkflow` call
61
+ 2. `VARIANT_REMOVE` through `deleteProductVariantsWorkflow`
62
+ 3. `VARIANT_ADD` through `createProductVariantsWorkflow`
63
+ 4. `VARIANT_UPDATE` scalar fields through `updateProductVariantsWorkflow`, then the variant image links
64
+ 5. `ATTRIBUTE_*` as one batch through `createAndLinkProductAttributesToProductWorkflow`
65
+ 6. `PRODUCT_DELETE` through `deleteProductsWorkflow`, followed by `deleteOffersWorkflow` for the product’s offers
66
+
67
+ The workflow then marks every action it handled as `applied: true`, so running the apply workflow again on the same change never writes an action twice. Types it doesn’t handle, `PRODUCT_ADD` and `CHANGE_REQUESTED`, keep whatever `applied` value they were stored with.
68
+
69
+ ## Related
61
70
 
62
- <Tip>
63
- Audit-trail changes (publish approvals, revision requests) are stored with
64
- their actions already `applied`, so confirming them is a no-op on the product
65
- itself. They exist purely as history.
66
- </Tip>
71
+ - [The change lifecycle and auto-confirm](/platform/product-edit/concepts/status-and-auto-confirm)
72
+ - [Stage actions directly](/platform/product-edit/guides/edit-a-product#stage-actions-directly)
73
+ - [The `ProductChangeAction` data model](/platform/product-edit/reference/data-models#productchangeaction)
@@ -1,69 +1,77 @@
1
1
  ---
2
- title: "The change pipeline"
2
+ title: "How product changes are staged and recorded"
3
3
  sidebarTitle: "The change pipeline"
4
- description: "The ProductChange record, its immutability, and the audit trail."
4
+ description: "How an edit becomes a ProductChange, who can have one open, and how the audit trail builds up."
5
5
  ---
6
6
 
7
- In this document, you'll learn how a product edit is captured and why the
8
- pipeline is built on immutable records.
7
+ A product change is one reviewable edit to one product. Because every seller sells against the same shared product, a seller’s edit can’t take effect the moment it’s made. Mercur stores the edit as a `ProductChange` with its actions, holds it for review, and applies it only on confirmation. The same records double as the product’s history.
9
8
 
10
- ## Product change
9
+ ## The product change record
11
10
 
12
- A product change is a single reviewable edit to one product, represented by the
13
- `ProductChange` data model (table `product_change`, id prefix `prodch`). It
14
- references the target product through `product_id`, carries the `status` of the
15
- review, and records who created and resolved it (`created_by`, `confirmed_by`,
16
- `declined_by`, `canceled_by`) with matching timestamps.
11
+ A `ProductChange` (id prefix `prodch`) points at its product through `product_id` and carries a `status` from the `ProductChangeStatus` enum. It owns one or more `ProductChangeAction` rows in `actions`, one per operation it will apply. The change is the unit you review. The actions are what gets written.
12
+
13
+ Each resolution stamps an actor and a time on the record:
14
+
15
+ - **Staging**: `created_by`
16
+ - **Confirm**: `confirmed_by`, `confirmed_at`
17
+ - **Decline**: `declined_by`, `declined_at`, `declined_reason`
18
+ - **Cancel**: `canceled_by`, `canceled_at`
19
+
20
+ The actor fields are plain text. Vendor routes write the seller id (`sel_…`) into `created_by`, and admin routes write the operator’s user id. Two notes travel with the change: `internal_note` for operators and `external_note` for the seller.
17
21
 
18
22
  ```ts
19
- const productChangeModuleService = container.resolve(MercurModules.PRODUCT_EDIT)
23
+ import { MercurModules } from "@mercurjs/types"
20
24
 
21
- const change = await productChangeModuleService.retrieveProductChange(id, {
22
- relations: ["actions"],
23
- })
25
+ const productEdit = container.resolve(MercurModules.PRODUCT_EDIT)
26
+
27
+ const change = await productEdit.retrieveProductChange(
28
+ "prodch_01JD8Q1234567",
29
+ { relations: ["actions"] }
30
+ )
24
31
  ```
25
32
 
26
- A change owns one or more `ProductChangeAction` records (`actions`). The change
27
- is the reviewable unit; the actions are the individual operations it will apply.
28
- See [Change actions](/platform/product-edit/concepts/change-actions).
33
+ The service call loads one change with its actions. To read a product’s full history, use the `product.changes` link instead, covered in [Links](/platform/product-edit/reference/links).
29
34
 
30
- <Note>
31
- A product can have **only one active (pending) change at a time**. Staging a
32
- new change while one is still pending is rejected. The vendor resolves or
33
- cancels the open change first.
34
- </Note>
35
+ ## How an edit is staged
35
36
 
36
- ## Immutability & the audit trail
37
+ Seller edits reach the pipeline through four edit workflows. Each one loads the current product, compares your input against it, and passes only the differences to `stageProductChangeWorkflow`:
37
38
 
38
- A change is never rewritten in place. It is created, its actions are appended,
39
- and it is resolved by moving `status` forward and stamping the actor and time.
40
- Because nothing is overwritten, the set of `ProductChange` rows on a product is a
41
- durable history of who changed what and who approved it.
39
+ - **`productEditUpdateProductWorkflow`**: product fields and status, as `UPDATE` and `STATUS_CHANGE` actions
40
+ - **`productEditUpdateVariantsWorkflow`**: variant add, update, and remove operations, as `VARIANT_*` actions
41
+ - **`productEditUpdateAttributesWorkflow`**: attribute add, update, and remove, as `ATTRIBUTE_*` actions
42
+ - **`productEditDeleteProductWorkflow`**: a single `PRODUCT_DELETE` action
42
43
 
43
- Some events aren't vendor edits at all, such as a publish approval or a revision
44
- request. They still belong in the history. Those are recorded as changes created
45
- already `confirmed`, so the audit trail captures them without waiting on review.
44
+ `stageProductChangeWorkflow` creates the `pending` change, creates its actions, emits `product-change.created`, and then runs `autoConfirmProductChangeWorkflow`. Whether the change confirms in that same run is covered in [Status & auto-confirm](/platform/product-edit/concepts/status-and-auto-confirm).
46
45
 
47
- ```ts
48
- await recordProductAuditChangeWorkflow(container).run({
49
- input: {
50
- actor_id: "user_123",
51
- changes: [
52
- {
53
- product_id: "prod_123",
54
- external_note: "Approved for publish",
55
- actions: [
56
- { product_id: "prod_123", action: "STATUS_CHANGE", details: { status: "published" } },
57
- ],
58
- },
59
- ],
60
- },
61
- })
62
- ```
46
+ ## One open change per author
47
+
48
+ The edit workflows and `createProductChangeWorkflow` refuse to stage a change when the same author already has a `pending` change on that product. The check is scoped by `created_by`, so two different sellers can each have one open change on the same shared product. When `created_by` is missing, the check covers every pending change on the product.
49
+
50
+ <Warning>
51
+ `stageProductChangeWorkflow` doesn’t run this check. Call it directly only when you’ve handled conflicts yourself, or prefer an edit workflow.
52
+ </Warning>
53
+
54
+ The conflict surfaces as a `NOT_ALLOWED` error: “There is already an active update request for this product. Only one request can be active at a time.” The author resolves it by canceling the open change or waiting for an operator to resolve it.
55
+
56
+ ## Audit changes
57
+
58
+ Some events change a product without a review, yet still belong in its history. `recordProductAuditChangeWorkflow` stores them as changes created already `confirmed`, with every action marked `applied`, so confirming never replays them. Mercur’s product workflows call it in four places:
59
+
60
+ | Workflow | Action recorded | `details` |
61
+ | --- | --- | --- |
62
+ | `createProductsWorkflow` | `PRODUCT_ADD` | `{ status, attribute_ids }` |
63
+ | `confirmProductsWorkflow` | `STATUS_CHANGE` | `{ status: "published" }` |
64
+ | `rejectProductWorkflow` | `STATUS_CHANGE` | `{ status: "rejected" }` |
65
+ | `requestProductChangeWorkflow` | `CHANGE_REQUESTED` | `{ message }` |
66
+
67
+ The `PRODUCT_ADD` record does real work: its `created_by` is the seller who submitted the product, and the vendor API reads it to tell which products a seller created.
68
+
69
+ <Note>
70
+ Operator edits through the admin product update route run Medusa’s `updateProductsWorkflow` directly and don’t create a change. The pipeline governs seller edits.
71
+ </Note>
72
+
73
+ ## Related
63
74
 
64
- <Tip>
65
- Read a product's full history through the read-only `product.changes` link
66
- (see the [Links reference](/platform/product-edit/reference/links)) rather than
67
- querying the module directly. The link keeps the audit trail attached to the
68
- product.
69
- </Tip>
75
+ - [The action types and their `details`](/platform/product-edit/concepts/change-actions)
76
+ - [Statuses, revision requests, and auto-confirm](/platform/product-edit/concepts/status-and-auto-confirm)
77
+ - [Stage an edit from your own route](/platform/product-edit/guides/edit-a-product)
@@ -1,66 +1,63 @@
1
1
  ---
2
- title: "Status & auto-confirm"
2
+ title: "Change statuses, revision requests, and auto-confirm"
3
3
  sidebarTitle: "Status & auto-confirm"
4
- description: "The change status lifecycle, revision requests, and auto-confirm."
4
+ description: "The four ProductChangeStatus values, how a revision request fits in, and when a change confirms without review."
5
5
  ---
6
6
 
7
- In this document, you'll learn about the states a change moves through and when
8
- it resolves without operator review.
7
+ Every product change has a status that says whether it’s waiting for review or already resolved. A change starts `pending` and is resolved exactly once: confirmed, declined, or canceled. Whether a seller’s edit waits for an operator at all depends on the `product_request` feature flag and on the `auto_confirm` input.
9
8
 
10
- ## Status
9
+ ## The status lifecycle
11
10
 
12
- A change's state is held in the `status` field of the `ProductChange` model,
13
- typed by the `ProductChangeStatus` enum. A change moves through four statuses:
11
+ The `status` field takes one of the four `ProductChangeStatus` values, exported from `@mercurjs/types`. Each resolution has its own workflow, which checks that the change is still `pending`, stamps the actor field, and emits an event.
14
12
 
15
- ```
16
- ┌───────────┐
17
- │ pending │
18
- └─────┬─────┘
19
- ┌───────────┼───────────┐
20
- confirm decline cancel
21
- ▼ ▼ ▼
22
- ┌───────────┐ ┌──────────┐ ┌──────────┐
23
- │ confirmed │ │ declined │ │ canceled │
24
- └───────────┘ └──────────┘ └──────────┘
25
- ```
13
+ <img className="block dark:hidden" src="/images/figures/product-edit-lifecycle-light.svg" alt="The ProductChangeStatus table: a change enters pending through stageProductChangeWorkflow, then confirmProductChangeWorkflow, rejectProductChangeWorkflow, or cancelProductChangeWorkflow moves it to confirmed, declined, or canceled, each emitting its own event and stamping its own actor field." />
14
+ <img className="hidden dark:block" src="/images/figures/product-edit-lifecycle-dark.svg" alt="The ProductChangeStatus table: a change enters pending through stageProductChangeWorkflow, then confirmProductChangeWorkflow, rejectProductChangeWorkflow, or cancelProductChangeWorkflow moves it to confirmed, declined, or canceled, each emitting its own event and stamping its own actor field." />
15
+
16
+ | Status | Meaning | Product touched |
17
+ | --- | --- | --- |
18
+ | `pending` | Staged and waiting for review. The default | No |
19
+ | `confirmed` | Approved, or recorded as an audit change | Yes, its actions are applied |
20
+ | `declined` | Rejected by an operator, with an optional `declined_reason` | No |
21
+ | `canceled` | Withdrawn, by the seller or an operator, before review | No |
26
22
 
27
- | Status | Meaning |
28
- | --- | --- |
29
- | `pending` | Awaiting operator review. The default for a staged edit |
30
- | `confirmed` | Approved; its actions are applied to the product |
31
- | `declined` | Rejected by the operator; nothing is applied |
32
- | `canceled` | Withdrawn (e.g. by the vendor) before review |
23
+ Confirming, declining, or canceling a change that isn’t `pending` fails with a `NOT_ALLOWED` error. Each resolution step saves the previous values, so if a later step fails, the workflow restores the change to `pending`.
33
24
 
34
25
  <Note>
35
- Only a `pending` change can be confirmed, declined, or canceled. The
36
- resolution workflows validate the current status first, so a change is
37
- resolved exactly once.
26
+ The model also has `requires_action_by`, `requires_action_at`, and `requires_action_reason` columns, but `ProductChangeStatus` has no `requires_action` value and no workflow writes those columns.
38
27
  </Note>
39
28
 
40
29
  ## Revision requests
41
30
 
42
- When an operator wants a submission reworked rather than approved or rejected,
43
- they **request a revision**. This is recorded as a `CHANGE_REQUESTED` audit
44
- action (a `confirmed` audit change) carrying the operator's message, and it emits
45
- `product.change-requested`. The product stays with the vendor to revise and
46
- resubmit. A revision request is a signal in the audit trail, not a fourth
47
- resolution of the pending change.
31
+ A revision request sends a proposed product back to its seller without approving or rejecting it. `requestProductChangeWorkflow` checks that the product status is `proposed`, records an audit change with a `CHANGE_REQUESTED` action, and emits `product.change-requested`. The product stays `proposed` for the seller to revise.
32
+
33
+ A revision request isn’t a fifth status, and it doesn’t resolve any pending change. It’s an already-`confirmed` entry in the product’s history. The operator’s message is stored twice: in the action’s `details.message` and in the change’s `external_note`.
48
34
 
49
35
  ## Auto-confirm
50
36
 
51
- Staging a change runs `autoConfirmProductChangeWorkflow`. Whether it confirms
52
- immediately depends on the marketplace's review setting:
37
+ Staging always runs `autoConfirmProductChangeWorkflow` right after the change is created. It runs `confirmProductChangeWorkflow` in the same workflow run when either condition holds:
38
+
39
+ - **The flag is off**: the `product_request` feature flag is disabled
40
+ - **Forced**: the caller passed `auto_confirm: true`, which reaches the workflow as `force`
41
+
42
+ Otherwise the change stays `pending`. The flag is on by default. Turn it off with an environment variable:
43
+
44
+ ```bash title=".env"
45
+ MEDUSA_FF_PRODUCT_REQUEST=false
46
+ ```
47
+
48
+ With the flag off, every seller edit applies immediately and still leaves a confirmed change in the trail. The same flag also controls product creation: while it’s on, a seller can only create products as `draft` or `proposed`.
49
+
50
+ ## When Mercur forces auto-confirm
51
+
52
+ There’s no risk scoring: Mercur doesn’t classify edits as low or high risk. It forces auto-confirm in two places:
53
+
54
+ - **Draft deletes**: `productEditDeleteProductWorkflow` defaults `auto_confirm` to `true` when the product is a `draft`
55
+ - **Operator deletes**: the admin product delete route always passes `auto_confirm: true`
53
56
 
54
- - **Review off:** the change is confirmed and applied in the same run, so
55
- low-friction edits don't wait for an operator.
56
- - **Review on:** the change stays `pending` for an operator to resolve.
57
+ An auto-confirmed change goes through the same `confirmProductChangeWorkflow` as a manual approval. It emits `product-change.confirmed` and fires the same hooks, with `confirmed_by` set to the change’s author.
57
58
 
58
- You can also force auto-confirm for a specific change (for example a
59
- trusted-source import) via the `auto_confirm` flag on the staging workflow,
60
- regardless of the review setting.
59
+ ## Related
61
60
 
62
- <Tip>
63
- Auto-confirm reuses the exact same `confirmProductChangeWorkflow` as a manual
64
- approval, so an auto-confirmed change is applied and audited identically to one
65
- an operator approves by hand.
66
- </Tip>
61
+ - [Confirm, decline, or cancel a change in code](/platform/product-edit/guides/confirm-or-decline-a-change)
62
+ - [Request a revision](/platform/product-edit/guides/request-a-revision)
63
+ - [The events each transition emits](/platform/product-edit/reference/events)