@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,50 +1,74 @@
1
1
  ---
2
- title: "Service reference"
2
+ title: "Order group service methods"
3
3
  sidebarTitle: "Service"
4
- description: "The Seller module service and its methods for working with order group records directly."
4
+ description: "Seller module service methods for OrderGroup records, the filters they accept, and how they compute totals."
5
5
  ---
6
6
 
7
- Order groups are owned by the **Seller module**, so you resolve the same service
8
- you'd use for stores. Resolve it from the Medusa container to read and write
9
- `OrderGroup` records directly, without going through a workflow. Use it inside
10
- custom services, subscribers, or scheduled jobs.
7
+ Order group records live in the Seller module, so you read and write them through the Seller module service. Use it in subscribers, scheduled jobs, and custom steps when you need group rows without child orders. For child orders and their statuses, use the [read workflows](/platform/order-group/reference/workflows) instead.
8
+
9
+ ## Resolve the service
10
+
11
+ Resolve the Seller module from the Medusa container with `MercurModules.SELLER`. There is no `ORDER_GROUP` module key.
11
12
 
12
13
  ```ts
13
14
  import { MercurModules } from "@mercurjs/types"
14
15
 
15
16
  const sellerModuleService = container.resolve(MercurModules.SELLER)
16
17
 
17
- const [orderGroups, count] = await sellerModuleService.listAndCountOrderGroups({
18
- customer_id: "cus_123",
19
- })
18
+ const [groups, count] = await sellerModuleService.listAndCountOrderGroups(
19
+ { customer_id: "cus_01JD8Q1234567" },
20
+ { take: 20 }
21
+ )
20
22
  ```
21
23
 
22
- <Note>
23
- There is no `ORDER_GROUP` module key. The `OrderGroup` model is registered on
24
- the Seller module, so its service methods live on `MercurModules.SELLER`.
25
- </Note>
24
+ The call returns the customer’s 20 newest groups and the total number of groups that match.
26
25
 
27
- ## Generated methods
26
+ ## Read methods
28
27
 
29
- The `OrderGroup` model gets the standard set of auto-generated methods:
28
+ The Seller module overrides the generated read methods for `OrderGroup`. All three run one SQL query through `OrderGroupRepository`, which fills `seller_count` and `total`:
30
29
 
31
- | Method | Description |
30
+ | Method | Returns |
32
31
  | --- | --- |
33
- | `createOrderGroups(data)` | Create one or more order groups |
34
- | `retrieveOrderGroup(id, config?)` | Retrieve a group by id (with computed `seller_count` / `total`) |
35
- | `listOrderGroups(filters?, config?)` | List groups matching filters |
36
- | `listAndCountOrderGroups(filters?, config?)` | List groups with a total count |
37
- | `updateOrderGroups(data)` | Update one or more groups |
38
- | `deleteOrderGroups(ids)` | Delete one or more groups |
39
-
40
- The list, count, and retrieve methods run through the module's order-group
41
- repository, which aggregates each group's child orders to fill in the computed
42
- `seller_count` and `total`. Supported filters include `id`, `customer_id`,
43
- `seller_id`, `status`, `sales_channel_id`, `created_at`, `updated_at`, and `q`.
32
+ | `listOrderGroups(filters?, config?, sharedContext?)` | `Promise<OrderGroupDTO[]>` |
33
+ | `listAndCountOrderGroups(filters?, config?, sharedContext?)` | `Promise<[OrderGroupDTO[], number]>` |
34
+ | `retrieveOrderGroup(id, config?, sharedContext?)` | `Promise<OrderGroupDTO>` |
35
+
36
+ From `config`, the repository reads `take`, `skip`, and `order` only. It ignores `select` and `relations` and always returns every column plus the two computed fields.
44
37
 
45
38
  <Warning>
46
- Prefer [workflows](/platform/order-group/reference/workflows) for reads that
47
- need aggregated child-order status, and for the checkout split. The service
48
- returns the group row and its computed totals but does **not** expand child
49
- orders' payment/fulfillment status, emit events, or run compensation.
39
+ `retrieveOrderGroup` doesn’t throw when no group has the id: it resolves to `undefined`. Check the value before you use it, or use `getOrderGroupDetailWorkflow`, which throws `NOT_FOUND`.
50
40
  </Warning>
