@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.
@@ -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`, `payments`, `bill_address`, `ship_address`, `payment_methods`, and `promotions`.
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
- "shipment_state": "shipped",
52
+ "fulfillment_status": "shipped",
53
53
  "payment_state": "paid",
54
54
  "item_total": "89.99",
55
55
  "display_item_total": "$89.99",
56
- "ship_total": "10.00",
57
- "display_ship_total": "$10.00",
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
- "shipments": [
93
+ "fulfillments": [
94
94
  {
95
- "id": "shp_9xPq4wMn",
95
+ "id": "ful_9xPq4wMn",
96
96
  "number": "H123456789",
97
- "state": "shipped",
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
- "shipped_at": "2025-01-16T14:00:00Z",
103
- "shipping_method": { "id": "sm_2wMn7xRt", "..." },
103
+ "fulfilled_at": "2025-01-16T14:00:00Z",
104
+ "delivery_method": { "id": "dm_2wMn7xRt", "..." },
104
105
  "stock_location": { "id": "sl_2wMn7xRt", "..." },
105
- "shipping_rates": [],
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
- Shipment payloads include nested `shipping_method`, `stock_location`, and `shipping_rates`.
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": "shp_9xPq4wMn",
276
+ "id": "ful_9xPq4wMn",
276
277
  "number": "H123456789",
277
- "state": "shipped",
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
- "shipped_at": "2025-01-16T14:00:00Z",
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
- "shipping_method": {
286
- "id": "sm_2wMn7xRt",
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
- "shipping_rates": []
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": "shp_9xPq4wMn",
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 shipments:
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,shipments' \
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, states, nested line items, shipments, payments, addresses |
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
- | `ShipmentSerializer` | Shipments with tracking, nested shipping method and rates |
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
- | `ship_total` / `display_ship_total` | Shipping cost |
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 + shipping + promos) |
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
- | `shipment_state` | Shipment status (`pending`, `ready`, `partial`, `shipped`, `backorder`) |
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 shipments and select a shipping rate
187
- const { data: shipments } = await client.carts.shipments.list(cartId)
188
- await client.carts.shipments.update(cartId, shipments[0].id, {
189
- selected_shipping_rate_id: 'sr_xxx',
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 shipping rate
222
- curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/shipments/shp_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 '{ "selected_shipping_rate_id": "sr_xxx" }'
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', 'shipments', 'payments'],
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,shipments,payments' \
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
- ## Shipment States
310
+ ## Fulfillment Statuses
310
311
 
311
- | State | Description |
312
- |-------|-------------|
313
- | `pending` | All shipments are pending |
314
- | `ready` | All shipments are ready to ship |
315
- | `partial` | At least one shipment is shipped, others are not |
316
- | `shipped` | All shipments have been shipped |
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, shipments, payment), advances through any remaining checkout states, and marks the order as complete.
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 shipment identifier | `H12345678901` |
56
+ | `number` | Unique fulfillment identifier | `H12345678901` |
55
57
  | `tracking` | Carrier tracking number | `1Z999AA10123456784` |
56
- | `state` | Current shipment state | `shipped` |
57
- | `cost` | Shipping cost | `9.99` |
58
- | `shipped_at` | When the shipment was shipped | `2025-07-21T14:36:00Z` |
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
- | `selected_shipping_rate` | The rate chosen by the customer | `{ cost: 9.99, name: "UPS Ground" }` |
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 shipments with available shipping rates
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: ['shipments'],
94
+ expand: ['fulfillments'],
90
95
  })
91
96
 
92
- // Each shipment has available shipping rates
93
- order.shipments?.forEach(shipment => {
94
- console.log(shipment.number) // "H12345678901"
95
- console.log(shipment.shipping_rates) // [{ id: "sr_xxx", name: "UPS Ground", cost: "9.99", selected: true }, ...]
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 shipping rate
99
- await client.carts.shipments.update(cartId, shipment.id, {
100
- selected_shipping_rate_id: 'sr_xxx',
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 shipments
106
- curl 'https://api.mystore.com/api/v3/store/carts/cart_xxx?expand=shipments' \
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 shipping rate
111
- curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/shipments/shp_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 '{ "selected_shipping_rate_id": "sr_xxx" }'
120
+ -d '{ "selected_delivery_rate_id": "rate_xxx" }'
116
121
  ```
117
122
 
118
123
 
@@ -132,7 +132,7 @@ Each webhook delivery sends a JSON payload with the following structure. The `da
132
132
  "item_count": 3,
133
133
  "currency": "USD",
134
134
  "items": [ ... ],
135
- "shipments": [ ... ],
135
+ "fulfillments": [ ... ],
136
136
  "payments": [ ... ]
137
137
  },
138
138
  "metadata": {
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",