@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.
- 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/llms.txt +111 -111
- package/package.json +1 -1
|
@@ -1,40 +1,134 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Catalog workflows"
|
|
3
3
|
sidebarTitle: "Workflows"
|
|
4
|
-
description: "Mercur's product create, review, and
|
|
4
|
+
description: "Mercur's product create, review, allowlist, and category workflows: inputs, hooks, and events."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from `@mercurjs/core/workflows` and run them against the Medusa container. They
|
|
9
|
-
wrap Medusa's native product flows to add the marketplace layer: the store
|
|
10
|
-
allowlist, the review lifecycle, and the audit trail.
|
|
7
|
+
Mercur ships 7 catalog workflows in `packages/core/src/workflows/product`. Import them from `@mercurjs/core/workflows` and run them with the Medusa container. For plain updates and deletes of products, variants, and categories, use Medusa's core flows, such as `updateProductsWorkflow`.
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
```ts
|
|
10
|
+
import { confirmProductsWorkflow } from "@mercurjs/core/workflows"
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
await confirmProductsWorkflow(container).run({
|
|
13
|
+
input: { product_ids: ["prod_01JD8Q1234567"] },
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## createProductsWorkflow
|
|
18
|
+
|
|
19
|
+
Creates master products with attributes and variants, links stores to them, and records each submission. Workflow id `mercur-create-products`. Returns the created `ProductDTO[]` in input order.
|
|
20
|
+
|
|
21
|
+
| Input | Type | Description |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `products` | `(CreateProductDTO & { seller_ids?: string[] })[]` | Products to create. `CreateProductDTO` from `@mercurjs/types` replaces `options` with `attributes` |
|
|
24
|
+
| `products[].seller_ids` | `string[]`, optional | Stores to put on the product's allowlist |
|
|
25
|
+
| `created_by` | `string` | Submitter id, a seller id or a user id, stored on the audit record |
|
|
26
|
+
| `additional_data` | `Record<string, unknown>`, optional | Forwarded to Medusa's product and variant workflows and the `productsCreated` hook |
|
|
27
|
+
|
|
28
|
+
The workflow runs these steps in order:
|
|
29
|
+
|
|
30
|
+
1. Runs the `validate` hook with `input` and `products`
|
|
31
|
+
2. Creates the products with Medusa's `createProductsWorkflow`, each with an internal `__default__` option
|
|
32
|
+
3. Attaches `attributes` with `addProductAttributesToProductWorkflow`
|
|
33
|
+
4. Removes the `__default__` option from products that gained a variant axis
|
|
34
|
+
5. Creates `variants` with `createProductVariantsWorkflow`, defaulting `manage_inventory` to `false`
|
|
35
|
+
6. Links `seller_ids` with `associateSellersWithProductStep`
|
|
36
|
+
7. Records a `PRODUCT_ADD` change per product with `recordProductAuditChangeWorkflow`
|
|
37
|
+
8. Runs the `productsCreated` hook with `products` and `additional_data`
|
|
38
|
+
9. Emits `product.created`
|
|
39
|
+
|
|
40
|
+
It exposes the `validate` and `productsCreated` hooks. It emits `product.created` with `{ id }` for each product.
|
|
41
|
+
|
|
42
|
+
## confirmProductsWorkflow
|
|
43
|
+
|
|
44
|
+
Publishes `proposed` products. Workflow id `mercur-confirm-products`.
|
|
45
|
+
|
|
46
|
+
| Input | Type | Description |
|
|
15
47
|
| --- | --- | --- |
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
48
|
+
| `product_ids` | `string[]` | Products to publish. All must be `proposed` |
|
|
49
|
+
| `actor_id` | `string`, optional | Reviewer, stored as `created_by` and `confirmed_by` |
|
|
50
|
+
| `internal_note` | `string`, optional | Operator note on each audit record |
|
|
51
|
+
| `additional_data` | `Record<string, unknown>`, optional | Passed to the hook |
|
|
20
52
|
|
|
21
|
-
|
|
53
|
+
Records a `STATUS_CHANGE` action with `{ status: "published" }` per product, then sets the status.
|
|
22
54
|
|
|
23
|
-
|
|
55
|
+
It exposes the `productsConfirmed` hook with `product_ids`, `internal_note`, and `additional_data`. It emits `product.published` with `{ id, internal_note }` for each product.
|
|
56
|
+
|
|
57
|
+
## rejectProductWorkflow
|
|
58
|
+
|
|
59
|
+
Rejects one `proposed` product. Workflow id `mercur-reject-product`.
|
|
60
|
+
|
|
61
|
+
| Input | Type | Description |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| `product_id` | `string` | Product to reject |
|
|
64
|
+
| `message` | `string`, optional | Reason, stored as the change's `external_note` |
|
|
65
|
+
| `actor_id` | `string`, optional | Reviewer |
|
|
66
|
+
| `additional_data` | `Record<string, unknown>`, optional | Passed to the hook |
|
|
67
|
+
|
|
68
|
+
Records a `STATUS_CHANGE` action with `{ status: "rejected" }`, then sets the status.
|
|
69
|
+
|
|
70
|
+
It exposes the `productRejected` hook with `product_id`, `message`, and `additional_data`. It emits `product.rejected` with `{ id, message }`.
|
|
71
|
+
|
|
72
|
+
## requestProductChangeWorkflow
|
|
73
|
+
|
|
74
|
+
Asks the submitter to revise a `proposed` product without changing its status. Workflow id `mercur-request-product-change`.
|
|
75
|
+
|
|
76
|
+
| Input | Type | Description |
|
|
24
77
|
| --- | --- | --- |
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
78
|
+
| `product_id` | `string` | Product to send back |
|
|
79
|
+
| `message` | `string`, optional | Requested changes, stored as `external_note` |
|
|
80
|
+
| `actor_id` | `string`, optional | Reviewer |
|
|
81
|
+
| `additional_data` | `Record<string, unknown>`, optional | Passed to the hook |
|
|
82
|
+
|
|
83
|
+
Records a `CHANGE_REQUESTED` action with `{ message }`.
|
|
84
|
+
|
|
85
|
+
It exposes the `productChangeRequested` hook with `product_id`, `message`, and `additional_data`. It emits `product.change-requested` with `{ id, message, actor_id }`.
|
|
86
|
+
|
|
87
|
+
## linkSellersToProductWorkflow
|
|
88
|
+
|
|
89
|
+
Adds stores to and removes stores from a product's allowlist. Workflow id `mercur-link-sellers-to-product`.
|
|
90
|
+
|
|
91
|
+
| Input | Type | Description |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `id` | `string` | Product id |
|
|
94
|
+
| `add` | `string[]`, optional | Seller ids to link |
|
|
95
|
+
| `remove` | `string[]`, optional | Seller ids to unlink |
|
|
96
|
+
|
|
97
|
+
Runs `associateSellersWithProductStep` and `detachSellersFromProductStep`, which undo their changes if the run fails. No hooks or events.
|
|
98
|
+
|
|
99
|
+
## linkSellersToProductCategoryWorkflow
|
|
100
|
+
|
|
101
|
+
Adds and removes `category_seller` rows for a category. Workflow id `mercur-link-sellers-to-product-category`.
|
|
102
|
+
|
|
103
|
+
| Input | Type | Description |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| `id` | `string` | Category id |
|
|
106
|
+
| `add` | `string[]`, optional | Seller ids to link |
|
|
107
|
+
| `remove` | `string[]`, optional | Seller ids to unlink |
|
|
108
|
+
|
|
109
|
+
Runs `associateSellersWithProductCategoryStep` and `detachSellersFromProductCategoryStep`. No hooks or events.
|
|
110
|
+
|
|
111
|
+
## assignProductsToCategoryWorkflow
|
|
112
|
+
|
|
113
|
+
Moves products into one category. Workflow id `mercur-assign-products-to-category`.
|
|
114
|
+
|
|
115
|
+
| Input | Type | Description |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| `id` | `string` | Category id |
|
|
118
|
+
| `add` | `string[]`, optional | Product ids. Each product's categories become `[id]` |
|
|
119
|
+
| `remove` | `string[]`, optional | Product ids. Each product's categories become empty |
|
|
120
|
+
|
|
121
|
+
Runs Medusa's `updateProductsWorkflow` with `category_ids`, so Medusa's product update events fire. No hooks of its own.
|
|
122
|
+
|
|
123
|
+
## Exported steps
|
|
124
|
+
|
|
125
|
+
These steps are exported for use in your own workflows:
|
|
126
|
+
|
|
127
|
+
| Step | Does |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `associateSellersWithProductStep` | Creates `product_seller` rows from `{ links: { product_id, seller_id }[] }` |
|
|
130
|
+
| `detachSellersFromProductStep` | Dismisses `product_seller` rows with the same input |
|
|
131
|
+
| `associateSellersWithProductCategoryStep` | Creates `category_seller` rows from `{ links: { category_id, seller_id }[] }` |
|
|
132
|
+
| `detachSellersFromProductCategoryStep` | Dismisses `category_seller` rows with the same input |
|
|
133
|
+
| `validateProductsStatusStep` | Throws `NOT_ALLOWED` unless every product in `products` has the `expected_status` |
|
|
134
|
+
| `validateSellerProductPermissionsStep` | Throws `NOT_ALLOWED` when `seller_id` is linked to any of `category_ids` |
|
|
@@ -1,65 +1,67 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Commission lines on orders"
|
|
3
3
|
sidebarTitle: "Order commission lines"
|
|
4
|
-
description: "
|
|
4
|
+
description: "What a commission line records, when Mercur writes and rewrites it, and how payouts read it."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
order changes.
|
|
7
|
+
A commission line is the resolved commission for one line item or one shipping method of an order. It’s derived data: Mercur computes it from the current rates and the order’s contents, writes it at checkout, and rewrites it whenever the order changes. Payouts read these lines to work out what the seller receives.
|
|
9
8
|
|
|
10
|
-
##
|
|
9
|
+
## What a line records
|
|
11
10
|
|
|
12
|
-
A
|
|
13
|
-
represented by the `CommissionLine` data model (table `commission_line`, id
|
|
14
|
-
prefix `comline`). Each line anchors to either an item (`item_id`) or a shipping
|
|
15
|
-
method (`shipping_method_id`). It records the rate that matched
|
|
16
|
-
(`commission_rate_id`, `code`), the applied `rate`, and the computed `amount`.
|
|
11
|
+
A line is a `CommissionLine` record (id prefix `comline`). It points at exactly one of `item_id` or `shipping_method_id`, and it stores the rate it came from and the amount.
|
|
17
12
|
|
|
18
13
|
```ts
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
item_id: "
|
|
14
|
+
const line = {
|
|
15
|
+
id: "comline_01JD8Q1234567",
|
|
16
|
+
item_id: "ordli_01JD8Q1234567",
|
|
22
17
|
shipping_method_id: null,
|
|
23
|
-
commission_rate_id: "
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
commission_rate_id: "comrate_01JD8Q1234567",
|
|
19
|
+
provider_id: "system",
|
|
20
|
+
code: "acme-apparel-k3x9qa",
|
|
21
|
+
rate: 12,
|
|
22
|
+
amount: 5.64,
|
|
27
23
|
description: null,
|
|
24
|
+
data: null,
|
|
28
25
|
}
|
|
29
26
|
```
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
28
|
+
The fields carry these values from the `system` provider:
|
|
29
|
+
|
|
30
|
+
- **`code`**: the code of the winning rate, kept so the line stays readable if the rate is renamed or deleted
|
|
31
|
+
- **`rate`**: the percent for a percentage rate, or the fixed amount for a fixed rate
|
|
32
|
+
- **`amount`**: the commission for the line, computed with `MathBN`
|
|
33
|
+
- **`description`**: `Shipping Commission` on shipping lines, empty on item lines
|
|
34
|
+
- **`provider_id`**: the provider that computed the line, `system` by default
|
|
35
|
+
- **`data`**: free JSON a custom provider can fill
|
|
36
|
+
|
|
37
|
+
## When lines are written
|
|
38
|
+
|
|
39
|
+
Mercur writes lines at checkout and rewrites them when the order’s contents change. Both paths run `refreshOrderCommissionLinesWorkflow`:
|
|
40
|
+
|
|
41
|
+
| Trigger | Where it runs |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Cart completed | A nested step of `completeCartWithSplitOrdersWorkflow`, for every seller order in the group |
|
|
44
|
+
| `order-edit.confirmed` | The `order-commission-refresh-handler` subscriber |
|
|
45
|
+
| `order.return_received` | The same subscriber |
|
|
46
|
+
| `order.claim_created` | The same subscriber |
|
|
47
|
+
| `order.exchange_created` | The same subscriber |
|
|
48
|
+
|
|
49
|
+
A refresh resolves against the rates that exist when it runs. Changing a rate doesn’t touch existing lines until the order is refreshed again.
|
|
50
|
+
|
|
51
|
+
## A refresh replaces, it doesn’t append
|
|
52
|
+
|
|
53
|
+
Computed lines carry no `id`. The module’s `upsertCommissionLines` first deletes every existing line whose `item_id` or `shipping_method_id` is in the new set, then inserts the new lines in the same transaction. Running a refresh twice leaves one line per item and shipping method.
|
|
54
|
+
|
|
55
|
+
<Warning>
|
|
56
|
+
Deletion is keyed on the anchors in the new set. If an item no longer gets a line, for example because you disabled the only rate that matched it, its old line stays in place.
|
|
57
|
+
</Warning>
|
|
58
|
+
|
|
59
|
+
## How payouts use the lines
|
|
60
|
+
|
|
61
|
+
`createPayoutWorkflow` reads every commission line whose `item_id` or `shipping_method_id` belongs to the order. It sums their `amount` and transfers the order `total` minus that sum to the seller’s payout account. With the lines from the [overview figure](/platform/commission/overview), an order total of € 92.00 and commission of € 10.14 pays the seller € 81.86.
|
|
62
|
+
|
|
63
|
+
## Related
|
|
64
|
+
|
|
65
|
+
- [Refresh an order’s lines from your own code](/platform/commission/guides/refresh-order-commission-lines)
|
|
66
|
+
- [The order events that trigger a refresh](/platform/commission/reference/events)
|
|
67
|
+
- [All `CommissionLine` fields](/platform/commission/reference/data-models)
|
|
@@ -1,86 +1,76 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "How Mercur picks a commission rate"
|
|
3
3
|
sidebarTitle: "Rule matching"
|
|
4
|
-
description: "The five dimensions, most-specific-wins
|
|
4
|
+
description: "The five rule dimensions, AND and OR matching, most-specific-wins, the oldest-rate tie-break, and shipping."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
order line.
|
|
7
|
+
Rule matching decides which single rate commissions each order line. Mercur finds every enabled rate whose rules fit the line’s product and seller, then keeps the one scoped on the most dimensions. Knowing the algorithm lets you predict a seller’s commission before an order exists.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
<img className="block dark:hidden" src="/images/figures/commission-rule-matching-light.svg" alt="Resolving the rate for a Linen shirt sold by ACME in the Shirts category: four enabled rates match, the acme-shirts rate scopes on two dimensions and wins, lighting does not match, and the default rate ranks last." />
|
|
10
|
+
<img className="hidden dark:block" src="/images/figures/commission-rule-matching-dark.svg" alt="Resolving the rate for a Linen shirt sold by ACME in the Shirts category: four enabled rates match, the acme-shirts rate scopes on two dimensions and wins, lighting does not match, and the default rate ranks last." />
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
rule
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
## Each dimension checks one product field
|
|
13
|
+
|
|
14
|
+
A rule compares its `reference_id` with one field of the line’s product. The product comes from the order line, and the seller comes from the offer the customer bought.
|
|
15
|
+
|
|
16
|
+
| `reference` | Matches when |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `product` | `product.id` equals `reference_id` |
|
|
19
|
+
| `product_type` | `product.type_id` equals `reference_id` |
|
|
20
|
+
| `product_collection` | `product.collection_id` equals `reference_id` |
|
|
21
|
+
| `product_category` | Any of `product.categories` has the id `reference_id` |
|
|
22
|
+
| `seller` | The offer’s `seller_id` equals `reference_id` |
|
|
23
|
+
|
|
24
|
+
Products are a shared catalog that no seller owns, which is why the `seller` dimension reads the offer on the line and not the product.
|
|
25
|
+
|
|
26
|
+
<Warning>
|
|
27
|
+
The module stores `reference` as free text and doesn’t validate it. A rule with any other `reference`, such as a typo, never matches, so the whole rate never matches.
|
|
28
|
+
</Warning>
|
|
29
|
+
|
|
30
|
+
## AND across dimensions, OR within one
|
|
31
|
+
|
|
32
|
+
Mercur groups a rate’s rules by `reference`. The rate matches a line when every group has at least one rule that matches. Rules in the same group are alternatives, and groups narrow each other.
|
|
17
33
|
|
|
18
34
|
```ts
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{ reference: "product_category", reference_id: "pcat_shoes" },
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
})
|
|
35
|
+
const rules = [
|
|
36
|
+
{ reference: "seller", reference_id: "sel_01JD8Q1234567" },
|
|
37
|
+
{ reference: "product_category", reference_id: "pcat_01JD8Q1234567" },
|
|
38
|
+
{ reference: "product_category", reference_id: "pcat_01JD8Q1234568" },
|
|
39
|
+
]
|
|
28
40
|
```
|
|
29
41
|
|
|
30
|
-
A rate with
|
|
31
|
-
rules only matches lines that satisfy them.
|
|
42
|
+
A rate with these rules matches lines sold by that seller in either category. It doesn’t match the seller’s lines in other categories, or other sellers’ lines in those categories.
|
|
32
43
|
|
|
33
|
-
## The
|
|
44
|
+
## The most specific rate wins
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
line's product:
|
|
46
|
+
When several rates match, Mercur sorts them by specificity and keeps the first. Specificity is the number of distinct dimensions a rate’s rules use, not the number of rules. The rules above give a specificity of 2.
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
| --- | --- |
|
|
40
|
-
| `product` | The line's product id equals `reference_id` |
|
|
41
|
-
| `product_type` | The product's type id equals `reference_id` |
|
|
42
|
-
| `product_collection` | The product's collection id equals `reference_id` |
|
|
43
|
-
| `product_category` | One of the product's categories equals `reference_id` |
|
|
44
|
-
| `seller` | The seller behind the line's offer equals `reference_id` |
|
|
48
|
+
Mercur resolves each line in this order:
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
1. Drop disabled rates and rates whose `currency_code` is set to another currency.
|
|
51
|
+
2. Keep the rates that match the line. A rate without rules always matches.
|
|
52
|
+
3. Sort by specificity, highest first.
|
|
53
|
+
4. Break ties by `created_at`, oldest first.
|
|
54
|
+
5. Apply the first rate to the line’s base amount.
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
The global rate has no rules, so its specificity is 0 and any matching scoped rate beats it. If no enabled rate matches, the line gets no commission line.
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
rates, the one scoped on the **most distinct dimensions** wins.
|
|
58
|
+
<Warning>
|
|
59
|
+
The tie-break also applies between rule-less rates. If you create a second rate without rules, it competes with the global rate at specificity 0, and the older of the two wins.
|
|
60
|
+
</Warning>
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
Rate A: seller = sel_123 (specificity 1)
|
|
61
|
-
Rate B: seller = sel_123 AND category = pcat_shoes (specificity 2) ← wins
|
|
62
|
-
```
|
|
62
|
+
## Shipping uses the global rate only
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
Specificity is the count of **distinct dimensions** a rate scopes on, not the
|
|
66
|
-
number of rules. Two `product_category` rules on one rate still count as a
|
|
67
|
-
single dimension (they OR together).
|
|
68
|
-
</Tip>
|
|
64
|
+
Shipping methods aren’t matched against rules. If the global rate has `include_shipping` set, every shipping method on the order gets a line from it, with the description `Shipping Commission`. Otherwise shipping isn’t commissioned.
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
The global rate’s `include_tax` flag applies to shipping too, adding the method’s `tax_total` to its `subtotal`. For a fixed global rate, each shipping method gets the fixed amount.
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
are evaluated `created_at` ascending, so the earliest-created rate is the
|
|
74
|
-
deterministic winner.
|
|
68
|
+
## Rates load once per batch
|
|
75
69
|
|
|
76
|
-
|
|
70
|
+
The `system` provider loads all enabled rates with their rules and values in one query per batch of orders. It then resolves every line of every order in memory. Adding rates doesn’t add queries per line.
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
shipping method is commissioned **only** by the global rate, and only when its
|
|
80
|
-
`include_shipping` flag is on. No scoped rate can commission shipping.
|
|
72
|
+
## Related
|
|
81
73
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
item and shipping commission.
|
|
86
|
-
</Note>
|
|
74
|
+
- [Rate types, per-currency values, and the global commission](/platform/commission/concepts/rules-and-rates)
|
|
75
|
+
- [Scope a rate with batchCommissionRulesWorkflow](/platform/commission/guides/batch-update-rules)
|
|
76
|
+
- [Write your own provider to replace this algorithm](/platform/commission/reference/service)
|
|
@@ -1,84 +1,86 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "
|
|
2
|
+
title: "Commission rates and rules"
|
|
3
3
|
sidebarTitle: "Rules & rates"
|
|
4
|
-
description: "
|
|
4
|
+
description: "How a commission rate expresses the marketplace's cut, how rules scope it, and what the global commission does."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
express the marketplace's cut.
|
|
7
|
+
A commission rate is the cut the marketplace takes from a sale, and rules decide which lines it applies to. Every marketplace has one global rate that covers everything, and you add scoped rates on top of it for specific sellers or parts of the catalog. Getting this model right decides what every seller is paid.
|
|
9
8
|
|
|
10
|
-
##
|
|
9
|
+
## A rate is a type and a value
|
|
11
10
|
|
|
12
|
-
A
|
|
13
|
-
represented by the `CommissionRate` data model (table `commission_rate`, id
|
|
14
|
-
prefix `comrate`). A rate has a `name`, a unique `code`, a `type`, and a
|
|
15
|
-
`value`, plus the `include_tax` and `include_shipping` toggles.
|
|
11
|
+
A rate is a `CommissionRate` record (id prefix `comrate`). Its `type` says how to read its `value`, and two flags change the base amount the value applies to.
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
name: "Standard",
|
|
22
|
-
type: CommissionRateType.PERCENTAGE,
|
|
23
|
-
value: 10,
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
})
|
|
27
|
-
```
|
|
13
|
+
| `type` | How `value` is read | Line `amount` |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `percentage` | A percent, so `10` means 10% | `base × value ÷ 100` |
|
|
16
|
+
| `fixed` | A flat amount per line item | The per-currency amount, or `value` |
|
|
28
17
|
|
|
29
|
-
|
|
30
|
-
(`CommissionRateType`):
|
|
18
|
+
The base amount is the line’s `subtotal`. A rate changes it with these flags:
|
|
31
19
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| `fixed` | A flat amount deducted per line |
|
|
20
|
+
- **`include_tax`**: adds the line’s `tax_total` to the base before the rate applies
|
|
21
|
+
- **`include_shipping`**: lets the rate commission shipping methods, and only takes effect on the global rate
|
|
22
|
+
- **`is_enabled`**: disabled rates are skipped during resolution
|
|
36
23
|
|
|
37
|
-
<
|
|
38
|
-
A rate
|
|
39
|
-
|
|
40
|
-
`standard-a1b2c3`).
|
|
41
|
-
</Note>
|
|
42
|
-
|
|
43
|
-
## Per-currency amounts
|
|
24
|
+
<Warning>
|
|
25
|
+
A fixed rate applies once per line item, not once per unit. A line with a quantity of 3 still gets one fixed amount. The `system` provider doesn’t cap a fixed amount at the line total.
|
|
26
|
+
</Warning>
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
represented by the `CommissionRateValue` data model (table
|
|
47
|
-
`commission_rate_value`, id prefix `comval`). Each value pairs a `currency_code`
|
|
48
|
-
with an `amount`, and the calculation picks the value matching the order's
|
|
49
|
-
currency.
|
|
28
|
+
Each rate also has a `name` and a unique `code`. When you create a rate without a `code`, the module builds one from the name plus a random suffix, so `"Standard"` becomes something like `standard-k3x9qa`.
|
|
50
29
|
|
|
51
30
|
```ts
|
|
31
|
+
import { CommissionRateType } from "@mercurjs/types"
|
|
32
|
+
import { createCommissionRatesWorkflow } from "@mercurjs/core/workflows"
|
|
33
|
+
|
|
52
34
|
await createCommissionRatesWorkflow(container).run({
|
|
53
35
|
input: [
|
|
54
|
-
{
|
|
55
|
-
name: "Flat fee",
|
|
56
|
-
type: CommissionRateType.FIXED,
|
|
57
|
-
value: 5, // fallback when no per-currency value matches
|
|
58
|
-
values: [
|
|
59
|
-
{ currency_code: "usd", amount: 5 },
|
|
60
|
-
{ currency_code: "eur", amount: 4 },
|
|
61
|
-
],
|
|
62
|
-
},
|
|
36
|
+
{ name: "Standard", type: CommissionRateType.PERCENTAGE, value: 10 },
|
|
63
37
|
],
|
|
64
38
|
})
|
|
65
39
|
```
|
|
66
40
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
41
|
+
The example creates a 10% rate with no rules, which matches every line until a more specific rate wins.
|
|
42
|
+
|
|
43
|
+
## Per-currency amounts for fixed rates
|
|
44
|
+
|
|
45
|
+
A fixed rate holds one amount per currency in `CommissionRateValue` records (id prefix `comval`). During resolution, Mercur picks the value whose `currency_code` equals the order’s currency. When none matches, it uses the rate’s own `value`.
|
|
46
|
+
|
|
47
|
+
Percentage rates ignore `values`, because a percent is the same in every currency. A rate’s own `currency_code` field is separate: when it’s set, the rate only applies to orders in that currency.
|
|
48
|
+
|
|
49
|
+
| Rate | `values` | Order in `usd` | Order in `gbp` |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| fixed, `value: 3` | `usd: 2.5`, `eur: 2` | 2.5 per line | 3 per line |
|
|
52
|
+
| percentage, `value: 10` | ignored | 10% of base | 10% of base |
|
|
53
|
+
|
|
54
|
+
## Rules scope a rate
|
|
55
|
+
|
|
56
|
+
A rule is a `CommissionRule` record (id prefix `comrule`) that belongs to one rate. It’s a `reference` naming a dimension and a `reference_id` naming the record, for example `seller` and `sel_01JD8Q1234567`. A rate without rules matches every line. A rate with rules matches only the lines that satisfy them.
|
|
57
|
+
|
|
58
|
+
The five dimensions are:
|
|
59
|
+
|
|
60
|
+
- **`product`**: a master product id
|
|
61
|
+
- **`product_type`**: a product type id
|
|
62
|
+
- **`product_collection`**: a collection id
|
|
63
|
+
- **`product_category`**: a category id
|
|
64
|
+
- **`seller`**: the seller of the offer on the order line
|
|
65
|
+
|
|
66
|
+
[Rule matching](/platform/commission/concepts/rule-matching) explains how rules combine and which rate wins when several match.
|
|
72
67
|
|
|
73
68
|
## The global commission
|
|
74
69
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
The global commission is the rate with `is_default: true`. The module seeds it on boot when none exists, as a 0% percentage rate named `Default` with the code `default`. You raise it to set the marketplace’s baseline cut.
|
|
71
|
+
|
|
72
|
+
The global rate has rules that set it apart:
|
|
73
|
+
|
|
74
|
+
- **Always present**: `deleteCommissionRatesWorkflow` throws `NOT_ALLOWED` if you try to delete it
|
|
75
|
+
- **Fallback for items**: it has no rules, so it matches every line and loses to any scoped rate that also matches
|
|
76
|
+
- **Only shipping rate**: its `include_shipping` flag is the only one that produces shipping commission lines
|
|
79
77
|
|
|
80
78
|
<Note>
|
|
81
|
-
|
|
82
|
-
[Rule matching](/platform/commission/concepts/rule-matching) for how
|
|
83
|
-
specificity and shipping are resolved.
|
|
79
|
+
Nothing stops you from setting `include_shipping` on a scoped rate, but the flag has no effect there. Shipping is resolved from the global rate alone.
|
|
84
80
|
</Note>
|
|
81
|
+
|
|
82
|
+
## Related
|
|
83
|
+
|
|
84
|
+
- [How Mercur picks one rate for each line](/platform/commission/concepts/rule-matching)
|
|
85
|
+
- [Create a rate from your server code](/platform/commission/guides/create-a-rate)
|
|
86
|
+
- [All `CommissionRate` fields](/platform/commission/reference/data-models)
|