41
+
42
+ ## Write methods
43
+
44
+ The write methods are the ones `MedusaService` generates for the model:
45
+
46
+ | Method | Description |
47
+ | --- | --- |
48
+ | `createOrderGroups(data)` | Creates one group from a `CreateOrderGroupDTO`, or several from an array |
49
+ | `updateOrderGroups(data)` | Updates groups from `UpdateOrderGroupDTO` objects |
50
+ | `deleteOrderGroups(ids)` | Deletes groups by id |
51
+ | `softDeleteOrderGroups(ids)` | Sets `deleted_at` on groups |
52
+ | `restoreOrderGroups(ids)` | Clears `deleted_at` on groups |
53
+
54
+ Creating a group doesn’t create orders or links. Checkout does that in `completeCartWithSplitOrdersWorkflow`, so prefer the workflow over calling `createOrderGroups` yourself.
55
+
56
+ ## Filters and sorting
57
+
58
+ The repository builds its own SQL and reads only the top-level keys below. It ignores every other key, including `$and` and `$or`.
59
+
60
+ | Filter | Matches |
61
+ | --- | --- |
62
+ | `id` | Group id, as a string or an array |
63
+ | `customer_id` | Customer id, as a string or an array |
64
+ | `cart_id` | Cart id, as a string or an array |
65
+ | `seller_id` | Groups with at least one child order from the seller |
66
+ | `status` | Groups with at least one child order in the status |
67
+ | `payment_status` | Groups with at least one child order in the derived payment status |
68
+ | `sales_channel_id` | Groups with at least one child order in the sales channel |
69
+ | `created_at`, `updated_at` | A date or an operator map: `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte`, `$in`, `$nin`, `$like`, `$ilike` |
70
+ | `q` | Group id or customer id containing the text, or a display id such as `#G98`, `G98`, or `98` on the group or a child order |
71
+
72
+ The order-level filters only choose which groups match. `seller_count` and `total` still cover every order in each matching group. Pass `id` as a plain string or array: an `{ $in: [...] }` map isn’t read.
73
+
74
+ `config.order` sorts by `display_id`, `created_at`, or `updated_at`, with `ASC` or `DESC`. Other keys are ignored. Without a valid key, groups come back by `created_at` descending.
@@ -1,39 +1,98 @@
1
1
  ---
2
- title: "Workflows"
2
+ title: "Order group workflows"
3
3
  sidebarTitle: "Workflows"
4
- description: "Order group workflows, service methods, and events."
4
+ description: "Input, output, hooks, steps, and events of the checkout split and the order group read workflows."
5
5
  ---
6
6
 
7
- This reference lists the workflows, service methods, and events for the Order
8
- Group domain. Import workflows from `@mercurjs/core/workflows` and run them
9
- against the Medusa container.
7
+ Three workflows and one step make up the order group surface: the checkout split that creates groups, and two read workflows that return groups with derived child order statuses. Import them from `@mercurjs/core/workflows` and run them with the Medusa container.
10
8
 
11
- ## Checkout workflow
9
+ ```ts
10
+ import {
11
+ completeCartWithSplitOrdersWorkflow,
12
+ createOrderGroupStep,
13
+ getOrderGroupDetailWorkflow,
14
+ getOrderGroupsListWorkflow,
15
+ } from "@mercurjs/core/workflows"
16
+ ```
12
17
 
13
- | Workflow | Input | Purpose |
18
+ ## completeCartWithSplitOrdersWorkflow
19
+
20
+ Completes a cart by creating one order per seller under a new order group. Workflow id `complete-cart-with-split-orders`. Mercur’s cart completion route and `processPaymentWorkflow` both run it.
21
+
22
+ | Input | Type | Description |
14
23
  | --- | --- | --- |
15
- | `completeCartWithSplitOrdersWorkflow` | `{ cart_id }` | Split a multi-seller cart into per-seller orders and create the parent `OrderGroup`. Returns `{ order_group_id }`. Idempotent per cart. |
24
+ | `cart_id` | `string` | The cart to complete |
25
+
26
+ It returns `{ order_group_id: string }`. If a group with the same `cart_id` already exists, the workflow skips the split and returns that group’s id.
16
27
 
