@spree/docs 0.1.45 → 0.1.47
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/webhooks-events.md +20 -18
- package/dist/developer/core-concepts/adjustments.md +2 -2
- package/dist/developer/core-concepts/events.md +2 -2
- package/dist/developer/core-concepts/inventory.md +56 -0
- package/dist/developer/core-concepts/orders.md +22 -19
- package/dist/developer/core-concepts/payments.md +1 -1
- package/dist/developer/core-concepts/shipments.md +24 -19
- package/dist/developer/core-concepts/webhooks.md +1 -1
- package/dist/developer/upgrades/5.4-to-5.5.md +99 -0
- package/package.json +1 -1
|
@@ -37,7 +37,7 @@ For details on creating webhook endpoints and verifying signatures, see [Webhook
|
|
|
37
37
|
|
|
38
38
|
Events: `order.created`, `order.updated`, `order.completed`, `order.canceled`, `order.resumed`, `order.paid`, `order.shipped`
|
|
39
39
|
|
|
40
|
-
Order payloads include nested `items`, `shipments
|
|
40
|
+
Order payloads include nested `items`, `fulfillments` (the Store API name for shipments), `payments`, `billing_address`, `shipping_address`, `payment_methods`, and `discounts`.
|
|
41
41
|
|
|
42
42
|
```json
|
|
43
43
|
{
|
|
@@ -49,12 +49,12 @@ Order payloads include nested `items`, `shipments`, `payments`, `bill_address`,
|
|
|
49
49
|
"special_instructions": null,
|
|
50
50
|
"currency": "USD",
|
|
51
51
|
"item_count": 3,
|
|
52
|
-
"
|
|
52
|
+
"fulfillment_status": "shipped",
|
|
53
53
|
"payment_state": "paid",
|
|
54
54
|
"item_total": "89.99",
|
|
55
55
|
"display_item_total": "$89.99",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
56
|
+
"delivery_total": "10.00",
|
|
57
|
+
"display_delivery_total": "$10.00",
|
|
58
58
|
"adjustment_total": "0.00",
|
|
59
59
|
"display_adjustment_total": "$0.00",
|
|
60
60
|
"promo_total": "0.00",
|
|
@@ -90,19 +90,20 @@ Order payloads include nested `items`, `shipments`, `payments`, `bill_address`,
|
|
|
90
90
|
"..."
|
|
91
91
|
}
|
|
92
92
|
],
|
|
93
|
-
"
|
|
93
|
+
"fulfillments": [
|
|
94
94
|
{
|
|
95
|
-
"id": "
|
|
95
|
+
"id": "ful_9xPq4wMn",
|
|
96
96
|
"number": "H123456789",
|
|
97
|
-
"
|
|
97
|
+
"status": "shipped",
|
|
98
|
+
"fulfillment_type": "shipping",
|
|
98
99
|
"tracking": "1Z999AA10123456784",
|
|
99
100
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=1Z999AA10123456784",
|
|
100
101
|
"cost": "10.00",
|
|
101
102
|
"display_cost": "$10.00",
|
|
102
|
-
"
|
|
103
|
-
"
|
|
103
|
+
"fulfilled_at": "2025-01-16T14:00:00Z",
|
|
104
|
+
"delivery_method": { "id": "dm_2wMn7xRt", "..." },
|
|
104
105
|
"stock_location": { "id": "sl_2wMn7xRt", "..." },
|
|
105
|
-
"
|
|
106
|
+
"delivery_rates": [],
|
|
106
107
|
"..."
|
|
107
108
|
}
|
|
108
109
|
],
|
|
@@ -268,29 +269,30 @@ Payment setup session payloads include a nested `payment_method`.
|
|
|
268
269
|
|
|
269
270
|
Events: `shipment.created`, `shipment.updated`, `shipment.shipped`, `shipment.canceled`, `shipment.resumed`
|
|
270
271
|
|
|
271
|
-
|
|
272
|
+
The Store API/SDK exposes shipments as `fulfillments`, and webhook payloads use the same V3 serializer. Payloads include nested `delivery_method`, `stock_location`, and `delivery_rates`.
|
|
272
273
|
|
|
273
274
|
```json
|
|
274
275
|
{
|
|
275
|
-
"id": "
|
|
276
|
+
"id": "ful_9xPq4wMn",
|
|
276
277
|
"number": "H123456789",
|
|
277
|
-
"
|
|
278
|
+
"status": "shipped",
|
|
279
|
+
"fulfillment_type": "shipping",
|
|
278
280
|
"tracking": "1Z999AA10123456784",
|
|
279
281
|
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=1Z999AA10123456784",
|
|
280
282
|
"cost": "10.00",
|
|
281
283
|
"display_cost": "$10.00",
|
|
282
|
-
"
|
|
284
|
+
"fulfilled_at": "2025-01-16T14:00:00Z",
|
|
283
285
|
"created_at": "2025-01-15T10:30:00Z",
|
|
284
286
|
"updated_at": "2025-01-16T14:00:00Z",
|
|
285
|
-
"
|
|
286
|
-
"id": "
|
|
287
|
+
"delivery_method": {
|
|
288
|
+
"id": "dm_2wMn7xRt",
|
|
287
289
|
"..."
|
|
288
290
|
},
|
|
289
291
|
"stock_location": {
|
|
290
292
|
"id": "sl_2wMn7xRt",
|
|
291
293
|
"..."
|
|
292
294
|
},
|
|
293
|
-
"
|
|
295
|
+
"delivery_rates": []
|
|
294
296
|
}
|
|
295
297
|
```
|
|
296
298
|
|
|
@@ -448,7 +450,7 @@ Events: `stock_movement.created`, `stock_movement.updated`, `stock_movement.dele
|
|
|
448
450
|
"quantity": -1,
|
|
449
451
|
"action": "sold",
|
|
450
452
|
"originator_type": "Spree::Shipment",
|
|
451
|
-
"originator_id": "
|
|
453
|
+
"originator_id": "ful_9xPq4wMn",
|
|
452
454
|
"stock_item_id": "si_6nRt2xLq",
|
|
453
455
|
"created_at": "2025-01-15T10:30:00Z",
|
|
454
456
|
"updated_at": "2025-01-15T10:30:00Z"
|
|
@@ -70,7 +70,7 @@ Adjustments are created by two sources:
|
|
|
70
70
|
|
|
71
71
|
## Store API
|
|
72
72
|
|
|
73
|
-
Adjustments appear in order responses when you expand line items or
|
|
73
|
+
Adjustments appear in order responses when you expand line items or fulfillments:
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
```typescript SDK
|
|
@@ -93,7 +93,7 @@ order.included_tax_total
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
```bash cURL
|
|
96
|
-
curl 'https://api.mystore.com/api/v3/store/orders/or_abc123?expand=items,
|
|
96
|
+
curl 'https://api.mystore.com/api/v3/store/orders/or_abc123?expand=items,fulfillments' \
|
|
97
97
|
-H 'Authorization: Bearer pk_xxx' \
|
|
98
98
|
-H 'X-Spree-Token: <token>'
|
|
99
99
|
```
|
|
@@ -306,10 +306,10 @@ Spree includes V3 serializers for all core models in [`api/app/serializers/spree
|
|
|
306
306
|
|
|
307
307
|
| Serializer | Model |
|
|
308
308
|
|------------|-------|
|
|
309
|
-
| `OrderSerializer` | Orders with totals,
|
|
309
|
+
| `OrderSerializer` | Orders with totals, statuses, nested line items, fulfillments, payments, addresses |
|
|
310
310
|
| `ProductSerializer` | Products with pricing, stock status, availability |
|
|
311
311
|
| `PaymentSerializer` | Payments with amounts, states, nested payment method and source |
|
|
312
|
-
| `
|
|
312
|
+
| `FulfillmentSerializer` | Fulfillments (shipments) with tracking, nested delivery method and delivery rates |
|
|
313
313
|
| `LineItemSerializer` | Line items with quantity, pricing, nested option values |
|
|
314
314
|
| `VariantSerializer` | Variants with SKU, pricing, nested option values |
|
|
315
315
|
| `PriceSerializer` | Prices with amounts, currency, price list |
|
|
@@ -3,6 +3,8 @@ title: Inventory
|
|
|
3
3
|
description: Stock locations, stock items, stock movements, and inventory tracking
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
import { Since } from '/snippets/since.mdx';
|
|
7
|
+
|
|
6
8
|
## Overview
|
|
7
9
|
|
|
8
10
|
Each [Variant](products.md#variants) has a `StockItem` that tracks its inventory at a specific location. A variant can have multiple stock items if it's available at multiple stock locations.
|
|
@@ -11,6 +13,8 @@ When products are sold or returned, individual `InventoryUnit` records track eac
|
|
|
11
13
|
|
|
12
14
|
Adding new inventory to an out-of-stock product that has backorders will first fill the backorders, then update the available count with the remainder.
|
|
13
15
|
|
|
16
|
+
During checkout, Spree holds stock with time-limited [Stock Reservations](#stock-reservations) to prevent two customers from buying the same last unit simultaneously.
|
|
17
|
+
|
|
14
18
|
### Inventory Model Diagram
|
|
15
19
|
|
|
16
20
|
```mermaid
|
|
@@ -52,14 +56,24 @@ erDiagram
|
|
|
52
56
|
integer shipment_id
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
StockReservation {
|
|
60
|
+
integer quantity
|
|
61
|
+
datetime expires_at
|
|
62
|
+
integer stock_item_id
|
|
63
|
+
integer line_item_id
|
|
64
|
+
integer order_id
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
StockLocation ||--o{ StockItem : "has many"
|
|
56
68
|
StockLocation ||--o{ StockTransfer : "source"
|
|
57
69
|
StockLocation ||--o{ StockTransfer : "destination"
|
|
58
70
|
Variant ||--o{ StockItem : "has many"
|
|
59
71
|
Variant ||--o{ InventoryUnit : "has many"
|
|
60
72
|
StockItem ||--o{ StockMovement : "has many"
|
|
73
|
+
StockItem ||--o{ StockReservation : "has many"
|
|
61
74
|
StockTransfer ||--o{ StockMovement : "has many"
|
|
62
75
|
Order ||--o{ InventoryUnit : "has many"
|
|
76
|
+
Order ||--o{ StockReservation : "has many"
|
|
63
77
|
Shipment ||--o{ InventoryUnit : "has many"
|
|
64
78
|
```
|
|
65
79
|
|
|
@@ -69,6 +83,7 @@ erDiagram
|
|
|
69
83
|
- **Stock Movement** → Records changes to Stock Item quantities (purchases, returns, transfers)
|
|
70
84
|
- **Stock Transfer** → Moves inventory between Stock Locations, creating Stock Movements at source and destination
|
|
71
85
|
- **Inventory Unit** → Represents individual units in [Orders](orders.md) and [Shipments](shipments.md)
|
|
86
|
+
- **Stock Reservation** → Time-limited soft hold on a Stock Item during checkout, scoped to a specific Order and Line Item
|
|
72
87
|
|
|
73
88
|
## Inventory Management
|
|
74
89
|
|
|
@@ -151,6 +166,47 @@ Here's the list of attributes for the Stock Movement model:
|
|
|
151
166
|
|
|
152
167
|
Stock Movements are crucial for maintaining accurate inventory levels and for historical tracking of inventory adjustments.
|
|
153
168
|
|
|
169
|
+
## Stock Reservations
|
|
170
|
+
|
|
171
|
+
Stock Reservations are a time-limited soft hold on stock during checkout. When a customer enters checkout, Spree holds the items in their cart for a limited time so other shoppers see reduced availability immediately. Two customers can no longer both pass the availability check on the same last unit only to have one of them fail at order completion.
|
|
172
|
+
|
|
173
|
+
### What changes for the storefront
|
|
174
|
+
|
|
175
|
+
Availability now subtracts the units other customers are holding in active checkouts. Whenever you read whether a variant is in stock — whether for a product page, cart line, or checkout summary — Spree returns the post-reservation number automatically. There's nothing for the storefront to compute and physical stock counts on each `StockItem` are never modified by reservations; reservations are an independent layer that's consulted at read time and cleaned up by background jobs.
|
|
176
|
+
|
|
177
|
+
### Lifecycle
|
|
178
|
+
|
|
179
|
+
| Trigger | Action |
|
|
180
|
+
|---|---|
|
|
181
|
+
| Customer enters checkout | A reservation is created for each line item with an expiry timestamp |
|
|
182
|
+
| Customer continues mutating the cart while in checkout | The expiry is pushed forward |
|
|
183
|
+
| Customer completes the order | The reservation is released; physical stock is decremented as before |
|
|
184
|
+
| Customer empties or abandons the cart | The reservation is released or expires automatically |
|
|
185
|
+
|
|
186
|
+
Reservations attach to each line item; when a line item or order is removed, the reservation goes with it.
|
|
187
|
+
|
|
188
|
+
### Configuration
|
|
189
|
+
|
|
190
|
+
| Setting | Default | Purpose |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `Spree::Config[:stock_reservations_enabled]` | `true` | Global kill switch. When `false`, reservations are not created and availability ignores them — behavior matches pre-5.5. |
|
|
193
|
+
| `Spree::Config[:default_stock_reservation_ttl_minutes]` | `10` | Fallback hold duration when a Store doesn't override. |
|
|
194
|
+
| `store.preferred_stock_reservation_ttl_minutes` | inherits global | Per-Store override. |
|
|
195
|
+
|
|
196
|
+
TTL is a Store-level setting — it's a checkout-experience policy, not a warehouse property. A multi-location cart never has to merge conflicting TTLs from different warehouses.
|
|
197
|
+
|
|
198
|
+
### Insufficient stock during checkout
|
|
199
|
+
|
|
200
|
+
When a cart change in checkout would push the order beyond available stock, the change is rejected up front. The customer sees a validation error immediately, instead of progressing through payment only to fail at the final submit.
|
|
201
|
+
|
|
202
|
+
### Background expiry
|
|
203
|
+
|
|
204
|
+
Abandoned checkouts leave behind expired reservation rows. Spree provides a job to clean them up but does **not** auto-schedule it — your application's job runner needs to invoke it periodically (every minute is typical). See the [5.4 to 5.5 upgrade guide](../upgrades/5.4-to-5.5.md#schedule-the-stock-reservations-expiry-job) for sidekiq-cron, solid_queue, and good_job snippets.
|
|
205
|
+
|
|
206
|
+
### Backorderable items
|
|
207
|
+
|
|
208
|
+
If a stock item is marked backorderable, it represents unlimited supply, so reservations are skipped entirely for that item. Availability is unaffected.
|
|
209
|
+
|
|
154
210
|
## Inventory Units
|
|
155
211
|
|
|
156
212
|
As we mentioned above, back-ordered, sold, or shipped products are stored as individual `InventoryUnit` objects so they can have relevant information attached to them.
|
|
@@ -67,13 +67,13 @@ The API returns these key fields on every order:
|
|
|
67
67
|
| `currency` | Order currency (e.g., `USD`) |
|
|
68
68
|
| `item_count` | Total number of items |
|
|
69
69
|
| `item_total` / `display_item_total` | Sum of line item prices |
|
|
70
|
-
| `
|
|
70
|
+
| `delivery_total` / `display_delivery_total` | Delivery cost |
|
|
71
71
|
| `tax_total` / `display_tax_total` | Total tax |
|
|
72
72
|
| `promo_total` / `display_promo_total` | Total discount from promotions |
|
|
73
|
-
| `adjustment_total` / `display_adjustment_total` | Sum of all adjustments (tax +
|
|
73
|
+
| `adjustment_total` / `display_adjustment_total` | Sum of all adjustments (tax + delivery + promos) |
|
|
74
74
|
| `total` / `display_total` | Final order total |
|
|
75
75
|
| `payment_state` | Payment status (`balance_due`, `paid`, `credit_owed`, `failed`, `void`) |
|
|
76
|
-
| `
|
|
76
|
+
| `fulfillment_status` | Fulfillment status (`pending`, `ready`, `partial`, `shipped`, `backorder`) |
|
|
77
77
|
| `completed_at` | Timestamp when the order was placed |
|
|
78
78
|
|
|
79
79
|
The `display_*` fields return formatted strings with currency symbols (e.g., `"$15.99"`).
|
|
@@ -183,10 +183,11 @@ await client.carts.update(cartId, {
|
|
|
183
183
|
},
|
|
184
184
|
})
|
|
185
185
|
|
|
186
|
-
// Get
|
|
187
|
-
|
|
188
|
-
await client.carts.
|
|
189
|
-
|
|
186
|
+
// Get fulfillments and select a delivery rate
|
|
187
|
+
// (the Store API/SDK exposes shipments as `fulfillments`)
|
|
188
|
+
const cart = await client.carts.get(cartId, { expand: ['fulfillments'] })
|
|
189
|
+
await client.carts.fulfillments.update(cartId, cart.fulfillments[0].id, {
|
|
190
|
+
selected_delivery_rate_id: 'rate_xxx',
|
|
190
191
|
})
|
|
191
192
|
|
|
192
193
|
// Create a payment session (e.g., Stripe)
|
|
@@ -218,12 +219,12 @@ curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx' \
|
|
|
218
219
|
}
|
|
219
220
|
}'
|
|
220
221
|
|
|
221
|
-
# Select a
|
|
222
|
-
curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/
|
|
222
|
+
# Select a delivery rate
|
|
223
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/ful_xxx' \
|
|
223
224
|
-H 'Authorization: Bearer pk_xxx' \
|
|
224
225
|
-H 'X-Spree-Token: abc123' \
|
|
225
226
|
-H 'Content-Type: application/json' \
|
|
226
|
-
-d '{ "
|
|
227
|
+
-d '{ "selected_delivery_rate_id": "rate_xxx" }'
|
|
227
228
|
|
|
228
229
|
# Complete the order
|
|
229
230
|
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/complete' \
|
|
@@ -271,7 +272,7 @@ const { data: orders } = await client.customer.orders.list()
|
|
|
271
272
|
|
|
272
273
|
// Get a specific order with details
|
|
273
274
|
const order = await client.orders.get('or_xxx', {
|
|
274
|
-
expand: ['items', '
|
|
275
|
+
expand: ['items', 'fulfillments', 'payments'],
|
|
275
276
|
})
|
|
276
277
|
```
|
|
277
278
|
|
|
@@ -281,7 +282,7 @@ curl 'https://api.mystore.com/api/v3/store/customer/orders' \
|
|
|
281
282
|
-H 'Authorization: Bearer <jwt_token>'
|
|
282
283
|
|
|
283
284
|
# Get a specific order
|
|
284
|
-
curl 'https://api.mystore.com/api/v3/store/orders/or_xxx?expand=items,
|
|
285
|
+
curl 'https://api.mystore.com/api/v3/store/orders/or_xxx?expand=items,fulfillments,payments' \
|
|
285
286
|
-H 'Authorization: Bearer <jwt_token>'
|
|
286
287
|
```
|
|
287
288
|
|
|
@@ -306,14 +307,16 @@ When a variant is added to an order, the price is locked on the line item. If th
|
|
|
306
307
|
| `failed` | Most recent payment attempt failed |
|
|
307
308
|
| `void` | Order was canceled and payments voided |
|
|
308
309
|
|
|
309
|
-
##
|
|
310
|
+
## Fulfillment Statuses
|
|
310
311
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
|
314
|
-
|
|
315
|
-
| `
|
|
316
|
-
| `
|
|
312
|
+
The order's `fulfillment_status` field summarizes the state of all fulfillments (the Store API/SDK exposes shipments as `fulfillments`).
|
|
313
|
+
|
|
314
|
+
| Status | Description |
|
|
315
|
+
|--------|-------------|
|
|
316
|
+
| `pending` | All fulfillments are pending |
|
|
317
|
+
| `ready` | All fulfillments are ready to ship |
|
|
318
|
+
| `partial` | At least one fulfillment is shipped, others are not |
|
|
319
|
+
| `shipped` | All fulfillments have been shipped |
|
|
317
320
|
| `backorder` | Some inventory is on backorder |
|
|
318
321
|
|
|
319
322
|
For more details, see [Shipments](shipments.md) and [Payments](payments.md).
|
|
@@ -237,7 +237,7 @@ After the customer completes payment, the frontend calls the Complete Payment Se
|
|
|
237
237
|
|
|
238
238
|
**Step 4: Complete Order**
|
|
239
239
|
|
|
240
|
-
The frontend calls `POST /carts/:id/complete` to finalize the order. Spree validates the order is ready (addresses,
|
|
240
|
+
The frontend calls `POST /carts/:id/complete` to finalize the order. Spree validates the order is ready (addresses, fulfillments, payment), advances through any remaining checkout states, and marks the order as complete.
|
|
241
241
|
|
|
242
242
|
This separation ensures the same flow works for all payment types — inline cards, offsite redirects, and wallet payments.
|
|
243
243
|
|
|
@@ -49,15 +49,19 @@ erDiagram
|
|
|
49
49
|
|
|
50
50
|
## Shipment Attributes
|
|
51
51
|
|
|
52
|
+
The Store API/SDK exposes shipments as `fulfillments` with these attributes:
|
|
53
|
+
|
|
52
54
|
| Attribute | Description | Example |
|
|
53
55
|
|-----------|-------------|---------|
|
|
54
|
-
| `number` | Unique
|
|
56
|
+
| `number` | Unique fulfillment identifier | `H12345678901` |
|
|
55
57
|
| `tracking` | Carrier tracking number | `1Z999AA10123456784` |
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
58
|
+
| `status` | Current fulfillment status | `shipped` |
|
|
59
|
+
| `fulfillment_type` | `shipping` or `digital` | `shipping` |
|
|
60
|
+
| `cost` | Delivery cost | `9.99` |
|
|
61
|
+
| `fulfilled_at` | When the fulfillment was shipped | `2025-07-21T14:36:00Z` |
|
|
59
62
|
| `stock_location` | Where items ship from | `Warehouse NYC` |
|
|
60
|
-
| `
|
|
63
|
+
| `delivery_method` | The selected delivery method | `{ id: "dm_xxx", name: "UPS Ground" }` |
|
|
64
|
+
| `delivery_rates` | Available rates for the customer to pick from | `[{ id: "rate_xxx", cost: "9.99", selected: true, ... }]` |
|
|
61
65
|
|
|
62
66
|
## Shipment States
|
|
63
67
|
|
|
@@ -84,35 +88,36 @@ During checkout, after the customer provides a shipping address, Spree calculate
|
|
|
84
88
|
|
|
85
89
|
|
|
86
90
|
```typescript SDK
|
|
87
|
-
// Get
|
|
91
|
+
// Get fulfillments with available delivery rates
|
|
92
|
+
// (the Store API/SDK exposes shipments as `fulfillments`)
|
|
88
93
|
const order = await client.orders.get(orderId, {
|
|
89
|
-
expand: ['
|
|
94
|
+
expand: ['fulfillments'],
|
|
90
95
|
})
|
|
91
96
|
|
|
92
|
-
// Each
|
|
93
|
-
order.
|
|
94
|
-
console.log(
|
|
95
|
-
console.log(
|
|
97
|
+
// Each fulfillment has available delivery rates
|
|
98
|
+
order.fulfillments?.forEach(fulfillment => {
|
|
99
|
+
console.log(fulfillment.number) // "H12345678901"
|
|
100
|
+
console.log(fulfillment.delivery_rates) // [{ id: "rate_xxx", name: "UPS Ground", cost: "9.99", selected: true }, ...]
|
|
96
101
|
})
|
|
97
102
|
|
|
98
|
-
// Select a
|
|
99
|
-
await client.carts.
|
|
100
|
-
|
|
103
|
+
// Select a delivery rate
|
|
104
|
+
await client.carts.fulfillments.update(cartId, fulfillment.id, {
|
|
105
|
+
selected_delivery_rate_id: 'rate_xxx',
|
|
101
106
|
})
|
|
102
107
|
```
|
|
103
108
|
|
|
104
109
|
```bash cURL
|
|
105
|
-
# Get
|
|
106
|
-
curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=
|
|
110
|
+
# Get fulfillments
|
|
111
|
+
curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=fulfillments' \
|
|
107
112
|
-H 'Authorization: Bearer pk_xxx' \
|
|
108
113
|
-H 'X-Spree-Token: abc123'
|
|
109
114
|
|
|
110
|
-
# Select a
|
|
111
|
-
curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/
|
|
115
|
+
# Select a delivery rate
|
|
116
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/fulfillments/ful_xxx' \
|
|
112
117
|
-H 'Authorization: Bearer pk_xxx' \
|
|
113
118
|
-H 'X-Spree-Token: abc123' \
|
|
114
119
|
-H 'Content-Type: application/json' \
|
|
115
|
-
-d '{ "
|
|
120
|
+
-d '{ "selected_delivery_rate_id": "rate_xxx" }'
|
|
116
121
|
```
|
|
117
122
|
|
|
118
123
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Upgrading to Spree 5.5
|
|
3
|
+
description: This guide covers upgrading a Spree 5.4 application to Spree 5.5.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **INFO:** Before proceeding to upgrade, please ensure you're at [Spree 5.4](5.3-to-5.4.md).
|
|
7
|
+
|
|
8
|
+
## Upgrade steps
|
|
9
|
+
|
|
10
|
+
### Update gems
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bundle update
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Fetch and run missing migrations
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bin/rake spree:install:migrations && bin/rails db:migrate
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Schedule the Stock Reservations expiry job
|
|
23
|
+
|
|
24
|
+
Spree 5.5 introduces time-limited stock reservations during checkout to prevent two customers from buying the same last unit at the same time. Abandoned checkouts leave behind expired reservation rows, and Spree does **not** auto-schedule the cleanup — your application's job runner must run `Spree::StockReservations::ExpireJob` periodically (every minute is the recommended cadence).
|
|
25
|
+
|
|
26
|
+
If you skip this step, expired reservations accumulate in the table indefinitely. The Quantifier still ignores them at availability-check time (so customers see correct stock), but the table grows unbounded.
|
|
27
|
+
|
|
28
|
+
#### sidekiq-cron
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
# config/sidekiq_cron.yml
|
|
32
|
+
expire_stock_reservations:
|
|
33
|
+
cron: "* * * * *"
|
|
34
|
+
class: "Spree::StockReservations::ExpireJob"
|
|
35
|
+
queue: default
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### solid_queue
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
# config/recurring.yml
|
|
42
|
+
expire_stock_reservations:
|
|
43
|
+
schedule: every minute
|
|
44
|
+
class: Spree::StockReservations::ExpireJob
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### good_job
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# config/initializers/good_job.rb
|
|
51
|
+
Rails.application.configure do
|
|
52
|
+
config.good_job.cron = {
|
|
53
|
+
expire_stock_reservations: {
|
|
54
|
+
cron: '* * * * *',
|
|
55
|
+
class: 'Spree::StockReservations::ExpireJob'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### (Optional) Tune the reservation TTL
|
|
62
|
+
|
|
63
|
+
The default reservation TTL is **10 minutes**. To override globally:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
# config/initializers/spree.rb
|
|
67
|
+
Spree::Config[:default_stock_reservation_ttl_minutes] = 15
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To override per Store, set the preference on the Store record:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
store.update!(preferred_stock_reservation_ttl_minutes: 20)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The per-Store value, when set, takes precedence over the global default.
|
|
77
|
+
|
|
78
|
+
### (Optional) Disable Stock Reservations
|
|
79
|
+
|
|
80
|
+
Stock reservations are enabled by default. To opt out and revert to pre-5.5 behavior (no holds during checkout, Quantifier returns raw `count_on_hand`):
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# config/initializers/spree.rb
|
|
84
|
+
Spree::Config[:stock_reservations_enabled] = false
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The Quantifier short-circuits before any reservation query when this is `false`, so there's no runtime cost and no table growth.
|
|
88
|
+
|
|
89
|
+
## Behavior changes worth knowing
|
|
90
|
+
|
|
91
|
+
### Cart changes during checkout can now fail with insufficient stock
|
|
92
|
+
|
|
93
|
+
When a customer is in checkout and tries to add an item, increase a quantity, or remove a line item, Spree now re-checks whether the cart still fits in available stock (subtracting what other customers are holding in their own active checkouts). If it doesn't, the change is rejected up front instead of silently completing and failing later at order submission.
|
|
94
|
+
|
|
95
|
+
Storefronts and custom integrations that act on the cart should expect this new failure path and surface the error to the customer.
|
|
96
|
+
|
|
97
|
+
### Storefront availability drops faster under contention
|
|
98
|
+
|
|
99
|
+
Other customers now see availability reduced by all active reservations, not just by completed orders. This is the intended fix to overselling — but if you have a real-time inventory dashboard that reads `count_on_hand` directly (rather than going through Spree's availability checks), you'll want to expose a "Reserved" axis to merchants so they can see in-checkout demand.
|