@spree/docs 0.1.297 → 0.1.299
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/idempotency.md +17 -1
- package/dist/api-reference/store.yaml +1 -5
- package/dist/developer/core-concepts/channels.md +1 -1
- package/dist/developer/core-concepts/freight.md +2 -2
- package/dist/developer/core-concepts/fulfillments.md +10 -0
- package/dist/developer/core-concepts/metafields.md +2 -2
- package/dist/developer/core-concepts/payments.md +1 -1
- package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
- package/dist/developer/how-to/build-a-b2b-store.md +2 -2
- package/dist/developer/how-to/custom-digital-asset-provider.md +1 -1
- package/dist/developer/how-to/custom-order-routing.md +5 -1
- package/dist/developer/sdk/admin/resources.md +1 -1
- package/dist/developer/upgrades/5.6-to-6.0.md +13 -0
- package/dist/integrations/payments/stripe-connect.md +3 -3
- package/package.json +1 -1
|
@@ -25,6 +25,20 @@ curl -X POST https://api.mystore.com/api/v3/store/carts/cart_xxx/items \
|
|
|
25
25
|
|
|
26
26
|
If the client retries this exact request with the same idempotency key, the API returns the original response with an `Idempotent-Replayed: true` header — without adding the item again.
|
|
27
27
|
|
|
28
|
+
## Who A Cached Response Belongs To
|
|
29
|
+
|
|
30
|
+
A cached response is only ever returned to the caller that produced it, so the request has to say who that caller is. One of these identifies them:
|
|
31
|
+
|
|
32
|
+
- the **customer's access token** (`Authorization: Bearer ...`), for a signed-in shopper
|
|
33
|
+
- the **cart token** (`X-Spree-Token`), for a guest working on a cart they already hold
|
|
34
|
+
- a **secret key** (`sk_...`), for a server-to-server caller on the Admin API
|
|
35
|
+
|
|
36
|
+
Retry with the same credentials you sent the first time. A replay answers from the cache without running the request again, so it never re-checks your access — a retry that drops or swaps a credential is treated as a different caller and runs for real.
|
|
37
|
+
|
|
38
|
+
Your publishable key (`pk_...`) does not count. Every visitor to your storefront sends the same one, so a key value that two shoppers happen to share — `checkout-1`, an order number, a timestamp — would hand one shopper the other's response, cart token included.
|
|
39
|
+
|
|
40
|
+
A request that carries none of the credentials above still runs exactly as it always has — it is simply never cached, so the `Idempotency-Key` header has no effect on it. Guests are not second-class here: a guest holds a cart token from the moment their cart exists, which covers the whole of checkout, including the steps where a duplicate costs money.
|
|
41
|
+
|
|
28
42
|
## Supported Endpoints
|
|
29
43
|
|
|
30
44
|
| Endpoint | Actions |
|
|
@@ -38,13 +52,15 @@ If the client retries this exact request with the same idempotency key, the API
|
|
|
38
52
|
| `POST /carts/:id/coupon_codes` | Applying coupon codes |
|
|
39
53
|
| `POST /carts/:id/store_credits` | Applying store credits |
|
|
40
54
|
|
|
55
|
+
Every one of these is replayable for a guest as well as for a signed-in shopper, because every one of them is reached with the cart's token. The exception is creating the cart itself: there is no cart yet, so a guest has no token to present and the key is ignored — a retry may leave behind a second, empty cart. Sign the shopper in, or accept the spare cart.
|
|
56
|
+
|
|
41
57
|
Idempotency keys are ignored on `GET`, `DELETE`, and other non-supported actions.
|
|
42
58
|
|
|
43
59
|
## Key Requirements
|
|
44
60
|
|
|
45
61
|
- Must be a string of **255 characters or less**
|
|
46
62
|
- Should be unique per distinct operation (UUIDs are recommended)
|
|
47
|
-
- Keys are scoped
|
|
63
|
+
- Keys are scoped to the caller and the store, so two callers can use the same key value without conflict
|
|
48
64
|
- Cached responses expire after **24 hours**
|
|
49
65
|
|
|
50
66
|
## Response Headers
|
|
@@ -8461,7 +8461,7 @@ paths:
|
|
|
8461
8461
|
client_secret: secret_123
|
|
8462
8462
|
payment_method_id: pm_UkLWZg9DAJ
|
|
8463
8463
|
payment_source_id: card_UkLWZg9DAJ
|
|
8464
|
-
payment_source_type:
|
|
8464
|
+
payment_source_type: credit_card
|
|
8465
8465
|
customer_id: cust_UkLWZg9DAJ
|
|
8466
8466
|
payment_method:
|
|
8467
8467
|
id: pm_UkLWZg9DAJ
|
|
@@ -12746,9 +12746,6 @@ components:
|
|
|
12746
12746
|
type: string
|
|
12747
12747
|
label:
|
|
12748
12748
|
type: string
|
|
12749
|
-
type:
|
|
12750
|
-
type: string
|
|
12751
|
-
deprecated: true
|
|
12752
12749
|
field_type:
|
|
12753
12750
|
type: string
|
|
12754
12751
|
enum:
|
|
@@ -12765,7 +12762,6 @@ components:
|
|
|
12765
12762
|
required:
|
|
12766
12763
|
- id
|
|
12767
12764
|
- label
|
|
12768
|
-
- type
|
|
12769
12765
|
- field_type
|
|
12770
12766
|
- key
|
|
12771
12767
|
- value
|
|
@@ -38,7 +38,7 @@ erDiagram
|
|
|
38
38
|
| `default` | Exactly one channel per store is the default. Used as a fallback when no channel header is present and as the auto-publish target for new products | `true` |
|
|
39
39
|
| `storefront_access` | Controls what an anonymous visitor may see: `public`, `prices_hidden`, or `login_required`. Unset inherits the store's setting. See [Storefront Access Gating](#storefront-access-gating) | `login_required` |
|
|
40
40
|
| `guest_checkout` | Whether an order may be placed without an account on this channel. Unset inherits the store's setting | `false` |
|
|
41
|
-
| `preferred_order_routing_strategy` | Optional per-channel override of the store's [Order Routing](../how-to/custom-order-routing.md) strategy | `
|
|
41
|
+
| `preferred_order_routing_strategy` | Optional per-channel override of the store's [Order Routing](../how-to/custom-order-routing.md) strategy | `rules` |
|
|
42
42
|
|
|
43
43
|
`code` is normalized to a URL-safe slug on save — `POS` becomes `pos`, `Point of Sale!` becomes `point-of-sale`. Leaving `code` blank derives it from `name`.
|
|
44
44
|
|
|
@@ -243,7 +243,7 @@ split.
|
|
|
243
243
|
```typescript Admin SDK
|
|
244
244
|
await adminClient.deliveryMethods.create({
|
|
245
245
|
name: 'Pallet freight',
|
|
246
|
-
rate_provider: '
|
|
246
|
+
rate_provider: 'freight',
|
|
247
247
|
rules: [
|
|
248
248
|
{ type: 'volume_rule', preferences: { minimum_volume: 1, maximum_volume: 15 } },
|
|
249
249
|
{ type: 'company_rule', preferences: { company_orders_only: true } },
|
|
@@ -257,7 +257,7 @@ curl -X POST 'https://api.mystore.com/api/v3/admin/delivery_methods' \
|
|
|
257
257
|
-H 'Content-Type: application/json' \
|
|
258
258
|
-d '{
|
|
259
259
|
"name": "Pallet freight",
|
|
260
|
-
"rate_provider": "
|
|
260
|
+
"rate_provider": "freight",
|
|
261
261
|
"rules": [
|
|
262
262
|
{ "type": "volume_rule", "preferences": { "minimum_volume": 1, "maximum_volume": 15 } },
|
|
263
263
|
{ "type": "company_rule", "preferences": { "company_orders_only": true } }
|
|
@@ -257,6 +257,16 @@ curl 'https://api.mystore.com/api/v3/store/delivery_methods/dm_xxx/pickup_locati
|
|
|
257
257
|
```
|
|
258
258
|
|
|
259
259
|
|
|
260
|
+
Third-party pickup points come from a pickup point provider — a locker network or partner-shop integration. Spree ships none; an extension registers its provider so a delivery method can select it by its short name (for example `inpost` for `SpreeInpost::PickupPointProvider`):
|
|
261
|
+
|
|
262
|
+
```ruby server/config/initializers/spree.rb
|
|
263
|
+
Rails.application.config.after_initialize do
|
|
264
|
+
Spree.pickup_point_providers << SpreeInpost::PickupPointProvider
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
A delivery method can only use a registered provider.
|
|
269
|
+
|
|
260
270
|
## Managing fulfillments
|
|
261
271
|
|
|
262
272
|
|
|
@@ -71,7 +71,7 @@ Because the definition carries the type and the label, the dashboard can build a
|
|
|
71
71
|
|
|
72
72
|
```typescript Admin SDK
|
|
73
73
|
const definition = await adminClient.customFieldDefinitions.create({
|
|
74
|
-
resource_type: '
|
|
74
|
+
resource_type: 'product',
|
|
75
75
|
namespace: 'properties',
|
|
76
76
|
key: 'material',
|
|
77
77
|
label: 'Material',
|
|
@@ -83,7 +83,7 @@ const definition = await adminClient.customFieldDefinitions.create({
|
|
|
83
83
|
|
|
84
84
|
```bash CLI
|
|
85
85
|
spree api post /custom_field_definitions -d '{
|
|
86
|
-
"resource_type": "
|
|
86
|
+
"resource_type": "product",
|
|
87
87
|
"namespace": "properties",
|
|
88
88
|
"key": "material",
|
|
89
89
|
"label": "Material",
|
|
@@ -686,7 +686,7 @@ sequenceDiagram
|
|
|
686
686
|
| `external_client_secret` | Client secret for the frontend SDK | `seti_ABC123_secret_xyz` |
|
|
687
687
|
| `external_data` | Provider-specific data | `{}` |
|
|
688
688
|
| `payment_source_id` | The saved payment source created after completion | `card_xyz789` |
|
|
689
|
-
| `payment_source_type` | The type of saved payment source | `
|
|
689
|
+
| `payment_source_type` | The type of saved payment source | `credit_card` |
|
|
690
690
|
|
|
691
691
|
### Payment Setup Session API
|
|
692
692
|
|
|
@@ -15,7 +15,7 @@ Create a definition for products (Settings → Custom fields, the in-place "Set
|
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
npx spree api post custom_field_definitions --data '{
|
|
18
|
-
"resource_type": "
|
|
18
|
+
"resource_type": "product",
|
|
19
19
|
"namespace": "specs",
|
|
20
20
|
"key": "tech_specs",
|
|
21
21
|
"label": "Technical specifications",
|
|
@@ -808,7 +808,7 @@ A method priced by the **Freight** rate provider returns an unpriced rate. It re
|
|
|
808
808
|
```typescript Admin SDK
|
|
809
809
|
await adminClient.deliveryMethods.create({
|
|
810
810
|
name: 'Pallet freight',
|
|
811
|
-
rate_provider: '
|
|
811
|
+
rate_provider: 'freight',
|
|
812
812
|
rules: [
|
|
813
813
|
{ type: 'volume_rule', preferences: { minimum_volume: 1, maximum_volume: 15 } },
|
|
814
814
|
{ type: 'company_rule', preferences: { company_orders_only: true } },
|
|
@@ -822,7 +822,7 @@ curl -X POST 'https://api.mystore.com/api/v3/admin/delivery_methods' \
|
|
|
822
822
|
-H 'Content-Type: application/json' \
|
|
823
823
|
-d '{
|
|
824
824
|
"name": "Pallet freight",
|
|
825
|
-
"rate_provider": "
|
|
825
|
+
"rate_provider": "freight",
|
|
826
826
|
"rules": [
|
|
827
827
|
{ "type": "volume_rule", "preferences": { "minimum_volume": 1, "maximum_volume": 15 } },
|
|
828
828
|
{ "type": "company_rule", "preferences": { "company_orders_only": true } }
|
|
@@ -94,7 +94,7 @@ end
|
|
|
94
94
|
|
|
95
95
|
Once registered, the provider appears as a source on the product's **Digital files** card: the **Add** button becomes a menu offering **Upload a file** alongside each registered provider. Picking your provider creates an asset with its `provider_type` set and no file attached.
|
|
96
96
|
|
|
97
|
-
> **NOTE:** Registration is what makes a `provider_type` valid.
|
|
97
|
+
> **NOTE:** Registration is what makes a `provider_type` valid. The Admin API names a provider by its shorthand — the class name's last segment, underscored, so `license_key` for the provider above and `file` for the built-in one — and an asset validates that its `provider_type` names a registered provider, so an unregistered or misspelled value is rejected with a 422 rather than failing at download time. A blank `provider_type` is always the built-in `File` provider.
|
|
98
98
|
|
|
99
99
|
## The download contract
|
|
100
100
|
|
|
@@ -236,7 +236,11 @@ First register the class so it's selectable (this is the allowlist the model val
|
|
|
236
236
|
Spree.order_routing.strategies << 'Acme::Oms::Strategy'.constantize
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
-
Then select it
|
|
239
|
+
Then select it — set on `Spree::Store` (default) or `Spree::Channel` (override). Setting an unregistered strategy fails validation. Ruby code may name the class; the Admin API and the dashboard name a strategy by its shorthand, `api_type` (the class name's last segment, underscored — `rules` for the built-in one). Override `self.api_type` on your strategy to give it a distinct, stable shorthand:
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
def self.api_type = 'acme_oms'
|
|
243
|
+
```
|
|
240
244
|
|
|
241
245
|
Activate on the whole store:
|
|
242
246
|
|
|
@@ -122,7 +122,7 @@ await client.products.customFields.create('prod_xxx', {
|
|
|
122
122
|
The generic escape hatch covers any owner type:
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
|
-
await client.customFields('
|
|
125
|
+
await client.customFields('product', 'prod_xxx').list()
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
## Adding your own resources
|
|
@@ -495,6 +495,19 @@ now returns `nil` for an ID with another model's prefix, the same as
|
|
|
495
495
|
`find_by_prefix_id`. Call `Spree::PrefixedId.decode_prefixed_id` if you
|
|
496
496
|
really need to decode any prefix.
|
|
497
497
|
|
|
498
|
+
## Type values are short names, not class names
|
|
499
|
+
|
|
500
|
+
The API no longer sends or accepts Ruby class names. Every value that used to name a class is now a short name (`Spree::CreditCard` becomes `credit_card`), so the API keeps the same shape whatever the server is built with. Records in the database are not touched.
|
|
501
|
+
|
|
502
|
+
If your storefront or webhook receiver reads any of these fields, compare against the short name instead:
|
|
503
|
+
|
|
504
|
+
| Field | Where | Before | After |
|
|
505
|
+
|---|---|---|---|
|
|
506
|
+
| `payment_source_type` | Store API payment setup sessions, `payment_setup_session.*` webhooks | `Spree::CreditCard` | `credit_card` |
|
|
507
|
+
| `type` | Custom fields on products, variants, categories and collections | `Spree::CustomFields::ShortText` | removed — read `field_type` (`short_text`) |
|
|
508
|
+
|
|
509
|
+
The Admin API follows the same rule everywhere: providers and strategies (`fulfillment_provider: 'manual'`, `preferred_order_routing_strategy: 'rules'`), custom field definition `resource_type` (`product`, `category`), tag `taggable_type`, the owner and originator types on records, and filters such as `q[type_eq]=orders`. An integration written against the Admin API preview sends and expects these short names; the [Admin SDK](../sdk/admin/resources.md) types list them.
|
|
510
|
+
|
|
498
511
|
## Every event is declared
|
|
499
512
|
|
|
500
513
|
Events are now declared on the model that publishes them (`publishes_event`, `publishes_events`), and `publish_event` raises in development and test for a name nobody declared. Extensions that publish their own events need one line each — see [Publishing your own events](../core-concepts/events.md#publishing-your-own-events). In production an undeclared event is logged and still delivered.
|
|
@@ -21,10 +21,10 @@ The same setting through the Admin API:
|
|
|
21
21
|
|
|
22
22
|
```typescript Admin SDK
|
|
23
23
|
const { data: providers } = await adminClient.payoutProviders.list()
|
|
24
|
-
// find the row with id '
|
|
24
|
+
// find the row with id 'stripe' — `available` says whether this store can use it
|
|
25
25
|
|
|
26
26
|
await adminClient.store.update({
|
|
27
|
-
preferred_payout_provider: '
|
|
27
|
+
preferred_payout_provider: 'stripe',
|
|
28
28
|
preferred_default_payouts_schedule_interval: 'weekly',
|
|
29
29
|
preferred_default_minimum_payout_amount: 50,
|
|
30
30
|
})
|
|
@@ -34,7 +34,7 @@ await adminClient.store.update({
|
|
|
34
34
|
curl -X PATCH https://your-store.com/api/v3/admin/store \
|
|
35
35
|
-H "X-Spree-API-Key: sk_xxx" \
|
|
36
36
|
-H "Content-Type: application/json" \
|
|
37
|
-
-d '{"preferred_payout_provider": "
|
|
37
|
+
-d '{"preferred_payout_provider": "stripe", "preferred_default_payouts_schedule_interval": "weekly", "preferred_default_minimum_payout_amount": 50}'
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
|