@spree/docs 0.1.182 → 0.1.184
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/dist/developer/core-concepts/addresses.md +106 -198
- package/dist/developer/core-concepts/architecture.md +97 -126
- package/dist/developer/core-concepts/calculators.md +75 -252
- package/dist/developer/core-concepts/carts.md +1 -1
- package/dist/developer/core-concepts/catalogs.md +140 -0
- package/dist/developer/core-concepts/channels.md +0 -4
- package/dist/developer/core-concepts/commissions.md +253 -0
- package/dist/developer/core-concepts/companies.md +240 -0
- package/dist/developer/core-concepts/customers.md +0 -3
- package/dist/developer/core-concepts/discounts.md +133 -0
- package/dist/developer/core-concepts/events.md +83 -576
- package/dist/developer/core-concepts/fees.md +144 -0
- package/dist/developer/core-concepts/imports-exports.md +105 -679
- package/dist/developer/core-concepts/inventory.md +114 -248
- package/dist/developer/core-concepts/markets.md +9 -12
- package/dist/developer/core-concepts/media.md +127 -16
- package/dist/developer/core-concepts/metafields.md +123 -200
- package/dist/developer/core-concepts/order-totals.md +110 -0
- package/dist/developer/core-concepts/orders.md +1 -1
- package/dist/developer/core-concepts/payments.md +11 -14
- package/dist/developer/core-concepts/pricing.md +11 -13
- package/dist/developer/core-concepts/products.md +191 -62
- package/dist/developer/core-concepts/promotions.md +12 -11
- package/dist/developer/core-concepts/search-filtering.md +2 -4
- package/dist/developer/core-concepts/sellers.md +210 -0
- package/dist/developer/core-concepts/staff-roles.md +56 -23
- package/dist/developer/core-concepts/store-credits-gift-cards.md +0 -3
- package/dist/developer/core-concepts/taxes.md +125 -113
- package/dist/developer/core-concepts/translations.md +61 -68
- package/dist/developer/core-concepts/webhooks.md +25 -59
- package/dist/developer/how-to/custom-promotion.md +3 -3
- package/package.json +1 -1
- package/dist/developer/core-concepts/companies-and-catalogs.md +0 -81
- package/dist/developer/core-concepts/taxes-discounts-fees.md +0 -199
|
@@ -5,7 +5,9 @@ description: Build percentage and fixed-amount discounts, free shipping, BOGO of
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
A promotion is a campaign: a percentage off, free shipping, or free items in the cart. **Rules** decide when it applies, **actions** decide what it does.
|
|
9
|
+
|
|
10
|
+
A promotion is a *rule that exists on its own*, set up in advance and lasting across many orders. What lands on any one order is a [Discount](discounts.md) — the record of money actually coming off. The promotion is the campaign; the discount is what a particular customer got.
|
|
9
11
|
|
|
10
12
|
```mermaid
|
|
11
13
|
erDiagram
|
|
@@ -52,14 +54,13 @@ Promotions can be activated in two ways:
|
|
|
52
54
|
- **Automatic promotions** — applied on their own when the rules are met (e.g. free shipping on orders over $50)
|
|
53
55
|
- **Coupon code promotions** — applied when a customer enters a valid code during checkout
|
|
54
56
|
|
|
55
|
-
###
|
|
57
|
+
### When promotions compete
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
Several promotions can qualify for the same item at once. Spree applies **only the one that saves the customer the most**, and records just that one.
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
Losing candidates aren't stored or tracked — they're simply reconsidered on every cart change. So a promotion that loses today can win tomorrow, when the basket changes, without any bookkeeping to keep straight.
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
- **A discount on the whole order is shared out across the items.** If a customer later returns one item, Spree knows exactly how much of the discount belonged to it.
|
|
63
|
+
What an applied promotion leaves behind on the order is a [Discount](discounts.md) — a permanent record that survives the promotion being edited or deleted.
|
|
63
64
|
|
|
64
65
|
## Promotion Attributes
|
|
65
66
|
|
|
@@ -112,7 +113,7 @@ You can also [build your own rules](../how-to/custom-promotion.md) for business-
|
|
|
112
113
|
|
|
113
114
|
Actions define what happens when a promotion applies.
|
|
114
115
|
|
|
115
|
-
### CreateAdjustment
|
|
116
|
+
### Order discount (`CreateAdjustment`)
|
|
116
117
|
|
|
117
118
|
A discount on the whole order. The amount is distributed proportionally across the line items — there is no single order-level row, so per-item reporting and partial returns always know their share.
|
|
118
119
|
|
|
@@ -120,7 +121,7 @@ A discount on the whole order. The amount is distributed proportionally across t
|
|
|
120
121
|
|
|
121
122
|
**Use case:** "10% off your order", "$20 off orders over $100".
|
|
122
123
|
|
|
123
|
-
### CreateItemAdjustments
|
|
124
|
+
### Item discount (`CreateItemAdjustments`)
|
|
124
125
|
|
|
125
126
|
A discount on individual line items. Only items that match the promotion's rules receive it — if a Category rule says "Electronics", only electronics are discounted.
|
|
126
127
|
|
|
@@ -128,13 +129,13 @@ A discount on individual line items. Only items that match the promotion's rules
|
|
|
128
129
|
|
|
129
130
|
**Use case:** "15% off shoes", "Buy 2+ shirts get 10% off each".
|
|
130
131
|
|
|
131
|
-
### FreeShipping
|
|
132
|
+
### Free shipping (`FreeShipping`)
|
|
132
133
|
|
|
133
134
|
Writes a discount on each fulfillment covering its delivery cost. The row is kept even when the cost is zero — its presence is what marks the order as having free shipping.
|
|
134
135
|
|
|
135
136
|
**Use case:** "Free shipping on orders over $75", "Free shipping with code FREESHIP".
|
|
136
137
|
|
|
137
|
-
### CreateLineItems
|
|
138
|
+
### Free items (`CreateLineItems`)
|
|
138
139
|
|
|
139
140
|
Adds specified products to the cart when the promotion is eligible, checking stock first. Items are not removed automatically if eligibility is lost — customers remove them manually.
|
|
140
141
|
|
|
@@ -245,7 +246,7 @@ Automatic promotions need no customer action at all — they are evaluated on ev
|
|
|
245
246
|
|
|
246
247
|
## Related Documentation
|
|
247
248
|
|
|
248
|
-
- [
|
|
249
|
+
- [Discounts](discounts.md) — the rows promotions write, and manual discounts
|
|
249
250
|
- [Build Custom Promotion Rules & Actions](../how-to/custom-promotion.md) — step-by-step guide to custom rules, actions and adjusters
|
|
250
251
|
- [Calculators](calculators.md) — promotion calculators
|
|
251
252
|
- [Carts](carts.md) — the cart lifecycle promotions act on
|
|
@@ -3,13 +3,11 @@ title: Search & Filtering
|
|
|
3
3
|
description: "Use Ransack-style query parameters to search, filter, sort, and paginate products, taxons, and other resources through the Spree Store API."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { Since } from '/snippets/since.mdx';
|
|
7
|
-
|
|
8
6
|
## Overview
|
|
9
7
|
|
|
10
8
|
Spree provides powerful search, filtering, and sorting capabilities for products and other resources. The Store API supports:
|
|
11
9
|
|
|
12
|
-
- Full-text search across product names, SKUs, and searchable
|
|
10
|
+
- Full-text search across product names, SKUs, and searchable custom fields
|
|
13
11
|
- Attribute-based filtering (price range, availability, stock status)
|
|
14
12
|
- Category and taxon filtering
|
|
15
13
|
- Faceted search with filter counts
|
|
@@ -276,7 +274,7 @@ curl 'https://api.mystore.com/api/v3/store/products?page=1&limit=24' \
|
|
|
276
274
|
|
|
277
275
|
See [Querying](../../api-reference/store-api/querying.md) for the full list of filtering, sorting, and pagination options.
|
|
278
276
|
|
|
279
|
-
## Search Providers
|
|
277
|
+
## Search Providers
|
|
280
278
|
|
|
281
279
|
Spree uses a pluggable search provider architecture. The default provider uses SQL (ILIKE + Ransack). For production catalogs with 1,000+ products, we recommend switching to [Meilisearch](../../integrations/search/meilisearch.md) for typo tolerance, relevance ranking, and faster faceted search.
|
|
282
280
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
AdminUser ||--o{
|
|
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
|
-
|
|
18
|
-
string
|
|
19
|
-
string
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:**
|
|
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
|
|
|
@@ -3,9 +3,6 @@ title: Store Credits & Gift Cards
|
|
|
3
3
|
description: How Spree models store credits and gift cards — stored value balances, redeemable codes, and checkout usage for refunds, loyalty, and gifting.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
import { Since } from '/snippets/since.mdx';
|
|
7
|
-
|
|
8
|
-
|
|
9
6
|
## Overview
|
|
10
7
|
|
|
11
8
|
Spree provides two stored value mechanisms that customers can use at checkout:
|