@spree/docs 0.1.183 → 0.1.185

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.
@@ -0,0 +1,210 @@
1
+ ---
2
+ title: Sellers
3
+ description: Running a marketplace — vendors with their own catalog and panel, orders split per seller, and commission on every sale.
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ A marketplace sells things it doesn't own. Vendors list their own products, the marketplace takes a cut, and a shopper buying from three vendors at once expects one basket and one payment — not three checkouts.
9
+
10
+ A **seller** is a vendor on your marketplace: their own products, their own staff, their own orders, and their own panel to work in.
11
+
12
+ ```mermaid
13
+ flowchart TB
14
+ Customer["One customer, one payment"] --> Group["Order group"]
15
+ Group --> O1["Order — Seller A"]
16
+ Group --> O2["Order — Seller B"]
17
+ O1 --> F1["Seller A ships"]
18
+ O2 --> F2["Seller B ships"]
19
+
20
+ style Group fill:#e3f2fd,stroke:#0077ff
21
+ ```
22
+
23
+ > **INFO:** This is all open source. Sellers, product review, order splitting, commissions and the seller panel ship in the box — you don't need a commercial licence to run a marketplace on Spree.
24
+
25
+ ## The seller lifecycle
26
+
27
+ A seller isn't simply created and switched on. Bringing a vendor onto a marketplace is a process with a decision at the end of it, and the status reflects where they are in it.
28
+
29
+ ```mermaid
30
+ stateDiagram-v2
31
+ [*] --> pending
32
+ pending --> invited : marketplace invites them
33
+ invited --> onboarding : they accept
34
+ onboarding --> ready_for_review : they finish the checklist
35
+ ready_for_review --> approved : marketplace approves
36
+ ready_for_review --> rejected : sent back
37
+ rejected --> onboarding : they try again
38
+ approved --> suspended : paused
39
+ suspended --> approved : reinstated
40
+ ```
41
+
42
+ | Status | Meaning |
43
+ |---|---|
44
+ | `pending` | Created, nothing sent yet |
45
+ | `invited` | Invitation sent, not yet accepted |
46
+ | `onboarding` | Working through the requirements |
47
+ | `ready_for_review` | Waiting on the marketplace |
48
+ | `approved` | Live — can sell |
49
+ | `rejected` | Sent back, with a reason |
50
+ | `suspended` | Paused, temporarily |
51
+ | `canceled` | Gone |
52
+
53
+ Only an **approved** seller can sell, and even then not while they're on holiday — sellers can pause their own listings without the marketplace suspending them.
54
+
55
+ ```typescript Admin SDK
56
+ await adminClient.sellers.invite('sel_xxx')
57
+ await adminClient.sellers.approve('sel_xxx')
58
+ await adminClient.sellers.suspend('sel_xxx', { reason: 'Unresolved delivery complaints' })
59
+ ```
60
+
61
+ Status is never set by writing to the field — each move is its own action, so approving a seller can run the checks that belong to approving.
62
+
63
+ ## Onboarding requirements
64
+
65
+ What a vendor must do before selling differs by marketplace. A hardware marketplace wants insurance documents; a craft marketplace wants a filled-in profile and one product.
66
+
67
+ So the checklist is **configured, not hardcoded**. Each store defines its own:
68
+
69
+ ```typescript Admin SDK
70
+ const { data: types } = await adminClient.sellerRequirements.types()
71
+
72
+ await adminClient.sellerRequirements.create({
73
+ type: 'Spree::SellerRequirements::MinimumProducts',
74
+ name: 'List at least three products',
75
+ required: true,
76
+ preferences: { minimum_count: 3 },
77
+ })
78
+ ```
79
+
80
+ Requirements come in three flavours, which is what lets one mechanism cover very different demands:
81
+
82
+ | Kind | How it's satisfied |
83
+ |---|---|
84
+ | **Computed** | Automatically, from the seller's own data — a billing address exists, three products are listed |
85
+ | **Attested** | The seller confirms something — accepting terms |
86
+ | **Verified** | The seller submits something and the marketplace rules on it — a document, an operator review |
87
+
88
+ Shipped out of the box: accepting terms, completing the profile, a billing address, a returns address, and a minimum number of products. Also available are generic document upload, attestation, operator review, and required custom fields.
89
+
90
+ A seller sees their checklist and its progress:
91
+
92
+ ```typescript Seller SDK
93
+ const onboarding = await sellerClient.onboarding.get()
94
+
95
+ onboarding.progress // { done: 3, total: 5 }
96
+ onboarding.requirements.forEach((r) => {
97
+ r.name // "Accept the seller terms"
98
+ r.status // complete | incomplete | pending | rejected
99
+ r.required // whether it blocks approval
100
+ })
101
+
102
+ await sellerClient.onboarding.submitForReview()
103
+ ```
104
+
105
+ The checklist is **worked out when read**, never stored on the seller. So a requirement added next month applies immediately to everyone, and nothing has to be backfilled.
106
+
107
+ > **NOTE:** The checklist is enforced at exactly two moments — submitting for review, and approval. Approval refuses while a required item is outstanding, unless an operator deliberately overrides it.
108
+ >
109
+ > Afterwards it's advisory. If a seller's insurance certificate lapses, that's flagged for the marketplace to act on; it does not silently stop their sales mid-trade.
110
+
111
+ ## Products belong to sellers
112
+
113
+ A product can name a seller. No seller means it's the marketplace's own stock — a marketplace that also sells directly is a normal setup.
114
+
115
+ Sellers don't publish; they **submit**, and the marketplace decides. That review flow, and the statuses behind it, are covered in [Products](products.md#seller-submissions).
116
+
117
+ ```typescript Seller SDK
118
+ const product = await sellerClient.products.create({ name: 'Handmade Vase' })
119
+ await sellerClient.products.submit(product.id)
120
+ ```
121
+
122
+ Stores that trust their vendors can turn auto-approval on and skip the queue.
123
+
124
+ ## One checkout, several sellers
125
+
126
+ This is the part that makes a marketplace different from a shop, and it's worth understanding before you build a storefront against it.
127
+
128
+ A customer fills one basket, enters one address, and pays once. But each seller needs their own order — they fulfil separately, get paid separately, and must never see each other's business.
129
+
130
+ So at completion, a checkout spanning several sellers becomes an **order group**: one container holding one order per seller.
131
+
132
+ ```mermaid
133
+ erDiagram
134
+ OrderGroup ||--o{ Order : "one per seller"
135
+ OrderGroup ||--o{ Payment : "one payment"
136
+ Payment ||--o{ PaymentSplit : "apportioned"
137
+ Order }o--|| Seller : "belongs to"
138
+ Order ||--o{ Fulfillment : "shipped by that seller"
139
+ ```
140
+
141
+ | Level | Owns |
142
+ |---|---|
143
+ | **Order group** | The customer, the addresses, the payment, the combined totals |
144
+ | **Order** | One seller's items, fulfillments and money lines |
145
+
146
+ The single payment is apportioned across the child orders as **payment splits**, so each seller's share of one charge is recorded exactly — which is what makes per-seller refunds and settlement possible later.
147
+
148
+ Two details worth knowing:
149
+
150
+ - **Group totals are added up, not divided.** The group's total is the sum of its children, so it always agrees with them.
151
+ - **Delivery and order-level fees are shared out by item value**, so a seller whose goods made up most of the basket carries most of the delivery charge.
152
+
153
+ > **WARNING:** A storefront must handle the possibility of an order group. Completing a cart may yield one order or several, and a customer's order history should show the group as one purchase rather than confronting them with three orders they don't remember placing separately.
154
+
155
+ ## Commission
156
+
157
+ Commission is what the marketplace charges for the sale — configured as **rates**, and recorded per sale as immutable **commission lines**.
158
+
159
+ ```typescript Admin SDK
160
+ const { data: lines } = await adminClient.commissionLines.list({
161
+ filter: { seller_id_eq: 'sel_xxx' },
162
+ })
163
+ ```
164
+
165
+ Three things are worth knowing here; the rest is on its own page.
166
+
167
+ - **Rates are tried in list order**, and the first whose rules match wins. A rate with no rules matches everything, so the marketplace default belongs at the bottom.
168
+ - **Commission is charged on the seller's net revenue by default**, after discounts and excluding the customer's tax.
169
+ - **Commission tax follows the seller's jurisdiction**, not the shopper's — the marketplace is selling a service to the vendor, which is a separate supply from the vendor's sale to the customer.
170
+
171
+ See [Commissions](commissions.md) for rate targeting, the four rule types, per-currency floors and caps, and how a fee is calculated and taxed.
172
+
173
+ ## Payouts
174
+
175
+ Spree records what each seller earned and what the marketplace charged: the payment splits say what each seller's share of the money was, and the commission lines say what was deducted.
176
+
177
+ > **WARNING:** **Actually moving money to sellers is not implemented in open source.** There is no payout or transfer ledger here — a seller carries payout *preferences* (how often, and a minimum amount), but nothing acts on them.
178
+ >
179
+ > Paying vendors means connecting a provider such as Stripe Connect, or building against the payment splits and [commission lines](commissions.md). Scheduled payout runs, refund clawbacks, reconciliation and tax reporting are Enterprise features.
180
+
181
+ ## The seller panel
182
+
183
+ Sellers get their own application — not access to the marketplace's dashboard. It's a separate API with its own sign-in, and every request is scoped to the seller making it, so no endpoint even takes a seller ID.
184
+
185
+ That last point is the security property worth relying on: a seller cannot ask for another seller's data, because there's nowhere in the request to name one.
186
+
187
+ ```typescript Seller SDK
188
+ import { createSellerClient } from '@spree/seller-sdk'
189
+
190
+ const sellerClient = createSellerClient({ baseUrl: 'https://marketplace.example.com' })
191
+
192
+ await sellerClient.auth.login({ email: 'vendor@example.com', password: '…' })
193
+
194
+ const { data: orders } = await sellerClient.orders.list()
195
+ await sellerClient.orders.fulfillments.fulfill(orderId, fulfillmentId, {
196
+ tracking: '1Z999AA10123456784',
197
+ })
198
+ ```
199
+
200
+ Sellers can manage their profile, invite their own team, list and submit products, see and fulfil their orders, manage their stock locations, and work through onboarding. They cannot see other sellers, the marketplace's own catalog, or anything belonging to the store at large.
201
+
202
+ What a seller's staff may do is governed by [roles](staff-roles.md) owned by the seller — the same permission system as the back office, with a narrower set of keys, so a seller role can never reach store settings.
203
+
204
+ ## Related
205
+
206
+ - [Commissions](commissions.md) — rates, rules, and what the marketplace charges
207
+ - [Products](products.md#seller-submissions) — the listing review flow
208
+ - [Orders](orders.md) — orders and their statuses
209
+ - [Staff & Roles](staff-roles.md) — how seller teams are governed
210
+ - [Seller API](../../api-reference/seller-api/introduction.md) — the full endpoint reference
@@ -5,47 +5,80 @@ description: Manage Spree admin users, roles, invitations, and permissions — c
5
5
 
6
6
  ## Overview
7
7
 
8
- Admin users manage the store via the Admin Panel. They have roles that control what they can access.
8
+ Staff manage a store through the dashboard and the Admin API. What each person can do is decided by the **roles** they hold.
9
9
 
10
10
  ```mermaid
11
11
  erDiagram
12
- AdminUser ||--o{ RoleUser : "has many"
13
- RoleUser }o--|| Role : "belongs to"
14
- RoleUser }o--|| Store : "scoped to"
15
- AdminUser ||--o{ Invitation : "invites"
12
+ Store ||--o{ Role : "owns"
13
+ Seller ||--o{ Role : "owns"
14
+ Role ||--o{ RoleUser : "assigned through"
15
+ AdminUser ||--o{ RoleUser : "holds"
16
+ Role ||--o{ Invitation : "offered by"
16
17
 
17
- AdminUser {
18
- string id
19
- string email
18
+ Role {
19
+ string name
20
+ string description
21
+ json permissions
22
+ boolean mutable
20
23
  }
21
-
22
24
  RoleUser {
23
25
  string role_id
24
- string resource_type
25
- string resource_id
26
- }
27
-
28
- Role {
29
- string name
26
+ string user_id
30
27
  }
31
-
32
28
  Invitation {
33
29
  string email
34
30
  string status
35
- string token
36
31
  datetime expires_at
37
32
  }
38
33
  ```
39
34
 
40
- ## Roles
35
+ Two things about that shape matter:
36
+
37
+ **A role belongs to what it governs.** Every role names its owner — a Store for back-office staff, a [Seller](sellers.md) for a marketplace seller's own team. The owner is both who the role belongs to and who it applies to, so a role on a Store is a staff role by construction. Assigning someone a role therefore grants access to that owner and nothing else, which is what keeps one store's staff out of another's data.
38
+
39
+ Because roles are scoped to their owner, two stores can each define a "Manager" without colliding.
40
+
41
+ **A role carries its permissions directly.** The role holds a plain list of permission keys, so what a role can do is visible on the role itself rather than assembled from something else at runtime.
42
+
43
+ ## Roles and permissions
44
+
45
+ A permission key is a verb and a resource — `read_orders`, `write_products`. Roles hold a list of them:
46
+
47
+ ```typescript Admin SDK
48
+ const role = await adminClient.roles.create({
49
+ name: 'Fulfillment staff',
50
+ description: 'Can see orders and ship them, nothing else',
51
+ permissions: ['read_orders', 'write_fulfillments', 'read_products'],
52
+ })
53
+ ```
54
+
55
+ Every grantable key is discoverable, so a permission picker never needs a hardcoded list:
56
+
57
+ ```typescript Admin SDK
58
+ const { data: permissions } = await adminClient.permissions.list()
59
+ ```
60
+
61
+ Keys are grouped so they can be presented sensibly — orders, catalog, marketing, customers, settings, access and analytics.
62
+
63
+ | Group | Covers |
64
+ |---|---|
65
+ | Orders | Orders, payments, fulfillments, refunds, gift cards, store credit |
66
+ | Catalog | Products, media, categories, collections, stock, publishing |
67
+ | Marketing | Promotions |
68
+ | Customers | Customer accounts and groups |
69
+ | Settings | Store settings, webhooks, integrations |
70
+ | Access | API keys, staff, sellers, commissions |
71
+ | Analytics | The dashboard |
72
+
73
+ > **NOTE:** **The same vocabulary gates API keys.** A secret key's scopes come from this catalog too, so "what may this integration do" and "what may this person do" are described the same way — there is no second permission system to keep in step.
74
+
75
+ ### The admin role
41
76
 
42
- Admin users can have different roles that control their permissions:
77
+ Each store gets one protected `admin` role meaning *everything in this store*. It can't be renamed, edited or deleted, and it isn't shared between stores — each owner has its own.
43
78
 
44
- | Role | Description |
45
- |------|-------------|
46
- | `admin` | Full access to all Admin Panel features |
79
+ A role is deletable only when nothing depends on it: staff assignments and pending invitations have to be moved first, so nobody silently loses access.
47
80
 
48
- > **INFO:** You can create custom roles with specific permissions. See the [Customize Permissions guide](../customization/permissions.md) for details.
81
+ > **INFO:** Roles are pure data. They're created through the dashboard, the Admin API, or seeds — there's no code-level role definition to keep in sync. For record-level rules beyond what keys express, see [Customize Permissions](../customization/permissions.md).
49
82
 
50
83
  ## Creating Admin Users
51
84
 
@@ -165,11 +165,11 @@ Whichever provider is in use, the result is the same: tax lines on the order, in
165
165
 
166
166
  Business customers are often exempt, and the paperwork is real. Spree records a customer's or company's **tax identifier** — a VAT number, an ABN — and can validate it. Companies can also hold exemption certificates, scoped to the country or state that issued them.
167
167
 
168
- Exemption is decided when tax is worked out, not stored as a flag on the customer, so an expired certificate stops applying by itself. See [Companies & Catalogs](companies-and-catalogs.md).
168
+ Exemption is decided when tax is worked out, not stored as a flag on the customer, so an expired certificate stops applying by itself. See [Companies](companies.md).
169
169
 
170
170
  ## Related
171
171
 
172
172
  - [Order totals](order-totals.md) — how tax rolls into what a customer pays
173
173
  - [Markets](markets.md) — where tax treatment and provider are chosen
174
174
  - [Addresses](addresses.md) — what determines the rate
175
- - [Companies & Catalogs](companies-and-catalogs.md) — B2B tax identifiers and exemptions
175
+ - [Companies](companies.md) — B2B tax identifiers and exemptions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.183",
3
+ "version": "0.1.185",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",
@@ -1,81 +0,0 @@
1
- ---
2
- title: Companies & Catalogs
3
- description: How Spree models B2B buyer organizations — a multi-level company tree with members and an address book — and the catalogs that decide what each audience sees and pays.
4
- ---
5
-
6
- ## Overview
7
-
8
- A business customer in Spree is a **tree of company nodes**, not a flat account. One node can stand alone — a company with a few members and addresses — or grow into a group: subsidiaries, divisions, regional units, each with its own buyers and ship-to sites.
9
-
10
- ```mermaid
11
- erDiagram
12
- Company ||--o{ Company : "parent of"
13
- Company ||--o{ CompanyMembership : "has many"
14
- Company ||--o{ CompanyAddress : "has many"
15
- Company ||--o{ CompanyInvitation : "has many"
16
- Company ||--o{ TaxIdentifier : "legal entities only"
17
- Company ||--o{ TaxExemptionCertificate : "legal entities only"
18
-
19
- Company {
20
- string name
21
- string kind "company | division"
22
- string external_id
23
- }
24
- CompanyMembership {
25
- string role "cosmetic label"
26
- }
27
- CompanyAddress {
28
- string label
29
- boolean default_billing
30
- boolean default_shipping
31
- }
32
- CompanyInvitation {
33
- string email
34
- string token
35
- datetime expires_at
36
- }
37
- ```
38
-
39
- Three ideas carry the whole model:
40
-
41
- - **The tree is structure.** `Spree::Company` is self-referential (`parent_id`), store-scoped, and shallow — depth is capped at five levels. How many ship-to sites a node has never dictates how many nodes exist; addresses are just an address book.
42
- - **Nodes are typed, and the type decides tax.** A `company` node is a legal entity; a `division` is an organizational unit. Tax registrations and exemption certificates exist only on legal entities, and a purchase's tax always resolves through the node's `legal_entity` — for a `company` that is the node itself, and for a `division` it is its nearest `company` ancestor, whose registrations then cover the division's purchases. The walk stops at that first `company` node whether or not it holds any registration, so a subsidiary with no VAT number of its own has none — it never borrows its parent's.
43
- - **Membership is standing over a subtree.** A `CompanyMembership` joins a customer to a node, and that standing covers the node and everything below it. Any authorization question — "may this customer act for node N?" — is a membership check on N and its ancestors, never an equality check on one node.
44
-
45
- ## Members and invitations
46
-
47
- People join by email, from either side — staff in the dashboard or an existing member on the storefront. An email that matches a store customer becomes a membership immediately; an unknown email becomes a `Spree::CompanyInvitation` — a plaintext-token record with a 30-day expiry, whose invite email links to the storefront acceptance page. Accepting either registers a new account (through the standard customer-creation flow, with the invited email) or binds the invitation to the signed-in customer, and lands as a membership either way.
48
-
49
- Memberships stay always-active and always customer-backed: the pending state lives on the invitation, never as a status on the membership.
50
-
51
- **In open source, every member can do everything within their standing** — buy, see the subtree's purchases, manage addresses and members. There are no company roles; the `role` string on a membership is a cosmetic label. Finer control (roles, approvals, spending limits) is an Enterprise layer that enforces through the storefront access policy class (`Spree::Dependencies.storefront_access_policy_class`) and the checkout `validate` hooks — injection points that are deliberate no-ops in OSS.
52
-
53
- ## Purchases
54
-
55
- Carts and orders carry a `company_id` pointing at any node — buying *for* a division means pointing at it. A buyer with exactly one membership resolves to it automatically; a buyer with several names the node on their cart (the `company_id` param on cart update, validated against their standing). The node is frozen onto the order at completion, like every other order attribute, so a placed order's tax treatment stays explainable no matter how the tree changes later.
56
-
57
- Exemption certificates and the company's tax registration always resolve through `company.legal_entity` — see [Taxes](taxes.md) for how they reach the tax provider.
58
-
59
- ## Catalogs
60
-
61
- A `Spree::Catalog` narrows what an audience sees and — through an optional price list — what they pay.
62
-
63
- ```mermaid
64
- erDiagram
65
- Catalog ||--o{ CatalogProduct : "assortment"
66
- Catalog ||--o{ CatalogAssignment : "audiences"
67
- Catalog }o--o| PriceList : "optional pricing"
68
- CatalogAssignment }o--|| Company : "or"
69
- CatalogAssignment }o--|| CustomerGroup : "or"
70
- CatalogAssignment }o--|| Market : "or"
71
- CatalogAssignment }o--|| Channel : "or"
72
- ```
73
-
74
- - **Assortment** — a positioned product join, and the switch between a catalog's two modes. A *curated* assortment restricts: the audience sees only what's in it. An *empty* assortment is a pricing-only overlay: the attached price list applies and nothing is hidden — which is how "everyone sees the public store, this company just gets special prices" is expressed. An explicit import action copies the price list's products into the assortment when a restrictive catalog should start from the priced range.
75
- - **Audiences** — assignments to a channel, a customer group, a market, or a company node. A company assignment covers the node's **subtree**: assign the group-wide catalog once at the root, and a branch adds its own extra catalog without re-assigning the shared one.
76
- - **Visibility** — a buyer resolving to a company node sees the union of the effective catalogs on the node and its ancestors; otherwise their customer group's catalogs apply; otherwise every channel listing (narrowed to the channel's default catalog when one is set). Any effective catalog with an empty assortment lifts the restriction — the union then includes the whole range. Gated storefront access runs first — a login-gated guest never reaches this resolution.
77
- - **Pricing** — the effective catalogs' price lists are checked nearest node first (the purchase node's own assignments before its parent's), then the ordinary rule-matched price lists, then base prices. A price list attached to a catalog applies *because the catalog applies* — its own rules are not consulted, and it is excluded from generic rule matching so a rule-less list cannot leak to every shopper.
78
-
79
- ## Storefront self-service
80
-
81
- The Store API ships a full company directory for members: their memberships with ancestor paths (`GET /store/account/companies`), node details and renaming, the address book, members and invitations, and the subtree's completed orders. Authorization on every endpoint is **standing plus the access policy** — never roles, never CanCanCan. The deliberate trade-off: within a company, OSS trusts every member; merchants who need restraint layer the Enterprise governance on the same injection points, with no schema changes.