@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.
- package/content/learn/architecture.mdx +166 -263
- package/content/platform/attribute/concepts/attribute-types.mdx +73 -46
- package/content/platform/attribute/concepts/global-vs-inline.mdx +54 -32
- package/content/platform/attribute/concepts/variant-axes.mdx +54 -32
- package/content/platform/attribute/guides/attach-attributes-to-a-product.mdx +96 -53
- package/content/platform/attribute/guides/create-a-variant-axis.mdx +106 -57
- package/content/platform/attribute/guides/create-an-attribute.mdx +105 -56
- package/content/platform/attribute/overview.mdx +37 -33
- package/content/platform/attribute/reference/data-models.mdx +53 -38
- package/content/platform/attribute/reference/events.mdx +47 -24
- package/content/platform/attribute/reference/links.mdx +98 -27
- package/content/platform/attribute/reference/service.mdx +71 -32
- package/content/platform/attribute/reference/workflows.mdx +158 -28
- package/content/platform/catalog/concepts/master-products.mdx +39 -37
- package/content/platform/catalog/concepts/product-seller-allowlist.mdx +56 -35
- package/content/platform/catalog/concepts/status-lifecycle.mdx +55 -49
- package/content/platform/catalog/concepts/variants-categories-collections.mdx +55 -35
- package/content/platform/catalog/guides/allowlist-stores.mdx +100 -37
- package/content/platform/catalog/guides/create-a-master-product.mdx +124 -48
- package/content/platform/catalog/guides/publish-or-reject-a-product.mdx +120 -55
- package/content/platform/catalog/overview.mdx +39 -35
- package/content/platform/catalog/reference/data-models.mdx +154 -43
- package/content/platform/catalog/reference/events.mdx +41 -25
- package/content/platform/catalog/reference/links.mdx +196 -28
- package/content/platform/catalog/reference/service.mdx +57 -35
- package/content/platform/catalog/reference/workflows.mdx +124 -30
- package/content/platform/commission/concepts/order-commission-lines.mdx +54 -52
- package/content/platform/commission/concepts/rule-matching.mdx +53 -63
- package/content/platform/commission/concepts/rules-and-rates.mdx +61 -59
- package/content/platform/commission/guides/batch-update-rules.mdx +69 -41
- package/content/platform/commission/guides/create-a-rate.mdx +101 -53
- package/content/platform/commission/guides/refresh-order-commission-lines.mdx +104 -35
- package/content/platform/commission/overview.mdx +43 -35
- package/content/platform/commission/reference/data-models.mdx +59 -45
- package/content/platform/commission/reference/events.mdx +28 -34
- package/content/platform/commission/reference/links.mdx +42 -33
- package/content/platform/commission/reference/service.mdx +103 -28
- package/content/platform/commission/reference/workflows.mdx +101 -27
- package/content/platform/offer/concepts/pricing-and-inventory.mdx +63 -49
- package/content/platform/offer/concepts/shipping.mdx +28 -29
- package/content/platform/offer/concepts/what-is-an-offer.mdx +48 -45
- package/content/platform/offer/guides/bulk-create-offers.mdx +97 -61
- package/content/platform/offer/guides/create-an-offer.mdx +112 -60
- package/content/platform/offer/guides/manage-offer-inventory.mdx +78 -38
- package/content/platform/offer/overview.mdx +39 -32
- package/content/platform/offer/reference/data-models.mdx +77 -47
- package/content/platform/offer/reference/events.mdx +44 -18
- package/content/platform/offer/reference/links.mdx +92 -40
- package/content/platform/offer/reference/service.mdx +66 -31
- package/content/platform/offer/reference/workflows.mdx +146 -37
- package/content/platform/order-group/concepts/computed-totals.mdx +40 -37
- package/content/platform/order-group/concepts/order-splitting.mdx +41 -39
- package/content/platform/order-group/concepts/the-order-group.mdx +39 -32
- package/content/platform/order-group/guides/list-order-groups.mdx +96 -56
- package/content/platform/order-group/guides/retrieve-an-order-group.mdx +93 -42
- package/content/platform/order-group/guides/split-a-cart.mdx +115 -40
- package/content/platform/order-group/overview.mdx +42 -32
- package/content/platform/order-group/reference/data-models.mdx +48 -31
- package/content/platform/order-group/reference/events.mdx +29 -17
- package/content/platform/order-group/reference/links.mdx +90 -29
- package/content/platform/order-group/reference/service.mdx +55 -31
- package/content/platform/order-group/reference/workflows.mdx +83 -24
- package/content/platform/payout/concepts/account-lifecycle.mdx +64 -67
- package/content/platform/payout/concepts/accounts-and-onboarding.mdx +50 -40
- package/content/platform/payout/concepts/payout-pipeline.mdx +70 -73
- package/content/platform/payout/guides/create-a-payout-account.mdx +65 -41
- package/content/platform/payout/guides/process-a-provider-webhook.mdx +84 -38
- package/content/platform/payout/guides/start-provider-onboarding.mdx +90 -46
- package/content/platform/payout/overview.mdx +43 -34
- package/content/platform/payout/reference/data-models.mdx +63 -36
- package/content/platform/payout/reference/events.mdx +56 -30
- package/content/platform/payout/reference/links.mdx +52 -22
- package/content/platform/payout/reference/service.mdx +72 -33
- package/content/platform/payout/reference/workflows.mdx +80 -19
- package/content/platform/product-edit/concepts/change-actions.mdx +52 -45
- package/content/platform/product-edit/concepts/change-pipeline.mdx +60 -52
- package/content/platform/product-edit/concepts/status-and-auto-confirm.mdx +43 -46
- package/content/platform/product-edit/guides/confirm-or-decline-a-change.mdx +104 -56
- package/content/platform/product-edit/guides/edit-a-product.mdx +126 -46
- package/content/platform/product-edit/guides/request-a-revision.mdx +99 -39
- package/content/platform/product-edit/overview.mdx +41 -34
- package/content/platform/product-edit/reference/data-models.mdx +73 -47
- package/content/platform/product-edit/reference/events.mdx +39 -29
- package/content/platform/product-edit/reference/links.mdx +32 -24
- package/content/platform/product-edit/reference/service.mdx +45 -26
- package/content/platform/product-edit/reference/workflows.mdx +173 -31
- package/content/platform/review/concepts/product-vs-seller-reviews.mdx +42 -41
- package/content/platform/review/concepts/ratings-and-moderation.mdx +45 -44
- package/content/platform/review/concepts/the-review-model.mdx +52 -45
- package/content/platform/review/guides/compute-aggregate-ratings.mdx +62 -37
- package/content/platform/review/guides/create-a-review.mdx +109 -39
- package/content/platform/review/guides/moderate-a-review.mdx +83 -37
- package/content/platform/review/guides/respond-to-a-review.mdx +85 -40
- package/content/platform/review/overview.mdx +43 -33
- package/content/platform/review/reference/data-models.mdx +41 -27
- package/content/platform/review/reference/events.mdx +55 -37
- package/content/platform/review/reference/links.mdx +80 -22
- package/content/platform/review/reference/service.mdx +61 -33
- package/content/platform/review/reference/workflows.mdx +80 -24
- package/content/platform/store/concepts/lifecycle.mdx +68 -44
- package/content/platform/store/concepts/store-entity.mdx +45 -27
- package/content/platform/store/concepts/team.mdx +56 -35
- package/content/platform/store/guides/create-a-store.mdx +102 -35
- package/content/platform/store/guides/manage-the-team.mdx +122 -32
- package/content/platform/store/guides/moderate-a-store.mdx +107 -38
- package/content/platform/store/overview.mdx +41 -34
- package/content/platform/store/reference/data-models.mdx +132 -57
- package/content/platform/store/reference/events.mdx +42 -17
- package/content/platform/store/reference/links.mdx +67 -49
- package/content/platform/store/reference/service.mdx +105 -33
- package/content/platform/store/reference/workflows.mdx +304 -24
- package/content/references/panel-extensions/layouts.mdx +169 -0
- package/llms.txt +112 -111
- package/package.json +1 -1
|
@@ -1,31 +1,87 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Review workflows"
|
|
3
3
|
sidebarTitle: "Workflows"
|
|
4
|
-
description: "
|
|
4
|
+
description: "The four review workflows, their inputs, steps, compensation, and errors."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
`@mercurjs/core/workflows` and run them against the Medusa container.
|
|
7
|
+
Core ships four review workflows in `packages/core/src/workflows/review/`. Import them from `@mercurjs/core/workflows` and run them with the Medusa container. None of them exposes a workflow hook, and none emits an event of its own: the events come from the module service, as listed in the [Events reference](/platform/review/reference/events).
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
```ts
|
|
10
|
+
import { createReviewWorkflow } from "@mercurjs/core/workflows"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const { result: review } = await createReviewWorkflow(container).run({
|
|
13
|
+
input: {
|
|
14
|
+
order_id: "order_01JD8Q1234567",
|
|
15
|
+
reference: "seller",
|
|
16
|
+
reference_id: "sel_01JD8Q1234567",
|
|
17
|
+
rating: 5,
|
|
18
|
+
customer_id: "cus_01JD8Q1234567",
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## createReviewWorkflow
|
|
24
|
+
|
|
25
|
+
Validates a submission, creates a `pending` review, and writes its customer, order, and target links. Workflow id `create-review`. Input type `CreateReviewDTO`, returns the created review.
|
|
26
|
+
|
|
27
|
+
| Field | Type | Description |
|
|
28
|
+
| --- | --- | --- |
|
|
29
|
+
| `order_id` | `string` | The order that earned the review. Must belong to `customer_id` |
|
|
30
|
+
| `reference` | `"product" \| "seller"` | The target type |
|
|
31
|
+
| `reference_id` | `string` | The product id or seller id |
|
|
32
|
+
| `rating` | `number` | The score. Not range checked |
|
|
33
|
+
| `customer_note` | `string \| null` | Optional. Defaults to `null` |
|
|
34
|
+
| `customer_id` | `string` | The customer writing the review |
|
|
35
|
+
|
|
36
|
+
The workflow runs three steps:
|
|
37
|
+
|
|
38
|
+
1. **`validate-review`**: fails with `INVALID_DATA` “Order not found!” when no order matches `order_id` and `customer_id`, and with “Review already exists” when the order has a review with the same `reference` and target id
|
|
39
|
+
2. **`create-review`**: creates the review and links it to the customer and the order; on rollback, deletes the review
|
|
40
|
+
3. **`create-remote-links`**: Medusa’s `createRemoteLinkStep`, which links the review to the product or the seller
|
|
41
|
+
|
|
42
|
+
## updateReviewWorkflow
|
|
43
|
+
|
|
44
|
+
Updates any of a review’s editable fields, including `status`. Workflow id `update-review`. Input type `UpdateReviewDTO`, returns the updated review.
|
|
45
|
+
|
|
46
|
+
| Field | Type | Description |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `id` | `string` | The review id |
|
|
49
|
+
| `rating` | `number` | Optional |
|
|
50
|
+
| `customer_note` | `string \| null` | Optional |
|
|
51
|
+
| `seller_note` | `string \| null` | Optional. Pass `null` to clear an answer |
|
|
52
|
+
| `status` | `"pending" \| "published" \| "rejected"` | Optional. Any value from any status |
|
|
53
|
+
|
|
54
|
+
Its single step, `update-review`, reads the review before writing. On rollback it restores the previous `rating`, `customer_note`, `seller_note`, and `status`.
|
|
55
|
+
|
|
56
|
+
## respondReviewWorkflow
|
|
57
|
+
|
|
58
|
+
Writes a store’s answer to `seller_note`, once. Workflow id `respond-review`. Input type `RespondReviewDTO`, returns the updated review.
|
|
59
|
+
|
|
60
|
+
| Field | Type | Description |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `id` | `string` | The review id |
|
|
63
|
+
| `seller_note` | `string` | The answer |
|
|
64
|
+
|
|
65
|
+
Its single step, `respond-review`, fails with `NOT_FOUND` “Review not found” for an unknown id and with `NOT_ALLOWED` “This review already has a response” when `seller_note` is set. On rollback it sets `seller_note` back to `null`. The workflow doesn’t check which store owns the review: the Vendor API checks the seller link before running it.
|
|
66
|
+
|
|
67
|
+
## deleteReviewWorkflow
|
|
68
|
+
|
|
69
|
+
Soft deletes a review. Workflow id `delete-review`. The input is the review id as a plain string, and the workflow returns that id.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
await deleteReviewWorkflow(container).run({ input: "rev_01JD8Q1234567" })
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Its single step, `delete-review`, calls `softDeleteReviews` and leaves the review’s links in place. On rollback it calls `restoreReviews`.
|
|
76
|
+
|
|
77
|
+
## Steps
|
|
78
|
+
|
|
79
|
+
`@mercurjs/core/workflows` also exports each step, so you can compose them into your own workflows:
|
|
80
|
+
|
|
81
|
+
| Step | Id | Input |
|
|
13
82
|
| --- | --- | --- |
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
|
|
19
|
-
Each workflow supports compensation: `createReviewWorkflow` deletes the review it
|
|
20
|
-
created on failure, `updateReviewWorkflow` and `respondReviewWorkflow` restore the
|
|
21
|
-
previous values, and `deleteReviewWorkflow` restores the soft-deleted row.
|
|
22
|
-
|
|
23
|
-
<Note>
|
|
24
|
-
`createReviewWorkflow` validates that the order belongs to the customer and
|
|
25
|
-
that the target hasn't already been reviewed on that order. `respondReviewWorkflow`
|
|
26
|
-
refuses to overwrite an existing response.
|
|
27
|
-
</Note>
|
|
28
|
-
|
|
29
|
-
To work with records directly instead of through a workflow, see the
|
|
30
|
-
[Service reference](/platform/review/reference/service). To run side effects when
|
|
31
|
-
a review changes, see the [Event reference](/platform/review/reference/events).
|
|
83
|
+
| `validateReviewStep` | `validate-review` | `CreateReviewDTO` |
|
|
84
|
+
| `createReviewStep` | `create-review` | `CreateReviewDTO` |
|
|
85
|
+
| `updateReviewStep` | `update-review` | `UpdateReviewDTO` |
|
|
86
|
+
| `respondReviewStep` | `respond-review` | `RespondReviewDTO` |
|
|
87
|
+
| `deleteReviewStep` | `delete-review` | `string` |
|
|
@@ -1,62 +1,86 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Store statuses and transitions"
|
|
3
3
|
sidebarTitle: "Lifecycle"
|
|
4
|
-
description: "
|
|
4
|
+
description: "The four store statuses, the workflow behind each transition, scheduled closures, and the premium flag."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Every store has a status that decides whether it can trade. The status lives in the `status` field of `Seller`, typed by the `SellerStatus` enum, and only dedicated workflows move it. Each transition validates the current status, records a reason or a timestamp, emits an event, and exposes a hook you can extend.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## The four statuses
|
|
10
10
|
|
|
11
|
-
A store
|
|
12
|
-
`SellerStatus` enum. A store moves through four statuses.
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
┌───────────────────┐
|
|
16
|
-
│ pending_approval │
|
|
17
|
-
└─────────┬─────────┘
|
|
18
|
-
│ approve
|
|
19
|
-
▼
|
|
20
|
-
┌───────────┐ ┌────────┐
|
|
21
|
-
│ suspended │◄───►│ open │
|
|
22
|
-
└───────────┘ └───┬────┘
|
|
23
|
-
│ terminate
|
|
24
|
-
▼
|
|
25
|
-
┌────────────┐
|
|
26
|
-
│ terminated │
|
|
27
|
-
└────────────┘
|
|
28
|
-
```
|
|
11
|
+
A store is in exactly one status at a time. New stores start in `pending_approval` unless you pass another status to `createSellersWorkflow`.
|
|
29
12
|
|
|
30
13
|
| Status | Meaning |
|
|
31
14
|
| --- | --- |
|
|
32
|
-
| `pending_approval` | Registered
|
|
33
|
-
| `open` |
|
|
34
|
-
| `suspended` |
|
|
35
|
-
| `terminated` |
|
|
15
|
+
| `pending_approval` | Registered and waiting for operator review |
|
|
16
|
+
| `open` | Trading: listed in the Store API with its offers |
|
|
17
|
+
| `suspended` | Frozen by the operator, hidden from the Store API |
|
|
18
|
+
| `terminated` | Closed by the operator, hidden from the Store API and from its members’ store list |
|
|
36
19
|
|
|
37
|
-
|
|
38
|
-
dedicated workflow: `approveSellerWorkflow`, `suspendSellerWorkflow`,
|
|
39
|
-
`unsuspendSellerWorkflow`, and `terminateSellerWorkflow`.
|
|
20
|
+
The Store API seller and offer listings return only `open` stores that aren’t inside a closure window. A store in any other status disappears from those listings, but its records stay intact.
|
|
40
21
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
22
|
+
## Transitions
|
|
23
|
+
|
|
24
|
+
Each transition is its own workflow, and each one checks the starting status before it writes.
|
|
25
|
+
|
|
26
|
+
<img className="block dark:hidden" src="/images/figures/store-lifecycle-light.svg" alt="A Status transitions table for the Seller module listing each lifecycle workflow with the status it starts from, the status it sets, and the event it emits, with the terminate row highlighted because only a suspended or pending store can be terminated." />
|
|
27
|
+
<img className="hidden dark:block" src="/images/figures/store-lifecycle-dark.svg" alt="A Status transitions table for the Seller module listing each lifecycle workflow with the status it starts from, the status it sets, and the event it emits, with the terminate row highlighted because only a suspended or pending store can be terminated." />
|
|
28
|
+
|
|
29
|
+
The workflows accept these starting statuses:
|
|
30
|
+
|
|
31
|
+
| Workflow | Allowed from | Sets |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `approveSellerWorkflow` | `pending_approval` | `open`, and `approved_at` on first approval |
|
|
34
|
+
| `suspendSellerWorkflow` | `pending_approval`, `open` | `suspended`, `status_reason`, and `rejected_at` on first suspension |
|
|
35
|
+
| `unsuspendSellerWorkflow` | `suspended` | `open`, and clears `status_reason` |
|
|
36
|
+
| `terminateSellerWorkflow` | `pending_approval`, `suspended` | `terminated` and `status_reason` |
|
|
37
|
+
| `unterminateSellerWorkflow` | `terminated` | `suspended`, and clears `status_reason` |
|
|
38
|
+
|
|
39
|
+
Any other starting status fails with a `NOT_ALLOWED` error and changes nothing.
|
|
40
|
+
|
|
41
|
+
<Warning>
|
|
42
|
+
You can’t terminate an `open` store. Suspend it first, then terminate it.
|
|
43
|
+
</Warning>
|
|
44
|
+
|
|
45
|
+
Termination isn’t final: `unterminateSellerWorkflow` brings a store back as `suspended`, and you then unsuspend it to reopen. Use suspension to reject a pending application: it stamps `rejected_at` and keeps the reason in `status_reason`.
|
|
45
46
|
|
|
46
47
|
## Scheduled closures
|
|
47
48
|
|
|
48
|
-
A store
|
|
49
|
-
fields without changing its status. During the window, the storefront shows as
|
|
50
|
-
unavailable and no new orders are accepted. The account stays `open` and resumes
|
|
51
|
-
on its own once `closed_to` passes.
|
|
49
|
+
A scheduled closure takes a store offline for a window without changing its status. Set `closed_from` and `closed_to`, and optionally a `closure_note` for the reason. Both the operator and the store’s own members can set these fields.
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
The Store API filter shows a store only when both bounds allow it:
|
|
52
|
+
|
|
53
|
+
- **`closed_from`**: `null`, or later than now
|
|
54
|
+
- **`closed_to`**: `null`, or earlier than now
|
|
55
|
+
|
|
56
|
+
The store comes back once `closed_to` passes, with no job or workflow involved. Set the fields as the closure starts, for example from a scheduled job, because a future `closed_to` hides the store right away even when `closed_from` is also in the future.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { updateSellersWorkflow } from "@mercurjs/core/workflows"
|
|
60
|
+
|
|
61
|
+
await updateSellersWorkflow(container).run({
|
|
62
|
+
input: {
|
|
63
|
+
selector: { id: "sel_01JD8Q1234567" },
|
|
64
|
+
update: {
|
|
65
|
+
closed_from: new Date(),
|
|
66
|
+
closed_to: new Date("2026-12-27"),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This closes the store from now until December 27. A `closed_from` in the past with a `null` `closed_to` hides the store until you clear the fields.
|
|
73
|
+
|
|
74
|
+
<Note>
|
|
75
|
+
Closures filter the Store API seller and offer listings. They don’t block cart or checkout workflows, so add your own check there if a closed store must refuse orders.
|
|
76
|
+
</Note>
|
|
57
77
|
|
|
58
78
|
## Premium
|
|
59
79
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
`is_premium` is a boolean that defaults to `false`. The Admin API accepts it on create and update, and the Vendor API doesn’t, so only the operator can set it. Mercur stores the flag and returns it, and your storefront decides what premium means, such as featured placement or a badge.
|
|
81
|
+
|
|
82
|
+
## Related
|
|
83
|
+
|
|
84
|
+
- [Moderate stores](/platform/store/guides/moderate-a-store)
|
|
85
|
+
- [Event reference](/platform/store/reference/events)
|
|
86
|
+
- [Workflows reference](/platform/store/reference/workflows)
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "What a store is made of"
|
|
3
3
|
sidebarTitle: "The store"
|
|
4
|
-
description: "The seller record, its
|
|
4
|
+
description: "The seller record, its currency and regions, and the satellite records that hold its business details."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
identity.
|
|
7
|
+
A store is the `Seller` record: the account a seller trades under on your marketplace. It holds the public profile customers see, the single currency the store settles in, and its status. Three satellite records carry its business details: an address, professional details, and bank payment details.
|
|
9
8
|
|
|
10
|
-
##
|
|
9
|
+
## The seller record
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
The `Seller` data model (table `seller`, id prefix `sel`) is the root of everything a store owns. Offers, orders, payouts, and price lists link to it from their own modules.
|
|
12
|
+
|
|
13
|
+
A seller has four unique fields besides its id:
|
|
14
|
+
|
|
15
|
+
- **`name`**: unique among stores that aren’t deleted
|
|
16
|
+
- **`handle`**: unique, URL safe, and generated from `name` when you omit it
|
|
17
|
+
- **`email`**: unique, the store’s contact address
|
|
18
|
+
- **`external_id`**: optional, unique when set, for mapping to an outside system
|
|
19
|
+
|
|
20
|
+
The service validates `handle` on create and update. A handle with characters that aren’t URL safe fails with an `INVALID_DATA` error.
|
|
15
21
|
|
|
16
22
|
```ts
|
|
23
|
+
import { createSellersWorkflow } from "@mercurjs/core/workflows"
|
|
24
|
+
|
|
17
25
|
const { result } = await createSellersWorkflow(container).run({
|
|
18
26
|
input: {
|
|
19
27
|
sellers: [
|
|
@@ -21,33 +29,43 @@ const { result } = await createSellersWorkflow(container).run({
|
|
|
21
29
|
name: "Acme Supplies",
|
|
22
30
|
email: "team@acme.com",
|
|
23
31
|
currency_code: "usd",
|
|
32
|
+
member: { email: "owner@acme.com" },
|
|
24
33
|
},
|
|
25
34
|
],
|
|
26
35
|
},
|
|
27
36
|
})
|
|
28
37
|
```
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
This creates a store with the handle `acme-supplies` and the status `pending_approval`. It also invites `owner@acme.com` to join the team as an administrator.
|
|
40
|
+
|
|
41
|
+
## Currency and regions
|
|
42
|
+
|
|
43
|
+
A store settles in one currency, set by `currency_code` when you create it. `UpdateSellerDTO` has no `currency_code` field, so the currency stays fixed for the life of the store. The vendor request context carries it as `seller_context.currency_code`.
|
|
44
|
+
|
|
45
|
+
A store also links to one or more regions through the `region_seller` link:
|
|
46
|
+
|
|
47
|
+
- **On create**: the store links to the default region of your Medusa store, plus any `region_ids` you pass
|
|
48
|
+
- **On change**: `linkSellerRegionsWorkflow` adds and removes region links in one call
|
|
49
|
+
- **Rule**: a store must keep at least one region, so removing the last one fails
|
|
50
|
+
|
|
51
|
+
## Business details
|
|
52
|
+
|
|
53
|
+
Three one-to-one records hang off the seller. Each workflow that writes them creates the record when it’s missing and updates it otherwise.
|
|
54
|
+
|
|
55
|
+
| Model | Holds | Written by |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `SellerAddress` | The store’s postal address and contact name | `updateSellerAddressWorkflow` |
|
|
58
|
+
| `ProfessionalDetails` | Corporate name, registration number, tax id | `updateSellerProfessionalDetailsWorkflow` |
|
|
59
|
+
| `PaymentDetails` | Bank account holder, IBAN, BIC, routing and account numbers | `updateSellerPaymentDetailsWorkflow` |
|
|
35
60
|
|
|
36
|
-
|
|
61
|
+
You remove professional details with `deleteSellerProfessionalDetailsWorkflow`. Deleting a seller cascades to all three records and to its pending invites, and detaches its members without deleting them.
|
|
37
62
|
|
|
38
|
-
|
|
39
|
-
and a tax id. These are represented by the `ProfessionalDetails` data model. When
|
|
40
|
-
this record is present, the store is a registered business. When it is absent,
|
|
41
|
-
the store is an individual seller.
|
|
63
|
+
## Fulfillment defaults
|
|
42
64
|
|
|
43
|
-
|
|
65
|
+
`default_leadtime_to_ship` holds the number of days a store needs before it ships, and defaults to `2`. Vendors can change it on their own store.
|
|
44
66
|
|
|
45
|
-
|
|
46
|
-
return, or business address. The bank details used to settle payouts live on a
|
|
47
|
-
`PaymentDetails` record. Both are satellite records of the store, and fulfillment
|
|
48
|
-
and payout settlement read from them.
|
|
67
|
+
## Related
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</Note>
|
|
69
|
+
- [Team & members](/platform/store/concepts/team)
|
|
70
|
+
- [Lifecycle](/platform/store/concepts/lifecycle)
|
|
71
|
+
- [Data models reference](/platform/store/reference/data-models)
|
|
@@ -1,50 +1,71 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "How store teams work"
|
|
3
3
|
sidebarTitle: "Team & members"
|
|
4
|
-
description: "Members, roles, invites, and the
|
|
4
|
+
description: "Members, memberships, owners, roles, invites, and how a member picks the store they act for."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A store’s team is the set of people who can sign in to the Vendor panel and act for that store. Mercur models the person and the membership separately, so one person can work for several stores with a different role in each. This matters as soon as an agency, a franchise, or a holding company runs more than one store on your marketplace.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Members and memberships
|
|
10
10
|
|
|
11
|
-
A
|
|
12
|
-
represented by the `Member` data model (table `member`, id prefix `mem`). Their
|
|
13
|
-
identity is tied to their `email`, which is unique across the system.
|
|
11
|
+
A `Member` (id prefix `mem`) is a person. Their `email` is unique across the whole marketplace, so the same person never has two member records. A member can be deactivated with `is_active` without touching their memberships.
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
A `SellerMember` (id prefix `selmem`) is one person’s membership in one store. It’s the pivot between `Seller` and `Member`, and it’s unique on `(seller_id, member_id)`. Each membership carries:
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
`(seller_id, member_id)`.
|
|
15
|
+
- **`role_id`**: the member’s role in this store, one of the `SellerRole` values
|
|
16
|
+
- **`is_owner`**: `true` for the store’s owner membership, `false` for everyone else
|
|
17
|
+
- **`metadata`**: free-form data for your own extensions
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
## Roles
|
|
20
|
+
|
|
21
|
+
`SellerRole` from `@mercurjs/types` lists the five roles a membership can hold. The membership stores the role as a plain string.
|
|
22
|
+
|
|
23
|
+
| `SellerRole` key | Stored value |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `SELLER_ADMINISTRATION` | `role_seller_administration` |
|
|
26
|
+
| `INVENTORY_MANAGEMENT` | `role_seller_inventory_management` |
|
|
27
|
+
| `ORDER_MANAGEMENT` | `role_seller_order_management` |
|
|
28
|
+
| `ACCOUNTING` | `role_seller_accounting` |
|
|
29
|
+
| `SUPPORT` | `role_seller_support` |
|
|
30
|
+
|
|
31
|
+
The vendor membership check confirms that a member belongs to the store they’re acting for. It doesn’t restrict routes by role, so enforce role rules in your own middleware when you need them.
|
|
32
|
+
|
|
33
|
+
## The owner
|
|
34
|
+
|
|
35
|
+
Every store has at most one owner membership, flagged with `is_owner`. Mercur sets it in two places:
|
|
36
|
+
|
|
37
|
+
- **Self-registration**: `createSellerAccountWorkflow` adds the registering member as owner with `role_seller_administration`
|
|
38
|
+
- **First accepted invite**: `acceptMemberInviteWorkflow` makes the new member owner when the store has none yet
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
an `is_owner` flag.
|
|
40
|
+
`updateMemberRoleWorkflow` and `removeSellerMemberWorkflow` both reject the owner membership with a `NOT_ALLOWED` error. That keeps every store with someone who can administer it.
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
A store must always keep at least one member with admin access. The last admin
|
|
36
|
-
cannot be removed or demoted.
|
|
37
|
-
</Tip>
|
|
42
|
+
## Invites
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
A `MemberInvite` (id prefix `meminv`) is a pending offer to join one store with one role. The service signs a JWT for each invite, stores it in `token`, and sets `expires_at` 7 days out unless you change the module’s `invite_valid_duration` option.
|
|
45
|
+
|
|
46
|
+
Invites follow these rules:
|
|
47
|
+
|
|
48
|
+
- **One open invite per email and store**: a unique index covers `(email, seller_id)` while `accepted` is `false`
|
|
49
|
+
- **No invites for existing members**: inviting an email that already belongs to the store fails
|
|
50
|
+
- **Existing accounts are reused**: accepting an invite for a known email links the existing member instead of creating one
|
|
51
|
+
- **Single use**: accepting deletes the invite, and an expired or accepted token fails validation
|
|
52
|
+
|
|
53
|
+
## The active store
|
|
54
|
+
|
|
55
|
+
A member who belongs to several stores picks the active one per request. The vendor middleware reads the store id from the `x-seller-id` header, or from the session, and checks for a matching `SellerMember`. It then exposes the store on `req.seller_context`, typed by the `SellerContext` interface:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
interface SellerContext {
|
|
59
|
+
seller_id: string
|
|
60
|
+
currency_code: string
|
|
61
|
+
seller_member: SellerMemberDTO
|
|
62
|
+
}
|
|
63
|
+
```
|
|
40
64
|
|
|
41
|
-
|
|
42
|
-
invited email already belongs to a user, accepting the invite links the new store
|
|
43
|
-
association to their existing account instead of creating a new one.
|
|
65
|
+
Every vendor route scopes its reads and writes to `seller_context.seller_id`, so switching stores in the Vendor panel never shows another store’s data. The store list a member sees leaves out terminated stores.
|
|
44
66
|
|
|
45
|
-
##
|
|
67
|
+
## Related
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
across stores.
|
|
69
|
+
- [Manage the team](/platform/store/guides/manage-the-team)
|
|
70
|
+
- [Workflows reference](/platform/store/reference/workflows)
|
|
71
|
+
- [What a store is made of](/platform/store/concepts/store-entity)
|
|
@@ -1,55 +1,122 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Create a store"
|
|
2
|
+
title: "Create a store from server code"
|
|
3
3
|
sidebarTitle: "Create a store"
|
|
4
|
-
description: "Create a
|
|
4
|
+
description: "Create stores with createSellersWorkflow from a route, a seed script, or an import job, and extend creation with hooks."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
is useful in a seed script, a custom API route, or an onboarding flow.
|
|
7
|
+
Create a store from your own server code when you seed a marketplace, import sellers from another system, or build a custom onboarding flow. `createSellersWorkflow` creates the `Seller`, links its regions, and invites its first team member.
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
with its defaults. Run it from any place that has access to the Medusa container.
|
|
9
|
+
## Before you begin
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
You need the following in place:
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
- A Mercur server with `@mercurjs/core` installed through `withMercur()`
|
|
14
|
+
- A default region set on your Medusa store, since every new store links to it
|
|
15
|
+
|
|
16
|
+
## Create the store
|
|
17
|
+
|
|
18
|
+
<Steps>
|
|
19
|
+
<Step title="Add a route that runs the workflow">
|
|
20
|
+
Create an API route and run the workflow against the request’s container. Each entry in `sellers` needs a `member.email`: the workflow sends that address an invite with the `role_seller_administration` role.
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
```ts title="src/api/admin/import-sellers/route.ts"
|
|
23
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
24
|
+
import { createSellersWorkflow } from "@mercurjs/core/workflows"
|
|
25
|
+
|
|
26
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
27
|
+
const { result: sellers } = await createSellersWorkflow(req.scope).run({
|
|
28
|
+
input: {
|
|
29
|
+
sellers: [
|
|
30
|
+
{
|
|
31
|
+
name: "Acme Supplies",
|
|
32
|
+
email: "team@acme.com",
|
|
33
|
+
currency_code: "usd",
|
|
34
|
+
member: { email: "owner@acme.com" },
|
|
35
|
+
region_ids: ["reg_01JD8Q1234567"],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
27
38
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
res.status(201).json({ sellers })
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The store gets the handle `acme-supplies` and links to the default region plus `reg_01JD8Q1234567`.
|
|
46
|
+
</Step>
|
|
47
|
+
<Step title="Open the store">
|
|
48
|
+
New stores start in `pending_approval`. Run `approveSellerWorkflow` to open one, or pass `status: "open"` in the seller input when you import stores you’ve already vetted.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { approveSellerWorkflow } from "@mercurjs/core/workflows"
|
|
52
|
+
|
|
53
|
+
await approveSellerWorkflow(req.scope).run({
|
|
54
|
+
input: { seller_id: sellers[0].id },
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Approval stamps `approved_at` and emits `seller.approved`.
|
|
59
|
+
</Step>
|
|
60
|
+
<Step title="Let the owner accept the invite">
|
|
61
|
+
The workflow emits `member_invite.created` with the invite’s `id`, `token`, and `expires_at`. Send the token to the invitee from a subscriber. When they accept it through `acceptMemberInviteWorkflow`, they join as the store’s owner. [Manage the team](/platform/store/guides/manage-the-team) shows both the subscriber and the accept call.
|
|
62
|
+
</Step>
|
|
63
|
+
</Steps>
|
|
64
|
+
|
|
65
|
+
## Attach custom data with additional_data
|
|
31
66
|
|
|
32
|
-
|
|
33
|
-
|
|
67
|
+
The workflow passes `additional_data` to its `sellersCreated` hook. Use the hook to save data that belongs to your extension, such as a referral code, without changing the workflow.
|
|
68
|
+
|
|
69
|
+
```ts title="src/workflows/hooks/sellers-created.ts"
|
|
70
|
+
import { createSellersWorkflow } from "@mercurjs/core/workflows"
|
|
71
|
+
|
|
72
|
+
createSellersWorkflow.hooks.sellersCreated(
|
|
73
|
+
async ({ sellers, additional_data }, { container }) => {
|
|
74
|
+
const referralCode = additional_data?.referral_code
|
|
75
|
+
if (!referralCode) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
const logger = container.resolve("logger")
|
|
79
|
+
logger.info(`${sellers.length} sellers joined via ${referralCode}`)
|
|
80
|
+
}
|
|
81
|
+
)
|
|
34
82
|
```
|
|
35
83
|
|
|
36
|
-
|
|
37
|
-
An operator can set a new store to `open` right away. A store created through
|
|
38
|
-
the public self-registration flow starts in `pending_approval` and waits for
|
|
39
|
-
operator review. See [Moderate stores](/platform/store/guides/moderate-a-store).
|
|
40
|
-
</Note>
|
|
84
|
+
Pass `additional_data: { referral_code: "SPRING25" }` next to `sellers` in the workflow input to reach this hook. The workflow also has a `validate` hook that runs before anything is written, so you can throw there to reject the input.
|
|
41
85
|
|
|
42
|
-
##
|
|
86
|
+
## Create the owner membership directly
|
|
43
87
|
|
|
44
|
-
|
|
45
|
-
hooks, so you can persist marketplace-specific data alongside the store without
|
|
46
|
-
forking the workflow.
|
|
88
|
+
`createSellersWorkflow` invites the owner. When the person already signed in, for example in a custom registration route, run `createSellerAccountWorkflow` instead. It creates the store in `pending_approval` and adds the member as owner in one call.
|
|
47
89
|
|
|
48
90
|
```ts
|
|
49
|
-
|
|
91
|
+
import { createSellerAccountWorkflow } from "@mercurjs/core/workflows"
|
|
92
|
+
|
|
93
|
+
const { result: seller } = await createSellerAccountWorkflow(
|
|
94
|
+
req.scope
|
|
95
|
+
).run({
|
|
50
96
|
input: {
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
auth_identity_id: req.auth_context.auth_identity_id,
|
|
98
|
+
member_email: "owner@acme.com",
|
|
99
|
+
seller: {
|
|
100
|
+
name: "Acme Supplies",
|
|
101
|
+
email: "team@acme.com",
|
|
102
|
+
currency_code: "usd",
|
|
103
|
+
},
|
|
53
104
|
},
|
|
54
105
|
})
|
|
55
106
|
```
|
|
107
|
+
|
|
108
|
+
The workflow upserts the member by email and stores its id on the auth identity under the `member` actor type. It also accepts `address`, `professional_details`, and `payment_details`, and writes each one when present.
|
|
109
|
+
|
|
110
|
+
## Handle a failed creation
|
|
111
|
+
|
|
112
|
+
The workflow rolls back every step when one fails. These inputs fail:
|
|
113
|
+
|
|
114
|
+
- **Duplicate `name`, `email`, `handle`, or `external_id`**: the unique indexes reject the insert
|
|
115
|
+
- **Invalid `handle`**: a handle with characters that aren’t URL safe fails with `INVALID_DATA`
|
|
116
|
+
- **Unknown region**: a `region_ids` entry that doesn’t exist fails with `NOT_FOUND`
|
|
117
|
+
|
|
118
|
+
## Next steps
|
|
119
|
+
|
|
120
|
+
- [Moderate stores](/platform/store/guides/moderate-a-store)
|
|
121
|
+
- [Manage the team](/platform/store/guides/manage-the-team)
|
|
122
|
+
- [Workflows reference](/platform/store/reference/workflows)
|