@spree/docs 0.1.240 → 0.1.242
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/delivery-setup.md +3 -1
- package/dist/developer/core-concepts/freight.md +279 -0
- package/dist/developer/core-concepts/metafields.md +0 -2
- package/dist/developer/core-concepts/payments.md +123 -72
- package/dist/developer/core-concepts/sellers.md +50 -5
- 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/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**
|
|
@@ -155,7 +155,8 @@ otherwise every parcel is quoted light.
|
|
|
155
155
|
|
|
156
156
|
Package types are that vocabulary: the box parcels ship in, and for wholesale,
|
|
157
157
|
the cartons products are packed into and the pallets or containers an order
|
|
158
|
-
leaves on.
|
|
158
|
+
leaves on. How those cartons and pallets roll up into a quote is
|
|
159
|
+
[freight](freight.md).
|
|
159
160
|
|
|
160
161
|
|
|
161
162
|
```typescript Admin SDK
|
|
@@ -248,6 +249,7 @@ box of their own with all three dimensions and a weight recorded. See
|
|
|
248
249
|
|
|
249
250
|
## Related
|
|
250
251
|
|
|
252
|
+
- [Freight](freight.md) — cartons, pallets and wholesale shipping
|
|
251
253
|
- [Fulfillments](fulfillments.md) — what happens to the parcel afterwards
|
|
252
254
|
- [Calculators](calculators.md) — how a price is worked out
|
|
253
255
|
- [EasyPost](../../integrations/shipping/easypost.md) — live carrier rates and labels
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Freight
|
|
3
|
+
description: How wholesale orders ship — the carton and pallet chain a product declares, the logistics rollup a forwarder quotes against, and rates that carry no price yet.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
A wholesale order does not leave the warehouse as parcels. It leaves as
|
|
9
|
+
cartons, a pallet, or a container, and for international freight the price is
|
|
10
|
+
quoted by a forwarder after someone looks at the order — not by an API at
|
|
11
|
+
checkout.
|
|
12
|
+
|
|
13
|
+
Four pieces make that work, and none of them disturb retail shipping:
|
|
14
|
+
|
|
15
|
+
| Piece | Question it answers |
|
|
16
|
+
|---|---|
|
|
17
|
+
| **Carton details** on a product | How do these units pack? |
|
|
18
|
+
| **Freight summary** on a cart or order | How big is the whole load? |
|
|
19
|
+
| **Volume and company rules** on a delivery method | Which shipment tier is this? |
|
|
20
|
+
| **Unpriced rates** | What does a buyer see before a price exists? |
|
|
21
|
+
|
|
22
|
+
One catalog serves both audiences. The same product ships parcel to a shopper
|
|
23
|
+
and carton to a trade buyer, because freight methods sit on the ordinary
|
|
24
|
+
shipping profile and are gated by who is buying rather than by what is bought.
|
|
25
|
+
See [delivery setup](delivery-setup.md) for profiles,
|
|
26
|
+
zones and methods, and [package types](delivery-setup.md#packaging)
|
|
27
|
+
for the packaging vocabulary this builds on.
|
|
28
|
+
|
|
29
|
+
## How products pack
|
|
30
|
+
|
|
31
|
+
Freight numbers are not derivable from retail shipping data. A case of 48
|
|
32
|
+
bottles is one carton, not 48 parcels — so a product says how its units pack,
|
|
33
|
+
and the rest follows the chain **unit → carton → pallet → cubic meters and
|
|
34
|
+
weight**.
|
|
35
|
+
|
|
36
|
+
The geometry lives on a shared carton, because merchants reuse a handful of
|
|
37
|
+
standard sizes across hundreds of products and one edit should fix all of
|
|
38
|
+
them. What varies per product stays on the product:
|
|
39
|
+
|
|
40
|
+
| Field | What it says |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `carton_package_type_id` | Which carton this is packed into. Must be a package type of kind `carton` |
|
|
43
|
+
| `units_per_carton` | How many units one carton holds |
|
|
44
|
+
| `carton_weight` | What a packed carton weighs, gross. Optional |
|
|
45
|
+
| `cartons_per_pallet` | How those cartons stack |
|
|
46
|
+
| `purchase_unit` | `unit` or `carton` — what a buyer is quoted in |
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
```typescript Admin SDK
|
|
50
|
+
// The carton, once, shared by every product packed in it.
|
|
51
|
+
const carton = await adminClient.packageTypes.create({
|
|
52
|
+
name: 'Master carton',
|
|
53
|
+
kind: 'carton',
|
|
54
|
+
length: 40, width: 30, height: 25, dimensions_unit: 'cm',
|
|
55
|
+
weight: 0.4, max_weight: 20, weight_unit: 'kg',
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// How this product fills it.
|
|
59
|
+
await adminClient.products.variants.update('prod_xxx', 'variant_xxx', {
|
|
60
|
+
carton_package_type_id: carton.id,
|
|
61
|
+
units_per_carton: 48,
|
|
62
|
+
carton_weight: 19.2,
|
|
63
|
+
cartons_per_pallet: 40,
|
|
64
|
+
purchase_unit: 'carton',
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```bash cURL
|
|
69
|
+
curl -X POST 'https://api.mystore.com/api/v3/admin/package_types' \
|
|
70
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
71
|
+
-H 'Content-Type: application/json' \
|
|
72
|
+
-d '{
|
|
73
|
+
"name": "Master carton", "kind": "carton",
|
|
74
|
+
"length": 40, "width": 30, "height": 25, "dimensions_unit": "cm",
|
|
75
|
+
"weight": 0.4, "max_weight": 20, "weight_unit": "kg"
|
|
76
|
+
}'
|
|
77
|
+
|
|
78
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/products/prod_xxx/variants/variant_xxx' \
|
|
79
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
80
|
+
-H 'Content-Type: application/json' \
|
|
81
|
+
-d '{
|
|
82
|
+
"carton_package_type_id": "pkgtype_xxx",
|
|
83
|
+
"units_per_carton": 48,
|
|
84
|
+
"carton_weight": 19.2,
|
|
85
|
+
"cartons_per_pallet": 40,
|
|
86
|
+
"purchase_unit": "carton"
|
|
87
|
+
}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
Stored quantities are always units, at every level. `purchase_unit` only
|
|
92
|
+
changes the vocabulary a storefront presents — a buyer shown "2 cartons" is
|
|
93
|
+
buying 96 units.
|
|
94
|
+
|
|
95
|
+
Two data errors are refused while the merchant is still editing rather than
|
|
96
|
+
discovered through a customer complaint: an `order_multiple` that straddles
|
|
97
|
+
carton boundaries can never ship whole, and quoting in cartons without saying
|
|
98
|
+
how many units one holds leaves a storefront no way to render the offer.
|
|
99
|
+
|
|
100
|
+
Reading a product back adds `units_per_pallet`, derived from the two divisors
|
|
101
|
+
so a merchant sees the far end of the chain without doing the arithmetic. It
|
|
102
|
+
is null unless both halves are recorded.
|
|
103
|
+
|
|
104
|
+
> **NOTE:** A product may only be packed into a package type of kind `carton`, and a
|
|
105
|
+
> carton with products packed into it cannot later become a pallet. Repack the
|
|
106
|
+
> products first.
|
|
107
|
+
|
|
108
|
+
## The freight summary
|
|
109
|
+
|
|
110
|
+
The **freight summary** is the load as a forwarder reads it: how many units,
|
|
111
|
+
the cartons they fill, the pallets those stack onto, the cubic meters they
|
|
112
|
+
occupy and what the whole thing weighs.
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"total_units": 480,
|
|
117
|
+
"total_cartons": 10,
|
|
118
|
+
"total_pallets": 1,
|
|
119
|
+
"total_volume": "0.3",
|
|
120
|
+
"total_weight": "192.0",
|
|
121
|
+
"complete": true
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`total_volume` is cubic meters and `total_weight` is kilograms, both as
|
|
126
|
+
decimal strings. `total_pallets` is null unless every carton-bearing line says
|
|
127
|
+
how it stacks — one silent product would understate the load, so no figure is
|
|
128
|
+
better than a wrong one.
|
|
129
|
+
|
|
130
|
+
**`complete` is the field to read before quoting from any of it.** False means
|
|
131
|
+
part of the catalog carries no carton data, so those products were measured
|
|
132
|
+
loose from their unit dimensions instead. The numbers are still the best
|
|
133
|
+
available and real enough to quote against, but they are a floor rather than a
|
|
134
|
+
total, and a surface using them should say so.
|
|
135
|
+
|
|
136
|
+
A cart of unmeasured goods has no freight summary at all rather than one full
|
|
137
|
+
of zeros, which would read as a shipment that takes up no space.
|
|
138
|
+
|
|
139
|
+
### Where it appears
|
|
140
|
+
|
|
141
|
+
| Response | Shape |
|
|
142
|
+
|---|---|
|
|
143
|
+
| Store cart | Totals only |
|
|
144
|
+
| Store delivery rate | Totals only, on freight rates |
|
|
145
|
+
| Admin cart and order | Totals plus a `lines` breakdown |
|
|
146
|
+
| Admin delivery rate | Totals plus `lines` |
|
|
147
|
+
|
|
148
|
+
The per-product breakdown is back-office only. A buyer is told what their
|
|
149
|
+
shipment costs and how big it is; which SKU accounts for which part of it is
|
|
150
|
+
the warehouse's business. Each admin line carries `variant_id`, `sku`, `name`,
|
|
151
|
+
`units`, `cartons`, `pallets`, `units_per_carton`, `cartons_per_pallet`,
|
|
152
|
+
`weight_per_carton`, `volume`, `weight` and its own `complete` flag.
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
```typescript SDK
|
|
156
|
+
const cart = await client.carts.get('cart_xxx')
|
|
157
|
+
|
|
158
|
+
if (cart.freight_summary) {
|
|
159
|
+
const { total_cartons, total_pallets, total_volume, complete } = cart.freight_summary
|
|
160
|
+
// `complete: false` — show these as a minimum, not a total.
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```bash cURL
|
|
165
|
+
curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
|
|
166
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
167
|
+
-H 'X-Spree-Token: guest_token'
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
> **NOTE:** A placed order's summary is never re-derived from the live catalog. The freight
|
|
172
|
+
> provider freezes a copy onto the rate it quoted, and the order reads that — so
|
|
173
|
+
> a carton size corrected next month does not rewrite what this container held.
|
|
174
|
+
> Two freight consignments on one order are merged into the one load the
|
|
175
|
+
> forwarder sees, and a canceled one is left out.
|
|
176
|
+
|
|
177
|
+
## Freight rates
|
|
178
|
+
|
|
179
|
+
A freight method is an ordinary [delivery method](delivery-setup.md#delivery-methods-and-zones)
|
|
180
|
+
priced by the **Freight** rate provider. That provider quotes nothing and says
|
|
181
|
+
so: it returns a rate with no price, carrying the freight summary the merchant
|
|
182
|
+
will send to the forwarder in place of an amount.
|
|
183
|
+
|
|
184
|
+
Such a rate comes back with `unpriced: true`, and every money field on it reads
|
|
185
|
+
**"Quoted after review"** rather than a figure. That matters: a zero cost
|
|
186
|
+
rendering as "Free" over a container of goods is a promise the merchant cannot
|
|
187
|
+
keep. Unpriced rates are also sorted after every priced one and are never
|
|
188
|
+
preselected, so a buyer with a real parcel option still sees it first.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
```typescript SDK
|
|
192
|
+
const rates = fulfillment.delivery_rates
|
|
193
|
+
|
|
194
|
+
rates.forEach((rate) => {
|
|
195
|
+
if (rate.unpriced) {
|
|
196
|
+
rate.display_total // "Quoted after review"
|
|
197
|
+
rate.freight_summary // what the forwarder will quote against
|
|
198
|
+
} else {
|
|
199
|
+
rate.display_total // "$24.00"
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```bash cURL
|
|
205
|
+
curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
|
|
206
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
207
|
+
-H 'X-Spree-Token: guest_token'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
Checkout completes on an unpriced rate. The shipping price arrives afterwards,
|
|
212
|
+
once the forwarder has quoted it.
|
|
213
|
+
|
|
214
|
+
> **NOTE:** How a wholesale buyer **pays** — deposits, net terms, credit limits — is a
|
|
215
|
+
> separate subject and is not part of freight in 6.0. Partial payments are
|
|
216
|
+
> generally available and predate this work. See
|
|
217
|
+
> [payments](payments.md).
|
|
218
|
+
|
|
219
|
+
## Shipment tiers
|
|
220
|
+
|
|
221
|
+
Which freight method a load qualifies for is configuration, not code. Each
|
|
222
|
+
tier — "Cartons", "Pallet", "20ft container" — is an ordinary delivery method
|
|
223
|
+
bounded by a rule.
|
|
224
|
+
|
|
225
|
+
**Volume rule.** Bounds the method by packed volume in cubic meters: a pallet
|
|
226
|
+
method takes 1–15 CBM, a 20ft container 15–33, and the estimator offers
|
|
227
|
+
whichever one the order actually fills.
|
|
228
|
+
|
|
229
|
+
Its minimum is deliberately forgiving. A partly measured catalog understates
|
|
230
|
+
the load, so the figure is a floor rather than a measurement — it may raise a
|
|
231
|
+
shipment into a tier but never exclude it from one. A maximum still applies,
|
|
232
|
+
since passing one on too small a number errs toward offering the method.
|
|
233
|
+
|
|
234
|
+
**Company rule.** Splits freight from parcel by who is buying. On, the method
|
|
235
|
+
is offered only to orders placed for a [company](companies.md),
|
|
236
|
+
so a carton tier disappears from retail carts. Off, only to orders that are
|
|
237
|
+
not, which keeps parcel methods away from wholesale buyers. A method with no
|
|
238
|
+
company rule is offered to both, so the rule is only ever added to state a
|
|
239
|
+
split.
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
```typescript Admin SDK
|
|
243
|
+
await adminClient.deliveryMethods.create({
|
|
244
|
+
name: 'Pallet freight',
|
|
245
|
+
rate_provider: 'Spree::DeliveryRateProvider::Freight',
|
|
246
|
+
rules: [
|
|
247
|
+
{ type: 'volume_rule', preferences: { minimum_volume: 1, maximum_volume: 15 } },
|
|
248
|
+
{ type: 'company_rule', preferences: { company_orders_only: true } },
|
|
249
|
+
],
|
|
250
|
+
})
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```bash cURL
|
|
254
|
+
curl -X POST 'https://api.mystore.com/api/v3/admin/delivery_methods' \
|
|
255
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
256
|
+
-H 'Content-Type: application/json' \
|
|
257
|
+
-d '{
|
|
258
|
+
"name": "Pallet freight",
|
|
259
|
+
"rate_provider": "Spree::DeliveryRateProvider::Freight",
|
|
260
|
+
"rules": [
|
|
261
|
+
{ "type": "volume_rule", "preferences": { "minimum_volume": 1, "maximum_volume": 15 } },
|
|
262
|
+
{ "type": "company_rule", "preferences": { "company_orders_only": true } }
|
|
263
|
+
]
|
|
264
|
+
}'
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
A freight method never consults a calculator — there is no price to work out —
|
|
269
|
+
so admin surfaces hide its pricing form. `GET /api/v3/admin/delivery_methods/rate_providers`
|
|
270
|
+
lists the providers installed on the store, which is what a picker should read
|
|
271
|
+
rather than a hardcoded list.
|
|
272
|
+
|
|
273
|
+
## Related
|
|
274
|
+
|
|
275
|
+
- [Delivery setup](delivery-setup.md) — profiles, zones, methods and package types
|
|
276
|
+
- [Fulfillments](fulfillments.md) — what happens to the consignment afterwards
|
|
277
|
+
- [Companies](companies.md) — who a wholesale order is placed for
|
|
278
|
+
- [Build a B2B store](../how-to/build-a-b2b-store.md) — the wholesale setup end to end
|
|
279
|
+
- [Package types](/user/settings/package-types) — configuring packaging in the dashboard
|
|
@@ -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:
|