@spree/docs 0.1.229 → 0.1.231
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.yaml +78 -78
- package/dist/developer/agentic/agent-skills.md +1 -1
- package/dist/developer/cli/quickstart.md +2 -2
- package/dist/developer/core-concepts/addresses.md +13 -0
- package/dist/developer/core-concepts/calculators.md +6 -1
- package/dist/developer/core-concepts/carts.md +19 -1
- package/dist/developer/core-concepts/catalogs.md +51 -2
- package/dist/developer/core-concepts/channels.md +20 -1
- package/dist/developer/core-concepts/commissions.md +70 -1
- package/dist/developer/core-concepts/companies.md +74 -0
- package/dist/developer/core-concepts/customers.md +17 -5
- package/dist/developer/core-concepts/data-privacy.md +20 -7
- package/dist/developer/core-concepts/delivery-setup.md +57 -0
- package/dist/developer/core-concepts/discounts.md +7 -0
- package/dist/developer/core-concepts/events.md +13 -7
- package/dist/developer/core-concepts/fees.md +20 -0
- package/dist/developer/core-concepts/fulfillments.md +3 -3
- package/dist/developer/core-concepts/imports-exports.md +21 -5
- package/dist/developer/core-concepts/inventory.md +73 -1
- package/dist/developer/core-concepts/markets.md +5 -6
- package/dist/developer/core-concepts/media.md +66 -1
- package/dist/developer/core-concepts/metafields.md +1 -1
- package/dist/developer/core-concepts/order-totals.md +18 -1
- package/dist/developer/core-concepts/orders.md +2 -1
- package/dist/developer/core-concepts/payments.md +100 -20
- package/dist/developer/core-concepts/pricing.md +13 -8
- package/dist/developer/core-concepts/products.md +52 -1
- package/dist/developer/core-concepts/reporting.md +5 -5
- package/dist/developer/core-concepts/returns-exchanges-claims.md +41 -2
- package/dist/developer/core-concepts/search-filtering.md +1 -2
- package/dist/developer/core-concepts/sellers.md +109 -2
- package/dist/developer/core-concepts/slugs.md +1 -1
- package/dist/developer/core-concepts/staff-roles.md +23 -3
- package/dist/developer/core-concepts/stores.md +21 -0
- package/dist/developer/core-concepts/taxes.md +43 -0
- package/dist/developer/core-concepts/translations.md +9 -0
- package/dist/developer/core-concepts/webhooks.md +45 -3
- package/dist/developer/create-spree-app/quickstart.md +10 -10
- package/dist/developer/dashboard/deployment.md +4 -4
- package/dist/developer/dashboard/recipes/attribute-end-to-end.md +1 -1
- package/dist/developer/deployment/docker.md +2 -2
- package/dist/developer/how-to/custom-promotion.md +33 -13
- package/dist/developer/multi-tenant/quickstart.md +6 -6
- package/dist/developer/storefront/nextjs/testing.md +2 -2
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ Spree has a highly flexible payments model which allows multiple payment methods
|
|
|
9
9
|
|
|
10
10
|
Payment methods typically represent a payment gateway. Gateways will process card payments, online bank transfers, buy-now-pay-later, wallet payments, and other types of payments. Spree also comes with a Check option for offline processing.
|
|
11
11
|
|
|
12
|
-
The `Payment` model in Spree tracks payments against [Orders](orders). Payments relate to a `source` which indicates how the payment was made, and a `PaymentMethod`, indicating the processor used for this payment.
|
|
12
|
+
The `Payment` model in Spree tracks payments against [Orders](orders.md). Payments relate to a `source` which indicates how the payment was made, and a `PaymentMethod`, indicating the processor used for this payment.
|
|
13
13
|
|
|
14
14
|
When a payment is created, it is given a unique, 8-character identifier. This is used when sending the payment details to the payment processor. Without this identifier, some payment gateways mistakenly reported duplicate payments.
|
|
15
15
|
|
|
@@ -153,7 +153,8 @@ Payment methods where `session_required?` returns `false` don't need a payment s
|
|
|
153
153
|
|
|
154
154
|
For these methods, the Store API allows creating a payment directly without going through the payment session flow:
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
|
|
157
|
+
```typescript Store SDK
|
|
157
158
|
const options = { spreeToken: cart.token }
|
|
158
159
|
|
|
159
160
|
// Create a payment for a non-session payment method
|
|
@@ -166,6 +167,19 @@ const payment = await client.carts.payments.create(cart.id, {
|
|
|
166
167
|
}, options)
|
|
167
168
|
```
|
|
168
169
|
|
|
170
|
+
```bash cURL
|
|
171
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payments' \
|
|
172
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
173
|
+
-H 'X-Spree-Token: CART_TOKEN' \
|
|
174
|
+
-H 'Content-Type: application/json' \
|
|
175
|
+
-d '{
|
|
176
|
+
"payment_method_id": "pm_xyz789",
|
|
177
|
+
"amount": "99.99",
|
|
178
|
+
"metadata": { "purchase_order_number": "PO-12345" }
|
|
179
|
+
}'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
|
|
169
183
|
The payment is created in `checkout` state. When the order transitions to `complete`, Spree calls `process_payments!` which runs the payment method's `authorize` (or `purchase` if auto-capture is enabled). For manual payment methods like Check, this is a no-op that succeeds immediately — the payment moves to `pending` (or `completed` with auto-capture), allowing the order to complete.
|
|
170
184
|
|
|
171
185
|
The merchant can later capture or void the payment from the Admin Panel once the actual payment is received (e.g., check arrives, bank transfer clears, cash is collected on delivery).
|
|
@@ -349,7 +363,8 @@ stateDiagram-v2
|
|
|
349
363
|
|
|
350
364
|
**Create a Payment Session:**
|
|
351
365
|
|
|
352
|
-
|
|
366
|
+
|
|
367
|
+
```typescript Store SDK
|
|
353
368
|
const options = { spreeToken: cart.token }
|
|
354
369
|
|
|
355
370
|
// Create a payment session for the selected payment method
|
|
@@ -364,9 +379,18 @@ console.log(session.external_id) // e.g. 'pi_3ABC123' (Stripe Payme
|
|
|
364
379
|
console.log(session.external_data.client_secret) // Use with Stripe.js or Adyen Drop-in
|
|
365
380
|
```
|
|
366
381
|
|
|
382
|
+
```bash cURL
|
|
383
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions' \
|
|
384
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
385
|
+
-H 'X-Spree-Token: CART_TOKEN' \
|
|
386
|
+
-H 'Content-Type: application/json' \
|
|
387
|
+
-d '{ "payment_method_id": "pm_xyz789", "amount": "99.99", "external_data": {} }'
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
|
|
367
391
|
**Response shape (`StorePaymentSession`):**
|
|
368
392
|
|
|
369
|
-
```json
|
|
393
|
+
```json Response
|
|
370
394
|
{
|
|
371
395
|
"id": "ps_abc123",
|
|
372
396
|
"status": "pending",
|
|
@@ -386,7 +410,8 @@ console.log(session.external_data.client_secret) // Use with Stripe.js or Adyen
|
|
|
386
410
|
|
|
387
411
|
**Update a Payment Session** (e.g., after order total changes):
|
|
388
412
|
|
|
389
|
-
|
|
413
|
+
|
|
414
|
+
```typescript Store SDK
|
|
390
415
|
const updated = await client.carts.paymentSessions.update(
|
|
391
416
|
cart.id, session.id,
|
|
392
417
|
{ amount: '149.99' },
|
|
@@ -394,9 +419,19 @@ const updated = await client.carts.paymentSessions.update(
|
|
|
394
419
|
)
|
|
395
420
|
```
|
|
396
421
|
|
|
422
|
+
```bash cURL
|
|
423
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions/ps_xxx' \
|
|
424
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
425
|
+
-H 'X-Spree-Token: CART_TOKEN' \
|
|
426
|
+
-H 'Content-Type: application/json' \
|
|
427
|
+
-d '{ "amount": "149.99" }'
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
|
|
397
431
|
**Complete a Payment Session** (after customer confirms payment on the frontend):
|
|
398
432
|
|
|
399
|
-
|
|
433
|
+
|
|
434
|
+
```typescript Store SDK
|
|
400
435
|
const completed = await client.carts.paymentSessions.complete(
|
|
401
436
|
cart.id, session.id,
|
|
402
437
|
{ session_result: '...', external_data: {} },
|
|
@@ -405,14 +440,31 @@ const completed = await client.carts.paymentSessions.complete(
|
|
|
405
440
|
console.log(completed.status) // 'completed'
|
|
406
441
|
```
|
|
407
442
|
|
|
443
|
+
```bash cURL
|
|
444
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/payment_sessions/ps_xxx/complete' \
|
|
445
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
446
|
+
-H 'X-Spree-Token: CART_TOKEN' \
|
|
447
|
+
-H 'Content-Type: application/json' \
|
|
448
|
+
-d '{ "session_result": "...", "external_data": {} }'
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
|
|
408
452
|
> **WARNING:** Completing a payment session does **not** complete the order. You must call `POST /carts/:id/complete` separately after the session is completed. This separation prevents race conditions between the frontend and payment webhooks.
|
|
409
453
|
|
|
410
454
|
**Complete the Order** (after the payment session is completed):
|
|
411
455
|
|
|
412
|
-
|
|
456
|
+
|
|
457
|
+
```typescript Store SDK
|
|
413
458
|
const order = await client.carts.complete(cart.id, options)
|
|
414
459
|
```
|
|
415
460
|
|
|
461
|
+
```bash cURL
|
|
462
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/carts/cart_xxx/complete' \
|
|
463
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
464
|
+
-H 'X-Spree-Token: CART_TOKEN'
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
|
|
416
468
|
### Payment Webhooks
|
|
417
469
|
|
|
418
470
|
Spree provides a generic webhook endpoint at `POST /api/v3/webhooks/payments/:payment_method_id` that payment gateway extensions can use. When a payment provider sends a webhook (e.g., Stripe `payment_intent.succeeded`), Spree:
|
|
@@ -427,7 +479,7 @@ The background job creates/updates the Payment record, marks the session as comp
|
|
|
427
479
|
|
|
428
480
|
Gateway extensions implement `parse_webhook_event` to normalize provider-specific payloads:
|
|
429
481
|
|
|
430
|
-
```ruby
|
|
482
|
+
```ruby server/app/models/my_gateway.rb
|
|
431
483
|
class MyGateway < Spree::Gateway
|
|
432
484
|
def parse_webhook_event(raw_body, headers)
|
|
433
485
|
# Verify signature — raise WebhookSignatureError if invalid
|
|
@@ -591,7 +643,8 @@ sequenceDiagram
|
|
|
591
643
|
|
|
592
644
|
**Create a Payment Setup Session:**
|
|
593
645
|
|
|
594
|
-
|
|
646
|
+
|
|
647
|
+
```typescript Store SDK
|
|
595
648
|
const options = { token: jwtToken }
|
|
596
649
|
|
|
597
650
|
// Create a setup session for saving a payment method
|
|
@@ -604,9 +657,18 @@ const setupSession = await client.customer.paymentSetupSessions.create({
|
|
|
604
657
|
console.log(setupSession.external_client_secret) // e.g. 'seti_ABC123_secret_xyz'
|
|
605
658
|
```
|
|
606
659
|
|
|
660
|
+
```bash cURL
|
|
661
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions' \
|
|
662
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
663
|
+
-H 'Authorization: Bearer $CUSTOMER_JWT' \
|
|
664
|
+
-H 'Content-Type: application/json' \
|
|
665
|
+
-d '{ "payment_method_id": "pm_xyz789", "external_data": {} }'
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
|
|
607
669
|
**Response shape (`StorePaymentSetupSession`):**
|
|
608
670
|
|
|
609
|
-
```json
|
|
671
|
+
```json Response
|
|
610
672
|
{
|
|
611
673
|
"id": "pss_abc123",
|
|
612
674
|
"status": "pending",
|
|
@@ -622,13 +684,22 @@ console.log(setupSession.external_client_secret) // e.g. 'seti_ABC123_secret_xyz
|
|
|
622
684
|
|
|
623
685
|
**Get a Payment Setup Session:**
|
|
624
686
|
|
|
625
|
-
|
|
687
|
+
|
|
688
|
+
```typescript Store SDK
|
|
626
689
|
const session = await client.customer.paymentSetupSessions.get('pss_abc123', options)
|
|
627
690
|
```
|
|
628
691
|
|
|
692
|
+
```bash cURL
|
|
693
|
+
curl 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions/pss_abc123' \
|
|
694
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
695
|
+
-H 'Authorization: Bearer $CUSTOMER_JWT'
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
|
|
629
699
|
**Complete a Payment Setup Session** (after the customer completes setup on the frontend using the gateway SDK and `external_client_secret`):
|
|
630
700
|
|
|
631
|
-
|
|
701
|
+
|
|
702
|
+
```typescript Store SDK
|
|
632
703
|
const completed = await client.customer.paymentSetupSessions.complete(
|
|
633
704
|
'pss_abc123',
|
|
634
705
|
{ external_data: {} },
|
|
@@ -638,6 +709,15 @@ console.log(completed.status) // 'completed'
|
|
|
638
709
|
console.log(completed.payment_source_id) // e.g. 'card_xyz789' - the saved payment method
|
|
639
710
|
```
|
|
640
711
|
|
|
712
|
+
```bash cURL
|
|
713
|
+
curl -X POST 'https://api.mystore.com/api/v3/store/customers/me/payment_setup_sessions/pss_abc123/complete' \
|
|
714
|
+
-H 'X-Spree-API-Key: pk_xxx' \
|
|
715
|
+
-H 'Authorization: Bearer $CUSTOMER_JWT' \
|
|
716
|
+
-H 'Content-Type: application/json' \
|
|
717
|
+
-d '{ "external_data": {} }'
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
|
|
641
721
|
Spree will verify the result with the provider and create a saved payment source (e.g., `Spree::CreditCard`) that can be used for future payments.
|
|
642
722
|
|
|
643
723
|
## Supported Gateways
|
|
@@ -678,14 +758,6 @@ Spree publishes events throughout the payment lifecycle that you can subscribe t
|
|
|
678
758
|
|
|
679
759
|
See [Events](events.md) for more details on subscribing to events.
|
|
680
760
|
|
|
681
|
-
## Related Documentation
|
|
682
|
-
|
|
683
|
-
- [Payments (Store SDK)](../sdk/store/payments.md) - SDK how-to for payment sessions, payments, and setup sessions
|
|
684
|
-
- [Build a Custom Payment Method](../how-to/custom-payment-method.md) - Step-by-step guide to creating your own payment gateway integration
|
|
685
|
-
- [Orders](orders.md) - Order lifecycle, payment and fulfillment status
|
|
686
|
-
- [Checkout Customization](carts.md) - Customizing the checkout flow
|
|
687
|
-
- [Events](events.md) - Subscribe to payment events
|
|
688
|
-
|
|
689
761
|
## Two paths to a completed order
|
|
690
762
|
|
|
691
763
|
A payment can finish in two places, and both have to end at the same result.
|
|
@@ -697,3 +769,11 @@ Whichever arrives first completes the order; the other finds the work already do
|
|
|
697
769
|
> **WARNING:** Never treat the browser returning from a redirect as proof of payment. The provider's webhook is the authoritative signal — a customer can close the tab, and a browser response can be forged.
|
|
698
770
|
|
|
699
771
|
Both paths can be replaced if your integration needs different behaviour — see [Dependencies](../customization/dependencies.md).
|
|
772
|
+
|
|
773
|
+
## Related Documentation
|
|
774
|
+
|
|
775
|
+
- [Payments (Store SDK)](../sdk/store/payments.md) - SDK how-to for payment sessions, payments, and setup sessions
|
|
776
|
+
- [Build a Custom Payment Method](../how-to/custom-payment-method.md) - Step-by-step guide to creating your own payment gateway integration
|
|
777
|
+
- [Orders](orders.md) - Order lifecycle, payment and fulfillment status
|
|
778
|
+
- [Checkout Customization](carts.md) - Customizing the checkout flow
|
|
779
|
+
- [Events](events.md) - Subscribe to payment events
|
|
@@ -268,7 +268,7 @@ curl 'https://api.mystore.com/api/v3/store/products/spree-tote?expand=prior_pric
|
|
|
268
268
|
|
|
269
269
|
The response includes a `prior_price` object when expanded:
|
|
270
270
|
|
|
271
|
-
```json
|
|
271
|
+
```json Response
|
|
272
272
|
{
|
|
273
273
|
"id": "prod_xxx",
|
|
274
274
|
"name": "Spree Tote",
|
|
@@ -287,15 +287,20 @@ The response includes a `prior_price` object when expanded:
|
|
|
287
287
|
|
|
288
288
|
Price history tracking is enabled by default. To disable it (e.g., for non-EU stores):
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
290
|
+
|
|
291
|
+
```typescript Admin SDK
|
|
292
|
+
await client.store.update({ preferred_track_price_history: false })
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```bash cURL
|
|
296
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/store' \
|
|
297
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
298
|
+
-H 'Content-Type: application/json' \
|
|
299
|
+
-d '{ "preferred_track_price_history": false }'
|
|
296
300
|
```
|
|
297
301
|
|
|
298
|
-
|
|
302
|
+
|
|
303
|
+
Retention defaults to 30 days, set per store with the `price_history_retention_days` preference. A Rake task prunes anything older:
|
|
299
304
|
|
|
300
305
|
|
|
301
306
|
```bash Spree CLI (Docker)
|
|
@@ -213,6 +213,7 @@ spree api post /products -d '{
|
|
|
213
213
|
|
|
214
214
|
For something with no options at all, send prices directly and skip the variants array — Spree forwards them to the product's single variant:
|
|
215
215
|
|
|
216
|
+
|
|
216
217
|
```typescript Admin SDK
|
|
217
218
|
await adminClient.products.create({
|
|
218
219
|
name: 'The Spree Handbook',
|
|
@@ -221,6 +222,18 @@ await adminClient.products.create({
|
|
|
221
222
|
})
|
|
222
223
|
```
|
|
223
224
|
|
|
225
|
+
```bash cURL
|
|
226
|
+
curl -X POST 'https://api.mystore.com/api/v3/admin/products' \
|
|
227
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
228
|
+
-H 'Content-Type: application/json' \
|
|
229
|
+
-d '{
|
|
230
|
+
"name": "The Spree Handbook",
|
|
231
|
+
"status": "active",
|
|
232
|
+
"prices": [{ "currency": "USD", "amount": "19.99" }]
|
|
233
|
+
}'
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
|
|
224
237
|
Don't pass both.
|
|
225
238
|
|
|
226
239
|
Update, clone, or archive a product (deleting soft-deletes it):
|
|
@@ -346,7 +359,7 @@ await client.products.approve('prod_xxx')
|
|
|
346
359
|
await client.products.reject('prod_xxx', { reason: 'Please add a photo showing scale.' })
|
|
347
360
|
|
|
348
361
|
// The review queue
|
|
349
|
-
const pending = await client.products.list({
|
|
362
|
+
const pending = await client.products.list({ status_eq: 'proposed' })
|
|
350
363
|
|
|
351
364
|
// Who decided, and when
|
|
352
365
|
const product = await client.products.get('prod_xxx', { expand: ['submission'] })
|
|
@@ -448,6 +461,7 @@ There's no such thing as a product without one. A book with no size or colour st
|
|
|
448
461
|
|
|
449
462
|
`default_variant_id` names the variant that represents the product — the price shown on a listing page, and what "add to cart" means before anyone picks anything.
|
|
450
463
|
|
|
464
|
+
|
|
451
465
|
```typescript Store SDK
|
|
452
466
|
const product = await client.products.get('spree-tote')
|
|
453
467
|
|
|
@@ -456,6 +470,12 @@ product.price // that variant's price
|
|
|
456
470
|
product.variant_count // 6
|
|
457
471
|
```
|
|
458
472
|
|
|
473
|
+
```bash cURL
|
|
474
|
+
curl 'https://api.mystore.com/api/v3/store/products/spree-tote' \
|
|
475
|
+
-H 'X-Spree-API-Key: pk_xxx'
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
|
|
459
479
|
For a single-variant product that's the only variant. For a product with options it's the first one, unless you say otherwise. If the default is ever removed, another is promoted automatically — a product is never left without one.
|
|
460
480
|
|
|
461
481
|
Products can also carry `buy_box_variant_id`, naming the variant a marketplace has chosen to feature when several sellers offer the same listing.
|
|
@@ -583,6 +603,7 @@ erDiagram
|
|
|
583
603
|
}
|
|
584
604
|
```
|
|
585
605
|
|
|
606
|
+
|
|
586
607
|
```typescript Admin SDK
|
|
587
608
|
const shoes = await adminClient.productTypes.create({
|
|
588
609
|
name: 'Footwear',
|
|
@@ -601,6 +622,19 @@ await adminClient.products.create({
|
|
|
601
622
|
})
|
|
602
623
|
```
|
|
603
624
|
|
|
625
|
+
```bash cURL
|
|
626
|
+
curl -X POST 'https://api.mystore.com/api/v3/admin/product_types' \
|
|
627
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
628
|
+
-H 'Content-Type: application/json' \
|
|
629
|
+
-d '{
|
|
630
|
+
"name": "Footwear",
|
|
631
|
+
"option_type_ids": ["optt_size", "optt_color"],
|
|
632
|
+
"category_ids": ["ctg_footwear"],
|
|
633
|
+
"delivery_profile_id": "dp_standard"
|
|
634
|
+
}'
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
|
|
604
638
|
### A type is a template, not a controller
|
|
605
639
|
|
|
606
640
|
This is the part that determines how you should think about them.
|
|
@@ -620,10 +654,17 @@ Seeding is also **additive** — it adds what's missing and never removes what a
|
|
|
620
654
|
|
|
621
655
|
If you *do* want an edited type to reach the products already using it, that's an explicit action:
|
|
622
656
|
|
|
657
|
+
|
|
623
658
|
```typescript Admin SDK
|
|
624
659
|
const { products_count } = await adminClient.productTypes.applyToProducts('pt_xxx')
|
|
625
660
|
```
|
|
626
661
|
|
|
662
|
+
```bash cURL
|
|
663
|
+
curl -X POST 'https://api.mystore.com/api/v3/admin/product_types/pt_xxx/apply_to_products' \
|
|
664
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
|
|
627
668
|
It runs in the background and is additive like the rest — never a side effect of saving a type.
|
|
628
669
|
|
|
629
670
|
> **INFO:** `required` on a type's custom field is **advisory** — it marks the field in the dashboard but isn't enforced on write, since Spree saves the product and its fields in two steps. Validate in your own tooling if you need it enforced.
|
|
@@ -878,6 +919,7 @@ curl 'https://api.mystore.com/api/v3/store/categories/clothing/shirts/products?l
|
|
|
878
919
|
|
|
879
920
|
Collections work the same way from a storefront's point of view:
|
|
880
921
|
|
|
922
|
+
|
|
881
923
|
```typescript Store SDK
|
|
882
924
|
const { data: collections } = await client.collections.list()
|
|
883
925
|
|
|
@@ -886,6 +928,15 @@ const { data: products } = await client.collections.products.list('summer-2025',
|
|
|
886
928
|
})
|
|
887
929
|
```
|
|
888
930
|
|
|
931
|
+
```bash cURL
|
|
932
|
+
curl 'https://api.mystore.com/api/v3/store/collections' \
|
|
933
|
+
-H 'X-Spree-API-Key: pk_xxx'
|
|
934
|
+
|
|
935
|
+
curl 'https://api.mystore.com/api/v3/store/collections/summer-2025/products?limit=12' \
|
|
936
|
+
-H 'X-Spree-API-Key: pk_xxx'
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
|
|
889
940
|
A rule-based collection is defined once and maintains itself — set it to match everything tagged `sale` and under $50, and products appear and disappear as those facts change. Ordering can be manual or by a rule such as newest first.
|
|
890
941
|
|
|
891
942
|
## Publications and Sales Channels
|
|
@@ -28,7 +28,7 @@ flowchart LR
|
|
|
28
28
|
|
|
29
29
|
A query names what to measure and how to slice it. This is the whole surface:
|
|
30
30
|
|
|
31
|
-
```json
|
|
31
|
+
```json Query
|
|
32
32
|
{
|
|
33
33
|
"metrics": ["total_sales", "orders"],
|
|
34
34
|
"dimensions": [{ "name": "completed_at", "grain": "week" }],
|
|
@@ -76,7 +76,7 @@ different clocks, so a payment total beside a units-received count is two
|
|
|
76
76
|
reports wearing one table — the query refuses the mix rather than inventing a
|
|
77
77
|
join between grains that have no honest relationship.
|
|
78
78
|
|
|
79
|
-
```ruby
|
|
79
|
+
```ruby server/config/initializers/spree.rb
|
|
80
80
|
Spree.reporting.metric :total_sales,
|
|
81
81
|
sql: 'SUM(%{orders}.total)', base: :orders, format: :money
|
|
82
82
|
```
|
|
@@ -118,7 +118,7 @@ row and inflate every other metric in the same query.
|
|
|
118
118
|
Core's sales metrics follow the sequence merchants and accountants already use,
|
|
119
119
|
so a Spree figure means what the same word means everywhere else:
|
|
120
120
|
|
|
121
|
-
```
|
|
121
|
+
```text
|
|
122
122
|
gross_sales − discounts − returns = net_sales
|
|
123
123
|
net_sales + shipping + duties + fees + taxes = total_sales
|
|
124
124
|
```
|
|
@@ -137,7 +137,7 @@ zero and margin over an incompletely-costed catalogue reads high.
|
|
|
137
137
|
A dimension groups and filters. Its definition owns every behaviour keyed off
|
|
138
138
|
it, which is what lets an extension-registered dimension work end to end:
|
|
139
139
|
|
|
140
|
-
```ruby
|
|
140
|
+
```ruby server/config/initializers/spree.rb
|
|
141
141
|
Spree.reporting.dimension :channel, base: :orders, column: :channel_id,
|
|
142
142
|
lookup: :channel,
|
|
143
143
|
subject: -> { Spree::Channel }, key_scope: 'read_channels',
|
|
@@ -179,7 +179,7 @@ fulfill", "Open returns" and "Low stock" are the state of the store right now,
|
|
|
179
179
|
with no time range and no currency. The registry holds these as **counters**
|
|
180
180
|
beside metrics and dimensions, and `GET /dashboard/counters` evaluates them:
|
|
181
181
|
|
|
182
|
-
```json
|
|
182
|
+
```json Response
|
|
183
183
|
{
|
|
184
184
|
"channel_id": null,
|
|
185
185
|
"counters": [
|
|
@@ -163,6 +163,7 @@ how postage records work in general.
|
|
|
163
163
|
|
|
164
164
|
What to do about a claim is decided when you resolve it, not when the customer opens it — merchants usually decide once they've seen the photos.
|
|
165
165
|
|
|
166
|
+
|
|
166
167
|
```typescript Admin SDK
|
|
167
168
|
await adminClient.orders.claims.approve('or_xxx', 'clm_xxx')
|
|
168
169
|
|
|
@@ -173,32 +174,70 @@ await adminClient.orders.claims.resolve('or_xxx', 'clm_xxx', {
|
|
|
173
174
|
})
|
|
174
175
|
```
|
|
175
176
|
|
|
177
|
+
```bash cURL
|
|
178
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/claims/clm_xxx/approve' \
|
|
179
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
180
|
+
|
|
181
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/claims/clm_xxx/resolve' \
|
|
182
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
183
|
+
-H 'Content-Type: application/json' \
|
|
184
|
+
-d '{
|
|
185
|
+
"resolution": "refund_and_replacement",
|
|
186
|
+
"refund_method": "store_credit",
|
|
187
|
+
"replacement_line_item_ids": ["li_xxx"]
|
|
188
|
+
}'
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
|
|
176
192
|
A replacement creates a new [fulfillment](fulfillments.md) on the original order, so the customer doesn't have to place a second one.
|
|
177
193
|
|
|
178
194
|
## Exchanges
|
|
179
195
|
|
|
180
196
|
An exchange works like a return, but ends by sending different items instead of refunding:
|
|
181
197
|
|
|
198
|
+
|
|
182
199
|
```typescript Admin SDK
|
|
183
200
|
await adminClient.orders.exchanges.approve('or_xxx', 'exch_xxx')
|
|
184
201
|
await adminClient.orders.exchanges.receive('or_xxx', 'exch_xxx')
|
|
185
202
|
await adminClient.orders.exchanges.fulfill('or_xxx', 'exch_xxx')
|
|
186
203
|
```
|
|
187
204
|
|
|
205
|
+
```bash cURL
|
|
206
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/approve' \
|
|
207
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
208
|
+
|
|
209
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/receive' \
|
|
210
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
211
|
+
|
|
212
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/orders/or_xxx/exchanges/exch_xxx/fulfill' \
|
|
213
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
|
|
188
217
|
## Reporting
|
|
189
218
|
|
|
190
219
|
Because each one is its own record, you can query them directly:
|
|
191
220
|
|
|
221
|
+
|
|
192
222
|
```typescript Admin SDK
|
|
193
223
|
// Everything awaiting action
|
|
194
|
-
const pending = await adminClient.returns.list({
|
|
224
|
+
const pending = await adminClient.returns.list({ status_eq: 'approved' })
|
|
195
225
|
|
|
196
226
|
// Claims opened this month
|
|
197
227
|
const claims = await adminClient.claims.list({
|
|
198
|
-
|
|
228
|
+
created_at_gt: '2026-08-01',
|
|
199
229
|
})
|
|
200
230
|
```
|
|
201
231
|
|
|
232
|
+
```bash cURL
|
|
233
|
+
curl 'https://api.mystore.com/api/v3/admin/returns?q[status_eq]=approved' \
|
|
234
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
235
|
+
|
|
236
|
+
curl 'https://api.mystore.com/api/v3/admin/claims?q[created_at_gt]=2026-08-01' \
|
|
237
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
|
|
202
241
|
## Return policy
|
|
203
242
|
|
|
204
243
|
Spree ships no built-in return window. Whether a customer may open a return is store policy, and it's checked when the return is created — so you can hold customers to a 30-day window while letting staff make an exception for a good customer, and vary the rule by market where local law requires it.
|
|
@@ -278,8 +278,7 @@ See [Querying](../../api-reference/store-api/querying.md) for the full list of f
|
|
|
278
278
|
|
|
279
279
|
Spree uses a pluggable search provider architecture. The default provider uses SQL (ILIKE + Ransack). For production catalogs with 1,000+ products, we recommend switching to [Meilisearch](../../integrations/search/meilisearch.md) for typo tolerance, relevance ranking, and faster faceted search.
|
|
280
280
|
|
|
281
|
-
```ruby
|
|
282
|
-
# config/initializers/spree.rb
|
|
281
|
+
```ruby server/config/initializers/spree.rb
|
|
283
282
|
Spree.search_provider = 'Spree::SearchProvider::Meilisearch'
|
|
284
283
|
```
|
|
285
284
|
|