17
- ## Read workflows
28
+ The workflow stores its execution (`store: true`) and keeps it for 3 days. It holds a lock on the cart id with a 30-second wait timeout and a 2-minute time to live, so concurrent completions of one cart run one at a time.
18
29
 
19
- | Workflow | Input | Purpose |
30
+ ### Hooks
31
+
32
+ | Hook | Receives | Use it to |
20
33
  | --- | --- | --- |
21
- | `getOrderGroupDetailWorkflow` | `{ order_group_id, fields }` | Load one group with its child orders and derived per-order payment/fulfillment status |
22
- | `getOrderGroupsListWorkflow` | `{ fields, variables?, sellerId? }` | List groups with a count; `sellerId` scopes each group's child orders to a seller |
34
+ | `validate` | `{ input, cart }` | Throw to stop the checkout before anything is created |
35
+ | `setOrderStatus` | `{ cart }` | Return `{ status?, status_by_seller_id? }` to set each child order’s initial status; the default is `pending` |
36
+
37
+ `SetOrderStatusHookResult` is exported with the workflow. `status_by_seller_id` is keyed by seller id and takes precedence over `status`.
38
+
39
+ ### Steps
40
+
41
+ The workflow runs these steps in order. Steps 3 to 6 run only when no group exists for the cart:
42
+
43
+ 1. `acquireLockStep` on the cart id, then a query for an existing group and the cart.
44
+ 2. `validateCartPaymentsStep`, then the `validate` and `setOrderStatus` hooks.
45
+ 3. `validateSellerCartItemsStep` and `validateSellerCartShippingStep`.
46
+ 4. `createOrderGroupStep` and `createOrdersStep`, in parallel.
47
+ 5. `createRemoteLinkStep`, `mirrorLineItemOfferLinksToOrderStep`, `updateCartsStep` to set `completed_at`, `reserveInventoryStep`, `registerUsageStep`, and two `emitEventStep` calls.
48
+ 6. `authorizePaymentSessionStep`, then `splitCapturedCartPaymentWorkflow` and `refreshOrderCommissionLinesWorkflow` as steps.
49
+ 7. `releaseLockStep` on the cart id.
50
+
51
+ ### Events
52
+
53
+ | Event | Payload |
54
+ | --- | --- |
55
+ | `order.placed` | `{ id }`, once per child order |
56
+ | `order_group.created` | `{ id }` of the new group |
57
+
58
+ ## getOrderGroupDetailWorkflow
59
+
60
+ Retrieves one order group with its child orders and sets `payment_status` and `fulfillment_status` on each order. Workflow id `get-order-group-detail`. Its input type is `GetOrderGroupDetailWorkflowInput`.
61
+
62
+ | Input | Type | Description |
63
+ | --- | --- | --- |
64
+ | `order_group_id` | `string` | The group to retrieve |
65
+ | `fields` | `string[]` | Fields to return, in `query.graph` syntax |
66
+
67
+ It returns the group. The workflow always adds `id`, `orders.id`, `orders.status`, `orders.version`, `orders.currency_code`, and `orders.items.*` to your fields, plus the payment collection and fulfillment fields it needs for the statuses. It throws `NOT_FOUND` when no group has the id.
68
+
69
+ The workflow removes some data it loaded for itself before it returns:
70
+
71
+ - **`orders.payment_collections`**: kept only when a requested field contains `payment_collections`
72
+ - **`orders.fulfillments`**: kept only when a requested field contains `fulfillments`
73
+ - **`orders.cart`**: kept only when a requested field starts with `orders.cart`
74
+
75
+ ## getOrderGroupsListWorkflow
76
+
77
+ Lists order groups with their child orders and derived statuses. Workflow id `get-order-groups-list`. Its input type is `GetOrderGroupsListWorkflowInput`.
78
+
79
+ | Input | Type | Description |
80
+ | --- | --- | --- |
81
+ | `fields` | `string[]` | Fields to return, in `query.graph` syntax |
82
+ | `variables` | `object`, optional | Filters plus `skip`, `take`, and `order` |
83
+ | `sellerId` | `string \| string[]`, optional | Removes other sellers’ orders from each returned group |
84
+
85
+ It returns `{ rows, metadata }`, where `metadata` holds `count`, `skip`, and `take`. It adds the same child order fields as the detail workflow and removes the same data, and adds `orders.seller.id` when you pass `sellerId`.
86
+
87
+ `sellerId` trims orders but doesn’t filter groups. Pass `variables.seller_id` as well to return only groups where the seller has an order. The filter keys are listed in [Filters and sorting](/platform/order-group/reference/service#filters-and-sorting).
23
88
 
24
- <Note>
25
- The read workflows always expand the group's child `orders`. Heavy relations
26
- (`payment_collections`, `fulfillments`) are only kept in the response when you
27
- request a matching field, and are used to derive each order's `payment_status`
28
- and `fulfillment_status`.
29
- </Note>
89
+ ## createOrderGroupStep
30
90
 
31
- ## Step
91
+ Creates one `OrderGroup` record. Step id `create-order-group`.
32
92
 
33
- | Step | Input | Purpose |
93
+ | Input | Type | Description |
34
94
  | --- | --- | --- |
35
- | `createOrderGroupStep` | `{ customer_id?, cart_id }` | Create the `OrderGroup` record (used inside the checkout workflow, with a compensating delete) |
95
+ | `cart_id` | `string` | The completed cart |
96
+ | `customer_id` | `string \| null`, optional | The customer on the cart |
36
97
 
37
- To work with records directly instead of through a workflow, see the
38
- [Service reference](/platform/order-group/reference/service). To run side effects
39
- when a group is created, see the [Event reference](/platform/order-group/reference/events).
98
+ It returns the created group. On rollback, it deletes the group with `deleteOrderGroups`. Use it in your own workflow only when you also create and link the child orders yourself.
@@ -1,75 +1,72 @@
1
1
  ---
2
- title: "Account lifecycle"
2
+ title: "Payout account and payout statuses"
3
3
  sidebarTitle: "Account lifecycle"
4
- description: "Payout account statuses and the webhook-driven transitions between them."
4
+ description: "The statuses a payout account and a payout move through, and the provider webhooks that move them."
5
5
  ---
6
6
 
7
- In this document, you'll learn about the payout account lifecycle and how its
8
- status stays in sync with the provider.
9
-
10
- ## Status
11
-
12
- A payout account's state is held in the `status` field of the `PayoutAccount`
13
- model, typed by the `PayoutAccountStatus` enum. An account moves through four
14
- statuses.
15
-
16
- ```
17
- ┌──────────┐
18
- │ PENDING │
19
- └────┬─────┘
20
- │ account.activated
21
- ▼
22
- ┌────────────┐ ┌────────┐
23
- │ RESTRICTED │◄─►│ ACTIVE │
24
- └────────────┘ └───┬────┘
25
- │ account.rejected
26
- ▼
27
- ┌──────────┐
28
- │ REJECTED │
29
- └──────────┘
30
- ```
31
-
32
- | Status | Meaning |
33
- | --- | --- |
34
- | `PENDING` | Account created, provider onboarding not yet complete. |
35
- | `ACTIVE` | Fully onboarded. Can receive payouts. |
36
- | `RESTRICTED` | Provider flagged the account, for example missing KYC. No payouts. |
37
- | `REJECTED` | Provider permanently disabled the account. |
7
+ A payout account’s status says whether the seller can receive money, and a payout’s status says where one transfer stands. Neither is set by an operator. The provider decides, sends a webhook, and `processPayoutForWebhookWorkflow` writes the new status. Read this page to know when `createPayoutWorkflow` will accept an account, and what each webhook does.
8
+
9
+ ## Account statuses
10
+
11
+ An account’s state is the `status` field on `PayoutAccount`, typed by the `PayoutAccountStatus` enum from `@mercurjs/types`. It defaults to `pending`, and the provider’s `createPayoutAccount` response can overwrite it.
12
+
13
+ | Status | Value | Meaning |
14
+ | --- | --- | --- |
15
+ | `PENDING` | `pending` | Created, setup not finished |
16
+ | `ACTIVE` | `active` | Can receive payouts |
17
+ | `RESTRICTED` | `restricted` | Missing information or a compliance issue |
18
+ | `REJECTED` | `rejected` | Permanently disabled by the provider |
19
+
20
+ Only `active` matters to the rest of the module. `createPayouts` throws `Account 'pacc_…' is not active` for any other status, so a payout never starts on a pending, restricted, or rejected account.
21
+
22
+ ## Webhooks set the account status
23
+
24
+ The provider owns the account status, and a webhook carries each change. The provider’s `getWebhookActionAndData` turns a raw webhook into an action and the `pacc` id it applies to. `processPayoutForWebhookWorkflow` then runs the matching step.
25
+
26
+ <img className="block dark:hidden" src="/images/figures/payout-account-lifecycle-light.svg" alt="A Stripe account.updated webhook behind the process-payout-for-webhook workflow. The workflow maps account.activated to the activate-payout-account step and the Active status, account.restricted to Restricted, account.rejected to Rejected, and not_supported to no change." />
27
+ <img className="hidden dark:block" src="/images/figures/payout-account-lifecycle-dark.svg" alt="A Stripe account.updated webhook behind the process-payout-for-webhook workflow. The workflow maps account.activated to the activate-payout-account step and the Active status, account.restricted to Restricted, account.rejected to Rejected, and not_supported to no change." />
28
+
29
+ The workflow doesn’t check the current status before it writes. Any action can move an account from any status, so the provider’s view always wins:
30
+
31
+ | Action | Step | New status |
32
+ | --- | --- | --- |
33
+ | `account.activated` | `activate-payout-account` | `active` |
34
+ | `account.restricted` | `restrict-payout-account` | `restricted` |
35
+ | `account.rejected` | `reject-payout-account` | `rejected` |
36
+ | `not_supported` | none | unchanged |
37
+
38
+ Each step is `updatePayoutAccountStep`, renamed with `.config({ name })`. It saves the previous status and restores it if the workflow rolls back.
39
+
40
+ ## How Stripe Connect maps account updates
41
+
42
+ The Stripe Connect provider handles only Stripe’s `account.updated` event, and it reads the `pacc` id from the Stripe account’s `metadata.account_id`. It picks the action in this order:
43
+
44
+ 1. `account.rejected` when `requirements.disabled_reason` starts with `rejected.`
45
+ 2. `account.activated` when every enabled `accountValidation` check passes
46
+ 3. `account.restricted` in every other case
47
+
48
+ The default checks are `detailsSubmitted`, `chargesEnabled`, `payoutsEnabled`, and `noOutstandingRequirements`, all `true`, with no `requiredCapabilities`.
38
49
 
39
50
  <Note>
40
- Payouts are only created against an `ACTIVE` account. The module rejects a
41
- payout for an account in any other status.
51
+ With Stripe Connect, an account can skip `pending` and go to `restricted` first. Stripe sends `account.updated` while the seller is still onboarding, and an account with outstanding requirements maps to `account.restricted`. It becomes `active` on a later update.
42
52
  </Note>
43
53
 
44
- ## Webhook-driven transitions
45
-
46
- Unlike the store lifecycle, payout account transitions are **not** operator
47
- actions. They follow the provider. The provider sends a webhook, a subscriber
48
- resolves it to an action, and `processPayoutForWebhookWorkflow` updates the
49
- status.
50
-
51
- | Webhook action | Resulting status |
52
- | --- | --- |
53
- | `account.activated` | `ACTIVE` |
54
- | `account.restricted` | `RESTRICTED` |
55
- | `account.rejected` | `REJECTED` |
56
-
57
- ```ts
58
- // Inside processPayoutForWebhookWorkflow
59
- when({ input }, ({ input }) => input.action === "account.activated")
60
- .then(() =>
61
- updatePayoutAccountStep({ id: input.data!.id, status: PayoutAccountStatus.ACTIVE })
62
- )
63
- ```
64
-
65
- <Tip>
66
- A `RESTRICTED` account is not terminal. Once the seller resolves the
67
- provider's requirements, the provider emits `account.activated` again and the
68
- account returns to `ACTIVE`. `REJECTED` is the only permanent state.
69
- </Tip>
70
-
71
- ## Payout status
72
-
73
- An individual transfer carries its own `PayoutStatus` (`PENDING` → `PROCESSING`
74
- → `PAID`, or `FAILED` / `CANCELED`). Provider webhooks advance it through the
75
- same workflow. See [The payout pipeline](/platform/payout/concepts/payout-pipeline).
54
+ ## Payout statuses
55
+
56
+ Each payout carries its own `PayoutStatus`. The module creates the row as `pending`, then stores the status the provider’s `createPayout` returns.
57
+
58
+ | Status | Value | Set by |
59
+ | --- | --- | --- |
60
+ | `PENDING` | `pending` | Row creation |
61
+ | `PROCESSING` | `processing` | A `payout.processing` webhook |
62
+ | `PAID` | `paid` | The provider response or a `payout.paid` webhook |
63
+ | `FAILED` | `failed` | A `payout.failed` webhook |
64
+ | `CANCELED` | `canceled` | A `payout.canceled` webhook |
65
+
66
+ The Stripe Connect provider returns `paid` as soon as Stripe creates the transfer, and it maps no Stripe event to a `payout.*` action. The `payout.*` branches exist for providers that settle asynchronously.
67
+
68
+ ## Related
69
+
70
+ - [Process a provider webhook](/platform/payout/guides/process-a-provider-webhook) to see the webhook path end to end
71
+ - [The payout pipeline](/platform/payout/concepts/payout-pipeline) for what happens once an account is active
72
+ - [Workflows reference](/platform/payout/reference/workflows) for every step name
@@ -1,70 +1,80 @@
1
1
  ---
2
- title: "Accounts & onboarding"
2
+ title: "Payout accounts and onboarding"
3
3
  sidebarTitle: "Accounts & onboarding"
4
- description: "The payout account, its onboarding record, and provider-specific data."
4
+ description: "How a seller connects to the payout provider, and where provider data such as account ids and onboarding links lives."
5
5
  ---
6
6
 
7
- In this document, you'll learn how a seller connects to a payment provider and
8
- where provider-specific data lives.
7
+ A payout account is a seller’s connection to the payout provider, and onboarding is the provider-side setup the seller completes before money can reach them. You need both before you can pay a seller. Mercur stores the provider’s own data on each record without reading it, so the same records work for any provider.
9
8
 
10
- ## Payout account
9
+ ## The payout account
11
10
 
12
- A payout account is the seller's connection to the payment provider. It is the
13
- record funds are transferred to. A payout account is represented by the
14
- `PayoutAccount` data model (table `payout_account`, id prefix `pacc`). It is
15
- created for a seller through `createPayoutAccountWorkflow`, which also links the
16
- account to the store.
11
+ A payout account is the record a payout is sent to. It’s the `PayoutAccount` data model (table `payout_account`, id prefix `pacc`), and the `seller_payout_account` link ties it to exactly one seller.
12
+
13
+ You create one with `createPayoutAccountWorkflow`. The workflow checks the seller has no account yet, asks the module to create the account, and links it to the seller:
17
14
 
18
15
  ```ts
16
+ import { createPayoutAccountWorkflow } from "@mercurjs/core/workflows"
17
+
19
18
  const { result } = await createPayoutAccountWorkflow(container).run({
20
19
  input: {
21
- seller_id: "sel_123",
22
- context: { /* forwarded to the provider */ },
23
- data: { /* forwarded to the provider */ },
20
+ seller_id: "sel_01JD8Q1234567",
21
+ data: { country: "US" },
24
22
  },
25
23
  })
26
24
  ```
27
25
 
28
- Creating an account is a two-step operation. The module first persists the
29
- `PayoutAccount`. It then calls the provider to create the connected account and
30
- stores what the provider returns in the account's `data` field. A seller has
31
- **exactly one** payout account.
26
+ Inside the module, `createPayoutAccount` runs 3 writes in order:
27
+
28
+ 1. Create the `PayoutAccount` row with your `data` and `context`
29
+ 2. Call the provider’s `createPayoutAccount` with `data.account_id` set to the new `pacc` id and `context.idempotency_key` defaulting to the same id
30
+ 3. Overwrite the row’s `data` and `status` with what the provider returns
31
+
32
+ If the provider call throws, the module deletes the new row and rethrows. The Stripe Connect provider returns `pending` and the full Stripe account object. The built-in `system` provider returns `active` and an empty object.
32
33
 
33
34
  <Note>
34
- A store can only have **one** payout account. `createPayoutAccountWorkflow`
35
- validates that the seller doesn't already have one before creating it.
35
+ A seller can have only one payout account. `validateSellerPayoutAccountStep` queries `seller_payout_account` and fails with `Seller already has a payout account` when a row exists.
36
36
  </Note>
37
37
 
38
38
  ## Onboarding
39
39
 
40
- Before an account can receive funds, the seller usually has to complete
41
- provider-side setup, such as identity verification, bank details, or KYC. That
42
- state is held in the `Onboarding` data model (table `onboarding`, id prefix
43
- `onb`). The record is a one-to-one satellite of the payout account.
40
+ Onboarding is the provider’s setup flow: identity checks, bank details, and anything else the provider needs. Its state lives in the `Onboarding` data model (table `onboarding`, id prefix `onb`), a one-to-one child of the payout account.
41
+
42
+ `createOnboardingWorkflow` takes an `account_id` and calls the provider’s `createOnboarding`. The module merges the account’s stored `data` with the `data` you pass, so the provider sees both. With Stripe Connect, the account’s `data.id` is the Stripe account id, and you pass `refresh_url` and `return_url`:
44
43
 
45
44
  ```ts
46
- await createOnboardingWorkflow(container).run({
45
+ import { createOnboardingWorkflow } from "@mercurjs/core/workflows"
46
+
47
+ const { result } = await createOnboardingWorkflow(container).run({
47
48
  input: {
48
- account_id: "pacc_123",
49
- context: { return_url: "https://store.example.com/settings/payouts" },
49
+ account_id: "pacc_01JD8Q1234567",
50
+ data: {
51
+ refresh_url: "https://vendor.example.com/payouts/refresh",
52
+ return_url: "https://vendor.example.com/payouts",
53
+ },
50
54
  },
51
55
  })
52
56
  ```
53
57
 
54
- The workflow asks the provider to produce onboarding data (for Stripe Connect,
55
- an onboarding link) and stores it on the record. Running it again on an account
56
- that already has an onboarding record **updates** it rather than creating a
57
- second one.
58
+ The result’s `data` holds what the provider returned. For Stripe Connect, that’s an account link object, and `data.url` is the hosted onboarding page. The workflow is safe to re-run:
59
+
60
+ - **First run**: creates the `Onboarding` record
61
+ - **Later runs**: update the same record with new provider data, which you need when a Stripe link expires
62
+
63
+ ## Provider data and context
64
+
65
+ Every payout record has a `data` JSON field, and accounts and onboarding records also have `context`. They play different roles:
66
+
67
+ - **`data`**: the provider’s durable payload, such as the Stripe account, the account link, or the transfer
68
+ - **`context`**: per-call hints for the provider, today only `idempotency_key`
69
+
70
+ The module never interprets `data`. It passes it to the provider and stores what comes back. When you switch providers, the records keep the same shape.
58
71
 
59
- ## Provider data
72
+ <Warning>
73
+ A payout account’s `data` holds the raw provider object. For Stripe Connect, that’s the full `Stripe.Account`, so select only the fields you need before you return it from a public route.
74
+ </Warning>
60
75
 
61
- The `data` JSON field on `PayoutAccount`, `Onboarding`, and `Payout` is where
62
- provider-specific values live, such as the Stripe account id, onboarding URLs, or
63
- transfer references. The module never interprets these fields. It forwards them
64
- to and from the provider.
76
+ ## Related
65
77
 
66
- <Tip>
67
- `context` carries per-request hints such as an `idempotency_key` or a
68
- `return_url`, while `data` carries the durable provider payload. Both are
69
- passed straight through the `IPayoutProvider` interface.
70
- </Tip>
78
+ - [Account lifecycle](/platform/payout/concepts/account-lifecycle) for the statuses an account moves through after onboarding
79
+ - [Create a payout account](/platform/payout/guides/create-a-payout-account) for a full route example
80
+ - [Start provider onboarding](/platform/payout/guides/start-provider-onboarding) for the onboarding route