@spree/docs 0.1.8 → 0.1.9

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.
@@ -80,9 +80,14 @@ You can filter by associated model attributes using underscore notation:
80
80
 
81
81
 
82
82
  ```typescript SDK
83
- // Products in a specific category
83
+ // Products in a specific category (includes descendants)
84
84
  const products = await client.products.list({
85
- categories_id_eq: 'ctg_abc123',
85
+ in_category: 'ctg_abc123',
86
+ })
87
+
88
+ // Multiple categories (OR logic, checkbox filters)
89
+ const products = await client.products.list({
90
+ in_categories: ['ctg_abc123', 'ctg_def456'],
86
91
  })
87
92
 
88
93
  // Categories at a specific depth
@@ -92,10 +97,10 @@ const categories = await client.categories.list({
92
97
  ```
93
98
 
94
99
  ```bash cURL
95
- # Products in a specific category
100
+ # Products in a specific category (includes descendants)
96
101
  curl -G 'http://localhost:3000/api/v3/store/products' \
97
102
  -H 'X-Spree-Api-Key: spree_pk_xxx' \
98
- --data-urlencode 'q[categories_id_eq]=ctg_abc123'
103
+ --data-urlencode 'q[in_category]=ctg_abc123'
99
104
 
100
105
  # Top-level categories only
101
106
  curl -G 'http://localhost:3000/api/v3/store/categories' \
@@ -7943,12 +7943,20 @@ paths:
7943
7943
  description: Filter by name containing string
7944
7944
  schema:
7945
7945
  type: string
7946
- - name: q[categories_id_eq]
7946
+ - name: q[in_category]
7947
7947
  in: query
7948
7948
  required: false
7949
- description: Filter by category ID
7949
+ description: Filter by category prefixed ID (includes descendants)
7950
7950
  schema:
7951
7951
  type: string
7952
+ - name: q[in_categories][]
7953
+ in: query
7954
+ required: false
7955
+ description: Filter by multiple category prefixed IDs (OR logic, includes descendants)
7956
+ schema:
7957
+ type: array
7958
+ items:
7959
+ type: string
7952
7960
  - name: q[price_gte]
7953
7961
  in: query
7954
7962
  required: false
@@ -73,9 +73,14 @@ const { data: products } = await client.categories.products.list('clothing/shirt
73
73
  limit: 12,
74
74
  })
75
75
 
76
- // Or filter by category ID
76
+ // Or filter by category ID (includes descendants)
77
77
  const { data: products } = await client.products.list({
78
- categories_id_eq: 'ctg_xxx',
78
+ in_category: 'ctg_xxx',
79
+ })
80
+
81
+ // Multiple categories (OR logic, checkbox filters)
82
+ const { data: products } = await client.products.list({
83
+ in_categories: ['ctg_shirts', 'ctg_pants'],
79
84
  })
80
85
  ```
81
86
 
