@spree/docs 0.1.239 → 0.1.241
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/api-reference/store-api/metadata.md +6 -6
- package/dist/developer/core-concepts/architecture.md +15 -19
- package/dist/developer/core-concepts/carts.md +71 -1
- package/dist/developer/core-concepts/metafields.md +0 -2
- package/dist/developer/create-spree-app/quickstart.md +22 -19
- package/dist/developer/customization/api.md +2 -2
- package/dist/developer/customization/checkout.md +117 -0
- package/dist/developer/customization/configuration.md +76 -175
- package/dist/developer/customization/decorators.md +120 -130
- package/dist/developer/customization/metadata.md +9 -9
- package/dist/developer/customization/model-preferences.md +34 -68
- package/dist/developer/customization/permissions.md +13 -28
- package/dist/developer/customization/validations.md +16 -43
- package/dist/developer/dashboard/concepts.md +4 -2
- package/dist/developer/dashboard/customization/backend.md +5 -2
- package/dist/developer/dashboard/customization/navigation.md +1 -1
- package/dist/developer/dashboard/customization/quickstart.md +1 -1
- package/dist/developer/dashboard/overview.md +2 -2
- package/dist/developer/dashboard/plugins/distributing.md +1 -1
- package/dist/developer/dashboard/public-api.md +3 -2
- package/dist/developer/dashboard/recipes/attribute-end-to-end.md +3 -3
- package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
- package/dist/developer/dashboard/recipes/page-action-button.md +2 -2
- package/dist/developer/dashboard/recipes/sidebar-widget.md +2 -2
- package/dist/developer/dashboard/slots-catalog.md +60 -0
- package/dist/developer/getting-started/quickstart.md +25 -24
- package/dist/developer/how-to/custom-promotion.md +1 -1
- package/dist/developer/how-to/custom-search-provider.md +2 -2
- package/dist/developer/how-to/custom-stock-splitter.md +2 -2
- package/dist/developer/sdk/admin/extending.md +1 -1
- package/dist/developer/sdk/admin/quickstart.md +1 -1
- package/dist/developer/sdk/admin/resources.md +1 -1
- package/dist/developer/sdk/configuration.md +1 -1
- package/dist/developer/sdk/extending.md +1 -1
- package/dist/developer/tutorial/testing.md +45 -91
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ description: "How to store and retrieve custom metadata on carts, orders, and li
|
|
|
6
6
|
|
|
7
7
|
The Store API supports storing arbitrary key-value **metadata** on carts (orders) and line items. Metadata is useful for tracking custom information like gift messages, attribution data, or integration-specific fields.
|
|
8
8
|
|
|
9
|
-
> **NOTE:** Metadata is different from [
|
|
9
|
+
> **NOTE:** Metadata is different from [custom fields](../../developer/core-concepts/metafields.md). Metadata is simple JSON storage (untyped, no schema), while custom fields are structured, typed, and schema-defined. Use metadata for integration data and internal tracking. Use custom fields for customer-facing custom attributes.
|
|
10
10
|
|
|
11
11
|
## Cart Metadata
|
|
12
12
|
|
|
@@ -146,18 +146,18 @@ Metadata is stored as a flat JSON object. You can use any keys and values:
|
|
|
146
146
|
}
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
## Metadata vs
|
|
149
|
+
## Metadata vs Custom Fields
|
|
150
150
|
|
|
151
|
-
Spree has two permanent, complementary systems for custom data — **metadata for machines,
|
|
151
|
+
Spree has two permanent, complementary systems for custom data — **metadata for machines, custom fields for humans**.
|
|
152
152
|
|
|
153
|
-
| | Metadata |
|
|
153
|
+
| | Metadata | Custom Fields |
|
|
154
154
|
|-|----------|------------|
|
|
155
155
|
| **Purpose** | Developer escape hatch — integration IDs, sync state | Merchant-defined structured attributes |
|
|
156
|
-
| **Schema** | Schemaless JSON — no definition required | Defined via
|
|
156
|
+
| **Schema** | Schemaless JSON — no definition required | Defined via CustomFieldDefinitions (typed, validated) |
|
|
157
157
|
| **Validation** | None | Type-specific validation |
|
|
158
158
|
| **Visibility** | Write-only in Store API, readable in Admin API | Configurable (admin-only or public) |
|
|
159
159
|
| **Admin UI** | JSON preview | Dedicated form fields |
|
|
160
160
|
| **Best for** | Integration data, tracking, attribution, write-and-forget | Product specs, custom attributes, customer-facing data |
|
|
161
161
|
| **API access** | Write via Store API, read via Admin API | Read/write via both APIs |
|
|
162
162
|
|
|
163
|
-
Both systems are here to stay. For more details on
|
|
163
|
+
Both systems are here to stay. For more details on custom fields, see the [Custom Fields guide](../../developer/core-concepts/metafields.md).
|
|
@@ -5,9 +5,10 @@ description: How Spree fits together — the catalog, the cart, the order, and f
|
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
-
Spree is a commerce engine you drive through a REST API. It holds the catalog, works out what a customer owes, takes the money, and tracks what was
|
|
8
|
+
Spree is a commerce engine you drive through a REST API. It holds the catalog, works out what a customer owes, takes the money, and tracks what was delivered. You build the storefront, the mobile app, Point of Sale or an internal tool on top of it.
|
|
9
9
|
|
|
10
|
-
There is no built-in storefront you have to accept
|
|
10
|
+
There is no built-in storefront you have to accept, however we do provide a [reference one built in Next.js](../storefront/nextjs/quickstart.md).
|
|
11
|
+
Everything a customer-facing app needs is on the Store API, and everything a back office needs is on the Admin API. Both ship as fully typed TypeScript clients.
|
|
11
12
|
|
|
12
13
|
## The shape of a purchase
|
|
13
14
|
|
|
@@ -72,7 +73,7 @@ Three things in that diagram are worth calling out, because they're where Spree
|
|
|
72
73
|
|
|
73
74
|
**Delivery is described by a profile, not a category.** A [delivery profile](fulfillments.md) says how a product travels — physically shipped, or digital — and which locations and methods serve it.
|
|
74
75
|
|
|
75
|
-
**Stock lives per location.** A variant
|
|
76
|
+
**Stock lives per location.** A variant can have a stock level at each [stock location](inventory.md), so availability is a real question about real warehouses rather than a single number.
|
|
76
77
|
|
|
77
78
|
## The two APIs
|
|
78
79
|
|
|
@@ -103,6 +104,7 @@ Spree assumes you'll extend it, and gives you three ways in that survive an upgr
|
|
|
103
104
|
|
|
104
105
|
| Approach | Use it for | Guide |
|
|
105
106
|
|---|---|---|
|
|
107
|
+
| **Events** | Reacting inside your own app when something happens — indexing a record, calling an API with your credentials | [Events](events.md) |
|
|
106
108
|
| **Webhooks** | Telling another system something happened — an ERP, a warehouse, an email tool | [Webhooks](webhooks.md) |
|
|
107
109
|
| **Custom fields** | Storing your own data on a product, order or customer without changing the schema | [Custom Fields](metafields.md) |
|
|
108
110
|
| **Providers** | Plugging in a real service for tax, delivery rates, payments or search | [Providers](../providers/overview.md) |
|
|
@@ -116,32 +118,26 @@ One Spree installation can run more than one business:
|
|
|
116
118
|
- **[Stores](stores.md)** are separate businesses — their own catalog, orders, customers and settings. Data does not cross between them.
|
|
117
119
|
- **[Channels](channels.md)** are the ways one store sells: a website, a mobile app, a retail till.
|
|
118
120
|
- **[Markets](markets.md)** are the regions a store sells into, each with its currency, locale and tax treatment.
|
|
119
|
-
- **[Sellers](
|
|
121
|
+
- **[Sellers](sellers.md)** let one store list goods from many vendors, for a marketplace.
|
|
120
122
|
|
|
121
123
|
## What you install
|
|
122
124
|
|
|
123
|
-
| Package | What it is |
|
|
124
|
-
|---|---|
|
|
125
|
-
| `spree` | The engine — catalog, checkout, payments, both APIs |
|
|
126
|
-
| `spree_emails` | Transactional email, if you don't send your own |
|
|
127
|
-
| `spree_dashboard` | The back-office dashboard |
|
|
128
|
-
|
|
129
|
-
Providers for search, delivery rates, tax and payments install separately, so you only carry what you use.
|
|
130
|
-
|
|
131
|
-
**TypeScript:**
|
|
132
|
-
|
|
133
125
|
| Package | What it is |
|
|
134
126
|
|---|---|
|
|
135
127
|
| [`@spree/sdk`](../sdk/quickstart.md) | Store API client — for your storefront |
|
|
136
128
|
| [`@spree/admin-sdk`](../sdk/admin/quickstart.md) | Admin API client — for back-office and integrations |
|
|
137
129
|
| [`@spree/cli`](../cli/quickstart.md) | Command line for local setup and calling the Admin API |
|
|
130
|
+
| [`@spree/dashboard`](../dashboard/overview.md) | The admin interface, a React app you own and extend |
|
|
138
131
|
|
|
139
|
-
> **INFO:** Spree runs on PostgreSQL, MySQL or SQLite.
|
|
132
|
+
> **INFO:** Spree runs on PostgreSQL, MySQL or SQLite. Check [database configuration](../deployment/database.md) for more details.
|
|
140
133
|
|
|
141
134
|
## Where to go next
|
|
142
135
|
|
|
136
|
+
Two ways in, depending on how you prefer to learn.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
- [Build something](../tutorial/introduction.md) — Add one feature end to end — a model with its own API, a screen in the dashboard, a page on the storefront, and the tests around it. Start here if you learn by doing.
|
|
140
|
+
- [Read the reference](products.md) — Every part of the platform, one page each: products and pricing, carts and orders, payments, fulfillment, promotions. Start here if you already know what you need.
|
|
141
|
+
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
- [Store SDK](../sdk/quickstart.md) — Build a storefront against the Store API.
|
|
146
|
-
- [Products](products.md) — How the catalog is modelled.
|
|
147
|
-
- [Carts](carts.md) — Cart, checkout, and what completion guarantees.
|
|
143
|
+
Building a storefront rather than extending the backend? Go straight to the [Spree Next.js Storefront](../storefront/nextjs/quickstart.md).
|
|
@@ -131,10 +131,80 @@ A cart tells you what it still needs before it can be completed:
|
|
|
131
131
|
}
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
Every entry has the same four fields. Branch on `code` — `message` is translated prose
|
|
135
|
+
meant for a person, and `step` is only a grouping hint.
|
|
135
136
|
|
|
136
137
|
This is what lets you design your own checkout. Spree tells you what's missing; you decide how and when to ask for it — one long page, a few steps, or whatever suits your storefront.
|
|
137
138
|
|
|
139
|
+
Seven checks run on every cart read:
|
|
140
|
+
|
|
141
|
+
| `code` | `step` | Raised when |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `line_items_required` | `cart` | The cart is empty |
|
|
144
|
+
| `email_required` | `address` | No email address |
|
|
145
|
+
| `ship_address_required` | `address` | Something needs shipping and there is no address |
|
|
146
|
+
| `delivery_method_required` | `delivery` | A delivery method hasn't been chosen |
|
|
147
|
+
| `payment_required` | `payment` | The total isn't covered by payments |
|
|
148
|
+
| `po_number_required` | `address` | A company requires a purchase-order number |
|
|
149
|
+
| `order_minimum_not_met` | `cart` | Below the order minimum, with the shortfall in the message |
|
|
150
|
+
|
|
151
|
+
> **WARNING:** **An empty `requirements` array does not guarantee completion will succeed.** What a
|
|
152
|
+
> cart reports is the *advisory* set. Completing one additionally checks stock
|
|
153
|
+
> (`out_of_stock`, `discontinued`), negotiated quantity rules
|
|
154
|
+
> (`quantity_rule_violated`) and the guest-checkout policy
|
|
155
|
+
> (`guest_checkout_not_allowed`) — each of which has to load line items, so they are
|
|
156
|
+
> held back to keep reading a cart cheap. Treat a failed completion as a normal path,
|
|
157
|
+
> not an exception.
|
|
158
|
+
|
|
159
|
+
### Checkout steps
|
|
160
|
+
|
|
161
|
+
A cart also reports a step list, so you can render a progress indicator:
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
```typescript Store SDK
|
|
165
|
+
const cart = await client.carts.get(cartId)
|
|
166
|
+
|
|
167
|
+
cart.current_step // "delivery"
|
|
168
|
+
cart.completed_steps // ["address"]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```json Response
|
|
172
|
+
{
|
|
173
|
+
"current_step": "delivery",
|
|
174
|
+
"completed_steps": ["address"]
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
Spree has no checkout state machine. The list is computed from the cart's own data,
|
|
180
|
+
and nothing on the server refuses a write because of where the customer appears to be
|
|
181
|
+
— you can set an address, choose delivery and attach a payment in any order.
|
|
182
|
+
`current_step` is simply the step of the first unmet requirement.
|
|
183
|
+
|
|
184
|
+
Five steps are built in, and three only appear when they apply:
|
|
185
|
+
|
|
186
|
+
| Step | Present when |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `address` | Always |
|
|
189
|
+
| `delivery` | The cart has items and isn't entirely digital |
|
|
190
|
+
| `payment` | The total is above zero |
|
|
191
|
+
| `confirm` | A payment method asks for one, or the store always wants one |
|
|
192
|
+
| `complete` | Always |
|
|
193
|
+
|
|
194
|
+
So a free digital order reports `address` then `complete`. A storefront that hardcodes
|
|
195
|
+
five steps will show two that never apply.
|
|
196
|
+
|
|
197
|
+
> **NOTE:** Requirements use one grouping name that is not a step: **`cart`**. Missing line
|
|
198
|
+
> items, the order minimum and stock problems are filed under it, and there is no
|
|
199
|
+
> `cart` entry in the step list — `current_step` reports those as `address`. If you
|
|
200
|
+
> group requirements by `step` to place them on screens, give `cart` a home.
|
|
201
|
+
|
|
202
|
+
Checkout introspection lives on the cart, not the order. An order has already been
|
|
203
|
+
placed, so `current_step` and `requirements` do not appear on it.
|
|
204
|
+
|
|
205
|
+
To add your own step or requirement, see
|
|
206
|
+
[Customizing checkout](../customization/checkout.md).
|
|
207
|
+
|
|
138
208
|
## Checkout
|
|
139
209
|
|
|
140
210
|
**Step 1: Add the customer's details**
|
|
@@ -9,8 +9,6 @@ Sooner or later you need to store something Spree doesn't have a field for. A fa
|
|
|
9
9
|
|
|
10
10
|
Custom fields are how you do that without changing the database. You declare a field once — its name, its type, who can see it — and from then on it can be set on any record of that kind, edited in the dashboard, returned by the API, and searched or filtered like any built-in field.
|
|
11
11
|
|
|
12
|
-
> **NOTE:** Custom fields are also known as **metafields** in the dashboard. The API and SDK call them custom fields throughout.
|
|
13
|
-
|
|
14
12
|
## Definitions and values
|
|
15
13
|
|
|
16
14
|
There are two halves, and it helps to keep them straight:
|
|
@@ -18,7 +18,7 @@ The CLI walks you through an interactive setup:
|
|
|
18
18
|
|
|
19
19
|
Once complete, your store is running at [http://localhost:3000](http://localhost:3000) — setup pulls the latest Spree image, seeds the database, and configures API keys, then prints a summary with your admin credentials and keys. If you skipped starting services, the first `pnpm dev` completes setup automatically.
|
|
20
20
|
|
|
21
|
-
The **
|
|
21
|
+
The **dashboard** and the marketplace **seller panel** are included in every project — there is no flag to add them.
|
|
22
22
|
|
|
23
23
|
## Prerequisites
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ npx create-spree-app@latest my-store --no-storefront --no-sample-data --no-start
|
|
|
35
35
|
|
|
36
36
|
| Flag | Description |
|
|
37
37
|
|------|-------------|
|
|
38
|
-
| `--react-dashboard` |
|
|
38
|
+
| `--react-dashboard` | No-op — the dashboard is always included. Kept so existing scripts keep working |
|
|
39
39
|
| `--no-storefront` | Skip Next.js storefront setup |
|
|
40
40
|
| `--no-sample-data` | Skip loading sample products and categories |
|
|
41
41
|
| `--no-start` | Don't start Docker services after scaffolding |
|
|
@@ -52,29 +52,32 @@ npx create-spree-app@latest my-store --no-storefront --no-sample-data --no-start
|
|
|
52
52
|
|
|
53
53
|
```text
|
|
54
54
|
my-store/
|
|
55
|
+
├── server/ # The Spree API — a full Rails app from spree/spree-starter
|
|
56
|
+
│ ├── app/ # Your models, controllers, serializers and jobs
|
|
57
|
+
│ ├── config/
|
|
58
|
+
│ ├── Dockerfile # Also builds the production image (API + dashboard)
|
|
59
|
+
│ └── Gemfile
|
|
60
|
+
├── apps/
|
|
61
|
+
│ ├── dashboard/ # Admin dashboard (React)
|
|
62
|
+
│ │ └── .env.local # Dev proxy target — no credentials
|
|
63
|
+
│ ├── seller-dashboard/ # Seller panel, for marketplaces (React)
|
|
64
|
+
│ └── storefront/ # Next.js storefront (unless --no-storefront)
|
|
65
|
+
│ └── .env.local # API URL + publishable key
|
|
66
|
+
├── .env # SECRET_KEY_BASE, SPREE_PORT, SPREE_VERSION_TAG, SPREE_SAMPLE_DATA
|
|
67
|
+
├── .spree/ # CLI credentials — gitignored, created on first run
|
|
55
68
|
├── docker-compose.yml # Spree backend (prebuilt image) + Postgres + Meilisearch
|
|
56
69
|
├── docker-compose.dev.yml # Alternative: build from local server/
|
|
57
70
|
├── render.yaml # Render Blueprint — one Docker service built from this repo
|
|
58
|
-
├── .env # SECRET_KEY_BASE, SPREE_PORT, SPREE_VERSION_TAG, SPREE_SAMPLE_DATA
|
|
59
71
|
├── .dockerignore # Keeps the production build context to sources
|
|
60
72
|
├── .gitignore
|
|
61
|
-
├── package.json #
|
|
62
|
-
├──
|
|
63
|
-
├──
|
|
64
|
-
|
|
65
|
-
│ ├── Dockerfile # Also builds the production image (API + dashboard)
|
|
66
|
-
│ ├── config/
|
|
67
|
-
│ ├── app/
|
|
68
|
-
│ └── ...
|
|
69
|
-
└── apps/
|
|
70
|
-
├── storefront/ # Next.js storefront (unless --no-storefront)
|
|
71
|
-
│ ├── .env.local # API URL + API key
|
|
72
|
-
│ └── ...
|
|
73
|
-
└── dashboard/ # React Dashboard (with --react-dashboard)
|
|
74
|
-
├── .env.local # Dev proxy target — no credentials
|
|
75
|
-
└── ...
|
|
73
|
+
├── package.json # Scripts and the pinned @spree/cli
|
|
74
|
+
├── AGENTS.md # Instructions for coding agents
|
|
75
|
+
├── CLAUDE.md # Project layout and conventions
|
|
76
|
+
└── README.md # How to run everything
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
**`server/` is the only part that is Spree's.** The apps under `apps/` are ordinary Vite and Next.js projects you own — restyle, restructure or replace them without forking anything. Each carries its own instructions (`server/CLAUDE.md`, `apps/dashboard/AGENTS.md`), which is also what a coding agent reads.
|
|
80
|
+
|
|
78
81
|
### What's in docker-compose.yml
|
|
79
82
|
|
|
80
83
|
- **Spree** — one `web` container running the `ghcr.io/spree/spree:latest` image on the configured port (default `3000`); background jobs run in-process via Solid Queue (stored in Postgres — job dashboard at `/jobs`)
|
|
@@ -127,7 +130,7 @@ Open [http://localhost:3000/admin](http://localhost:3000/admin) and log in with:
|
|
|
127
130
|
| **Email** | `spree@example.com` |
|
|
128
131
|
| **Password** | `spree123` |
|
|
129
132
|
|
|
130
|
-
|
|
133
|
+
The dashboard runs as its own dev server: `spree dev` starts it alongside the API at [http://localhost:5173](http://localhost:5173), with the same credentials, live-reloading from `apps/dashboard/`. See the [dashboard docs](../dashboard/overview.md).
|
|
131
134
|
|
|
132
135
|
### Store API
|
|
133
136
|
|
|
@@ -5,7 +5,7 @@ description: Add new Store API endpoints, customize existing JSON responses with
|
|
|
5
5
|
|
|
6
6
|
Before you start customizing Spree API endpoints, make sure you reviewed all existing API endpoints in the [Spree API docs](../../api-reference/admin-api/endpoints.md).
|
|
7
7
|
|
|
8
|
-
For a step-by-step walkthrough of adding a complete new resource (model, serializer, controller, routes), see the [API tutorial](../tutorial/
|
|
8
|
+
For a step-by-step walkthrough of adding a complete new resource (model, serializer, controller, routes), see the [API tutorial](../tutorial/model-and-api.md).
|
|
9
9
|
|
|
10
10
|
## Customizing JSON Responses
|
|
11
11
|
|
|
@@ -35,7 +35,7 @@ Restart the server and the Product API will include your new attribute.
|
|
|
35
35
|
|
|
36
36
|
### Adding an Association
|
|
37
37
|
|
|
38
|
-
Let's say you've created a `Spree::Brand` model that belongs to `Product` (see the [tutorial](../tutorial/
|
|
38
|
+
Let's say you've created a `Spree::Brand` model that belongs to `Product` (see the [tutorial](../tutorial/model-and-api.md) for the full example).
|
|
39
39
|
|
|
40
40
|
Create a serializer for the new model:
|
|
41
41
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Customizing Checkout
|
|
3
|
+
description: Add your own checkout step or requirement, and reorder or remove the built-in ones.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Spree does not run your checkout — your storefront does. What Spree owns is the list
|
|
7
|
+
of things a cart still needs, which it reports on cart reads and enforces when you
|
|
8
|
+
complete one. Adding to that list is how you make checkout require something of your
|
|
9
|
+
own.
|
|
10
|
+
|
|
11
|
+
> **NOTE:** For what a cart reports and how to read it, see
|
|
12
|
+
> [Carts](../core-concepts/carts.md#checkout-requirements). This page is about
|
|
13
|
+
> adding to it.
|
|
14
|
+
|
|
15
|
+
## Requirements are the gate, steps are a label
|
|
16
|
+
|
|
17
|
+
Two things are easy to confuse:
|
|
18
|
+
|
|
19
|
+
- A **requirement** is enforced. It appears on cart reads while unmet, and completing
|
|
20
|
+
a cart recomputes the list and refuses if anything is outstanding.
|
|
21
|
+
- A **step** is descriptive. It exists so a storefront can render a progress
|
|
22
|
+
indicator. Nothing on the server checks which step a customer is on.
|
|
23
|
+
|
|
24
|
+
So a requirement is what you reach for when something must be true before an order
|
|
25
|
+
exists. A step is only worth adding when your checkout genuinely has a stage the
|
|
26
|
+
built-in five don't describe.
|
|
27
|
+
|
|
28
|
+
## Adding a requirement
|
|
29
|
+
|
|
30
|
+
When the flow is unchanged and you only need one more condition:
|
|
31
|
+
|
|
32
|
+
```ruby server/config/initializers/spree.rb
|
|
33
|
+
Spree::Checkout::Registry.add_requirement(
|
|
34
|
+
step: :address,
|
|
35
|
+
field: :vat_number,
|
|
36
|
+
message: 'VAT number is required for business orders',
|
|
37
|
+
satisfied: ->(cart) { cart.custom_fields['vat_number'].present? }
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
While `satisfied:` returns false the cart reports the requirement and refuses to
|
|
42
|
+
complete. Its `code` is derived as `<field>_required` — `vat_number_required` here.
|
|
43
|
+
|
|
44
|
+
Pass `applicable:` to scope it. It is checked before `satisfied:`, so a requirement
|
|
45
|
+
that doesn't apply never appears at all:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
applicable: ->(cart) { cart.customer&.company.present? }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`message` is stored verbatim — wrap it in `Spree.t` yourself if it needs translating.
|
|
52
|
+
|
|
53
|
+
## Adding a step
|
|
54
|
+
|
|
55
|
+
A step bundles its own requirements and says when it is satisfied:
|
|
56
|
+
|
|
57
|
+
```ruby server/config/initializers/spree.rb
|
|
58
|
+
Spree::Checkout::Registry.register_step(
|
|
59
|
+
name: :loyalty,
|
|
60
|
+
before: :payment,
|
|
61
|
+
satisfied: ->(cart) { cart.custom_fields['loyalty_number'].present? },
|
|
62
|
+
requirements: ->(cart) {
|
|
63
|
+
[{ step: 'loyalty', field: 'loyalty_number',
|
|
64
|
+
message: 'Loyalty number is required' }]
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The step now shows up in the cart's `current_step` and `completed_steps`, its
|
|
70
|
+
requirements appear in `requirements` while unsatisfied, and completion refuses the
|
|
71
|
+
cart until they are met.
|
|
72
|
+
|
|
73
|
+
Each lambda receives the cart. Placement is controlled by two keywords:
|
|
74
|
+
|
|
75
|
+
| Keyword | Effect |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `before:` / `after:` | Place the step next to an existing one. `before:` wins if both are given |
|
|
78
|
+
| `applicable:` | Whether the step exists for this cart at all — checked before `satisfied:` |
|
|
79
|
+
|
|
80
|
+
An unanchored step lands immediately before `complete`. An anchor naming a step this
|
|
81
|
+
particular cart doesn't have — `before: :payment` on a free cart — falls back to the
|
|
82
|
+
same place.
|
|
83
|
+
|
|
84
|
+
Unlike an added requirement, a step's `requirements:` lambda may set its own `code:`.
|
|
85
|
+
|
|
86
|
+
## Reordering or removing built-in steps
|
|
87
|
+
|
|
88
|
+
```ruby server/config/initializers/spree.rb
|
|
89
|
+
# Drop a step from the reported list
|
|
90
|
+
Spree::Checkout::Registry.base_steps.delete('confirm')
|
|
91
|
+
|
|
92
|
+
# Or change when one applies
|
|
93
|
+
Spree::Checkout::Registry.base_steps['payment'] = ->(cart) { cart.total > 0 }
|
|
94
|
+
|
|
95
|
+
# Or reorder wholesale — known names keep their own applicability
|
|
96
|
+
Spree::Checkout::Registry.base_step_names = %w[address payment delivery complete]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> **WARNING:** Removing a step removes it from the reported list. It does **not** remove the
|
|
100
|
+
> requirements filed under it — dropping `payment` does not let an unpaid cart
|
|
101
|
+
> complete. Requirements and steps are separate on purpose.
|
|
102
|
+
|
|
103
|
+
## Prefer a requirement to a workflow hook
|
|
104
|
+
|
|
105
|
+
A [workflow hook](workflows.md) on `carts.complete.validate`
|
|
106
|
+
can also reject a cart, and there are cases for it — anything needing a network call, or a
|
|
107
|
+
decision that only makes sense at the moment of completion.
|
|
108
|
+
|
|
109
|
+
For anything a customer has to fix, a requirement is better: it is reported on every
|
|
110
|
+
cart read, so the storefront can show it while they can still act, rather than failing
|
|
111
|
+
at the last step with an error they have to interpret.
|
|
112
|
+
|
|
113
|
+
## Related
|
|
114
|
+
|
|
115
|
+
- [Carts](../core-concepts/carts.md) — reading requirements and completing a cart
|
|
116
|
+
- [Custom fields](../core-concepts/metafields.md) — storing your own data on a cart
|
|
117
|
+
- [Services & Workflows](workflows.md) — hooks that run during completion
|