@spree/docs 0.1.7 → 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.
- package/dist/api-reference/store-api/querying.md +9 -4
- package/dist/api-reference/store.yaml +2492 -1578
- package/dist/developer/core-concepts/orders.md +8 -8
- package/dist/developer/core-concepts/promotions.md +4 -4
- package/dist/developer/core-concepts/search-filtering.md +13 -4
- package/dist/developer/core-concepts/store-credits-gift-cards.md +9 -3
- package/dist/developer/core-concepts/webhooks.md +47 -12
- package/dist/developer/sdk/quickstart.md +3 -1
- package/dist/developer/sdk/store/cart-checkout.md +15 -5
- package/dist/developer/storefront/nextjs/spree-next-package.md +53 -6
- package/package.json +1 -1
|
@@ -238,23 +238,23 @@ Apply or remove promotional coupon codes during checkout:
|
|
|
238
238
|
|
|
239
239
|
|
|
240
240
|
```typescript SDK
|
|
241
|
-
// Apply a
|
|
242
|
-
await client.carts.
|
|
241
|
+
// Apply a discount code
|
|
242
|
+
await client.carts.discountCodes.apply(cartId, 'SAVE20')
|
|
243
243
|
|
|
244
|
-
// Remove a
|
|
245
|
-
await client.carts.
|
|
244
|
+
// Remove a discount code
|
|
245
|
+
await client.carts.discountCodes.remove(cartId, 'SAVE20')
|
|
246
246
|
```
|
|
247
247
|
|
|
248
248
|
```bash cURL
|
|
249
|
-
# Apply a
|
|
250
|
-
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/
|
|
249
|
+
# Apply a discount code
|
|
250
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/discount_codes' \
|
|
251
251
|
-H 'Authorization: Bearer spree_pk_xxx' \
|
|
252
252
|
-H 'X-Spree-Token: abc123' \
|
|
253
253
|
-H 'Content-Type: application/json' \
|
|
254
254
|
-d '{ "code": "SAVE20" }'
|
|
255
255
|
|
|
256
|
-
# Remove a
|
|
257
|
-
curl -X DELETE 'https://api.mystore.com/api/v3/store/carts/cart_xxx/
|
|
256
|
+
# Remove a discount code
|
|
257
|
+
curl -X DELETE 'https://api.mystore.com/api/v3/store/carts/cart_xxx/discount_codes/SAVE20' \
|
|
258
258
|
-H 'Authorization: Bearer spree_pk_xxx' \
|
|
259
259
|
-H 'X-Spree-Token: abc123'
|
|
260
260
|
```
|
|
@@ -278,13 +278,13 @@ Customers apply coupon codes during checkout via the Store API:
|
|
|
278
278
|
|
|
279
279
|
|
|
280
280
|
```typescript SDK
|
|
281
|
-
// Apply a
|
|
282
|
-
const cart = await client.carts.
|
|
281
|
+
// Apply a discount code to the cart
|
|
282
|
+
const cart = await client.carts.discountCodes.apply('cart_abc123', 'SUMMER20', {
|
|
283
283
|
spreeToken: '<token>',
|
|
284
284
|
})
|
|
285
285
|
|
|
286
|
-
// Remove a
|
|
287
|
-
await client.carts.
|
|
286
|
+
// Remove a discount code from the cart
|
|
287
|
+
await client.carts.discountCodes.remove('cart_abc123', 'SUMMER20', {
|
|
288
288
|
spreeToken: '<token>',
|
|
289
289
|
})
|
|
290
290
|
```
|
|
@@ -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
|
-
|
|
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[
|
|
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
|
|
|
@@ -248,15 +248,21 @@ Gift cards are applied using the same coupon codes endpoint as promotion codes.
|
|
|
248
248
|
|
|
249
249
|
```typescript SDK
|
|
250
250
|
// Apply gift card code to cart (works for guests and registered customers)
|
|
251
|
-
|
|
251
|
+
// Gift cards use a dedicated endpoint — they reduce amount_due, not total
|
|
252
|
+
const cart = await client.carts.giftCards.apply('cart_abc123', 'abc1234def', {
|
|
253
|
+
spreeToken: '<token>',
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// Remove gift card (ID from cart.gift_card.id)
|
|
257
|
+
await client.carts.giftCards.remove('cart_abc123', cart.gift_card.id, {
|
|
252
258
|
spreeToken: '<token>',
|
|
253
259
|
})
|
|
254
260
|
```
|
|
255
261
|
|
|
256
262
|
```bash cURL
|
|
257
263
|
# Apply gift card to cart (works for guests and registered customers)
|
|
258
|
-
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_abc123/
|
|
259
|
-
-H '
|
|
264
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_abc123/gift_cards' \
|
|
265
|
+
-H 'X-Spree-Api-Key: pk_xxx' \
|
|
260
266
|
-H 'X-Spree-Token: <token>' \
|
|
261
267
|
-H 'Content-Type: application/json' \
|
|
262
268
|
-d '{ "code": "abc1234def" }'
|
|
@@ -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
|
-
|
|
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
|
-
|
|
192
|
+
Use `@spree/sdk/webhooks` for framework-agnostic verification:
|
|
176
193
|
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -61,13 +61,15 @@ The SDK uses a resource builder pattern for nested resources:
|
|
|
61
61
|
| `carts` | `paymentMethods` | `list` |
|
|
62
62
|
| `carts` | `paymentSessions` | `create`, `get`, `update`, `complete` |
|
|
63
63
|
| `carts` | `fulfillments` | `list`, `update` |
|
|
64
|
-
| `carts` | `
|
|
64
|
+
| `carts` | `discountCodes` | `apply`, `remove` |
|
|
65
|
+
| `carts` | `giftCards` | `apply`, `remove` |
|
|
65
66
|
| `carts` | `storeCredits` | `apply`, `remove` |
|
|
66
67
|
| `customer` | `addresses` | `list`, `get`, `create`, `update`, `delete`, `markAsDefault` |
|
|
67
68
|
| `customer` | `creditCards` | `list`, `get`, `delete` |
|
|
68
69
|
| `customer` | `giftCards` | `list`, `get` |
|
|
69
70
|
| `customer` | `orders` | `list` |
|
|
70
71
|
| `customer` | `paymentSetupSessions` | `create`, `get`, `complete` |
|
|
72
|
+
| `policies` | — | `list`, `get` |
|
|
71
73
|
| `categories` | `products` | `list` |
|
|
72
74
|
| `wishlists` | `items` | `create`, `update`, `delete` |
|
|
73
75
|
|
|
@@ -55,16 +55,26 @@ await client.carts.items.update(cartId, lineItemId, {
|
|
|
55
55
|
await client.carts.items.delete(cartId, lineItemId, options);
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
###
|
|
58
|
+
### Discount Codes
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
61
|
const options = { spreeToken: cart.token };
|
|
62
62
|
|
|
63
|
-
// Apply a
|
|
64
|
-
await client.carts.
|
|
63
|
+
// Apply a discount code
|
|
64
|
+
await client.carts.discountCodes.apply(cartId, 'SAVE20', options);
|
|
65
65
|
|
|
66
|
-
// Remove a
|
|
67
|
-
await client.carts.
|
|
66
|
+
// Remove a discount code
|
|
67
|
+
await client.carts.discountCodes.remove(cartId, 'SAVE20', options);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Gift Cards
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Apply a gift card (reduces amount_due, not total)
|
|
74
|
+
const cart = await client.carts.giftCards.apply(cartId, 'GC-ABCD-1234', options);
|
|
75
|
+
|
|
76
|
+
// Remove a gift card (ID from cart.gift_card.id)
|
|
77
|
+
await client.carts.giftCards.remove(cartId, 'gc_abc123', options);
|
|
68
78
|
```
|
|
69
79
|
|
|
70
80
|
## Checkout
|
|
@@ -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
|
|
@@ -120,19 +123,21 @@ Checkout functions use the implicit cart resolved from cookies. No `orderId` is
|
|
|
120
123
|
import {
|
|
121
124
|
getCheckout,
|
|
122
125
|
updateCheckout,
|
|
123
|
-
getFulfillments,
|
|
124
126
|
selectDeliveryRate,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
applyDiscountCode,
|
|
128
|
+
removeDiscountCode,
|
|
129
|
+
applyGiftCard,
|
|
130
|
+
removeGiftCard,
|
|
127
131
|
complete,
|
|
128
132
|
} from '@spree/next';
|
|
129
133
|
|
|
130
134
|
const checkout = await getCheckout();
|
|
131
135
|
await updateCheckout({ shipping_address: { ... }, billing_address: { ... } });
|
|
132
|
-
const fulfillments = await getFulfillments();
|
|
133
136
|
await selectDeliveryRate(fulfillmentId, deliveryRateId);
|
|
134
|
-
await
|
|
135
|
-
await
|
|
137
|
+
await applyDiscountCode('SAVE20');
|
|
138
|
+
await removeDiscountCode('SAVE20');
|
|
139
|
+
await applyGiftCard('GC-ABCD-1234');
|
|
140
|
+
await removeGiftCard('gc_abc123');
|
|
136
141
|
await complete();
|
|
137
142
|
```
|
|
138
143
|
|
|
@@ -282,6 +287,48 @@ const products = await listProducts({ limit: 10 }, { locale: 'fr', country: 'FR'
|
|
|
282
287
|
const category = await getCategory('clothing/shirts', {}, { locale: 'de', country: 'DE' });
|
|
283
288
|
```
|
|
284
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
|
+
|
|
285
332
|
## TypeScript
|
|
286
333
|
|
|
287
334
|
All types are re-exported from `@spree/sdk`:
|