@@ -84,8 +89,12 @@ const { data: products } = await client.products.list({
84
89
  curl 'https://api.mystore.com/api/v3/store/categories/clothing/shirts/products?limit=12' \
85
90
  -H 'Authorization: Bearer spree_pk_xxx'
86
91
 
87
- # Filter by category ID
88
- curl 'https://api.mystore.com/api/v3/store/products?q[categories_id_eq]=ctg_xxx' \
92
+ # Filter by category ID (includes descendants)
93
+ curl 'https://api.mystore.com/api/v3/store/products?q[in_category]=ctg_xxx' \
94
+ -H 'Authorization: Bearer spree_pk_xxx'
95
+
96
+ # Multiple categories (OR logic)
97
+ curl 'https://api.mystore.com/api/v3/store/products?q[in_categories][]=ctg_shirts&q[in_categories][]=ctg_pants' \
89
98
  -H 'Authorization: Bearer spree_pk_xxx'
90
99
  ```
91
100
 
@@ -163,22 +163,56 @@ Each webhook request includes these headers:
163
163
 
164
164
  ### Verifying Webhook Signatures
165
165
 
166
- To ensure webhooks are genuinely from your Spree store, verify the signature:
166
+ To ensure webhooks are genuinely from your Spree store, verify the signature.
167
+
168
+ #### Next.js (recommended)
169
+
170
+ Use `@spree/next/webhooks` for a ready-made Route Handler:
171
+
172
+ ```typescript
173
+ // src/app/api/webhooks/spree/route.ts
174
+ import { createWebhookHandler } from '@spree/next/webhooks'
175
+
176
+ export const POST = createWebhookHandler({
177
+ secret: process.env.SPREE_WEBHOOK_SECRET!,
178
+ handlers: {
179
+ 'order.completed': async (event) => {
180
+ // event.data is the order payload (same shape as Store API)
181
+ await sendOrderConfirmationEmail(event.data)
182
+ },
183
+ 'order.canceled': async (event) => {
184
+ await sendCancellationEmail(event.data)
185
+ },
186
+ },
187
+ })
188
+ ```
167
189
 
168
- ```ruby
169
- # In your webhook receiver
170
- def verify_webhook(request)
171
- payload = request.body.read
172
- signature = request.headers['X-Spree-Webhook-Signature']
173
- secret_key = ENV['SPREE_WEBHOOK_SECRET'] # Your endpoint's secret_key
190
+ #### Any JavaScript/TypeScript framework
174
191
 
175
- expected = OpenSSL::HMAC.hexdigest('SHA256', secret_key, payload)
192
+ Use `@spree/sdk/webhooks` for framework-agnostic verification:
176
193
 
177
- ActiveSupport::SecurityUtils.secure_compare(signature, expected)
178
- end
194
+ ```typescript
195
+ import { verifyWebhookSignature } from '@spree/sdk/webhooks'
196
+ import type { WebhookEvent } from '@spree/sdk/webhooks'
197
+ import type { Order } from '@spree/sdk'
198
+
199
+ // Hono, Cloudflare Workers, or any Web Fetch API-based framework
200
+ app.post('/webhooks/spree', async (req, res) => {
201
+ const body = await req.text()
202
+ const signature = req.headers['x-spree-webhook-signature']
203
+ const timestamp = req.headers['x-spree-webhook-timestamp']
204
+
205
+ if (!verifyWebhookSignature(body, signature, timestamp, process.env.SPREE_WEBHOOK_SECRET!)) {
206
+ return res.status(401).json({ error: 'Invalid signature' })
207
+ }
208
+
209
+ const event: WebhookEvent<Order> = JSON.parse(body)
210
+ // handle event...
211
+ res.json({ received: true })
212
+ })
179
213
  ```
180
214
 
181
- Example in a Rails controller:
215
+ #### Ruby
182
216
 
183
217
  ```ruby
184
218
  class WebhooksController < ApplicationController
@@ -209,7 +243,8 @@ class WebhooksController < ApplicationController
209
243
  request.body.rewind
210
244
 
211
245
  signature = request.headers['X-Spree-Webhook-Signature']
212
- expected = OpenSSL::HMAC.hexdigest('SHA256', ENV['SPREE_WEBHOOK_SECRET'], payload)
246
+ timestamp = request.headers['X-Spree-Webhook-Timestamp']
247
+ expected = OpenSSL::HMAC.hexdigest('SHA256', ENV['SPREE_WEBHOOK_SECRET'], "#{timestamp}.#{payload}")
213
248
 
214
249
  ActiveSupport::SecurityUtils.secure_compare(signature.to_s, expected)
215
250
  end
@@ -20,6 +20,9 @@ Set environment variables and the client initializes automatically:
20
20
  ```env
21
21
  SPREE_API_URL=https://api.mystore.com
22
22
  SPREE_PUBLISHABLE_KEY=spree_pk_xxx
23
+
24
+ # Required for webhook-based emails (see Webhooks section below)
25
+ SPREE_WEBHOOK_SECRET=your_webhook_endpoint_secret_key
23
26
  ```
24
27
 
25
28
  ### Explicit initialization
@@ -284,6 +287,48 @@ const products = await listProducts({ limit: 10 }, { locale: 'fr', country: 'FR'
284
287
  const category = await getCategory('clothing/shirts', {}, { locale: 'de', country: 'DE' });
285
288
  ```
286
289
 
290
+ ## Webhooks
291
+
292
+ Handle Spree webhook events in your Next.js app with `@spree/next/webhooks`:
293
+
294
+ ```typescript
295
+ // src/app/api/webhooks/spree/route.ts
296
+ import { createWebhookHandler } from '@spree/next/webhooks'
297
+
298
+ export const POST = createWebhookHandler({
299
+ secret: process.env.SPREE_WEBHOOK_SECRET!,
300
+ handlers: {
301
+ 'order.completed': async (event) => {
302
+ await sendOrderConfirmationEmail(event.data)
303
+ },
304
+ 'order.canceled': async (event) => {
305
+ await sendCancellationEmail(event.data)
306
+ },
307
+ 'order.shipped': async (event) => {
308
+ await sendShippingNotification(event.data)
309
+ },
310
+ 'customer.password_reset_requested': async (event) => {
311
+ await sendPasswordResetEmail(event.data)
312
+ },
313
+ },
314
+ })
315
+ ```
316
+
317
+ The handler verifies HMAC-SHA256 signatures, rejects replayed requests, routes events to your handlers, and returns 200 immediately (handlers run async).
318
+
319
+ ### Local development
320
+
321
+ Webhooks require a publicly accessible URL. Use [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/) for local development:
322
+
323
+ ```bash
324
+ brew install cloudflared
325
+ cloudflared tunnel --url http://localhost:3001
326
+ ```
327
+
328
+ Then create a webhook endpoint in **Spree Admin → Settings → Developers → Webhooks** pointing to your tunnel URL + `/api/webhooks/spree`.
329
+
330
+ For more details, see [Webhooks](../../core-concepts/webhooks.md).
331
+
287
332
  ## TypeScript
288
333
 
289
334
  All types are re-exported from `@spree/sdk`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",