@spree/docs 0.1.305 → 0.1.306
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 +14 -43
- package/dist/developer/core-concepts/calculators.md +2 -2
- package/dist/developer/core-concepts/channels.md +5 -5
- package/dist/developer/core-concepts/pricing.md +2 -2
- package/dist/developer/customization/model-preferences.md +131 -1
- package/dist/developer/customization/validations.md +2 -2
- package/dist/developer/how-to/build-a-b2b-store.md +5 -5
- package/dist/developer/how-to/build-a-marketplace.md +22 -22
- package/dist/developer/how-to/custom-order-routing.md +3 -3
- package/dist/developer/upgrades/5.6-to-6.0.md +33 -3
- package/dist/integrations/payments/stripe-connect.md +4 -4
- package/package.json +1 -1
|
@@ -11854,50 +11854,21 @@ components:
|
|
|
11854
11854
|
- id
|
|
11855
11855
|
- name
|
|
11856
11856
|
- code
|
|
11857
|
-
|
|
11857
|
+
PreferenceSchema:
|
|
11858
11858
|
type: object
|
|
11859
|
-
description:
|
|
11860
|
-
|
|
11861
|
-
a
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
11872
|
-
description: Default value (any JSON type), null when there is no default
|
|
11873
|
-
nullable: true
|
|
11874
|
-
required:
|
|
11875
|
-
- key
|
|
11876
|
-
- type
|
|
11877
|
-
PromotionActionCalculator:
|
|
11878
|
-
type: object
|
|
11879
|
-
description: The action's nested calculator (when the action carries one — null
|
|
11880
|
-
for actions like `free_shipping`)
|
|
11881
|
-
properties:
|
|
11882
|
-
type:
|
|
11883
|
-
type: string
|
|
11884
|
-
example: flat_rate
|
|
11885
|
-
description: Wire shorthand for the calculator subclass
|
|
11886
|
-
label:
|
|
11887
|
-
type: string
|
|
11888
|
-
example: Flat Rate
|
|
11889
|
-
preferences:
|
|
11890
|
-
type: object
|
|
11891
|
-
additionalProperties: true
|
|
11892
|
-
preference_schema:
|
|
11893
|
-
type: array
|
|
11894
|
-
items:
|
|
11895
|
-
"$ref": "#/components/schemas/PreferenceField"
|
|
11896
|
-
required:
|
|
11897
|
-
- type
|
|
11898
|
-
- label
|
|
11899
|
-
- preferences
|
|
11900
|
-
- preference_schema
|
|
11859
|
+
description: 'The JSON Schema (draft 2020-12) of a type''s `preferences`: one
|
|
11860
|
+
property per setting, with `format` (`money`, `currency`, `iso-country`, `prefixed-id`,
|
|
11861
|
+
…), `enum`, `default`, `x-spree-secret` for a secret and `x-spree-prefix`
|
|
11862
|
+
for an id list. Unknown keys are refused.'
|
|
11863
|
+
additionalProperties: true
|
|
11864
|
+
example:
|
|
11865
|
+
type: object
|
|
11866
|
+
properties:
|
|
11867
|
+
amount:
|
|
11868
|
+
type: string
|
|
11869
|
+
format: money
|
|
11870
|
+
default: '0.0'
|
|
11871
|
+
additionalProperties: false
|
|
11901
11872
|
PromotionActionLineItem:
|
|
11902
11873
|
type: object
|
|
11903
11874
|
description: One row in a `create_line_items` action — the variant added to
|
|
@@ -94,8 +94,8 @@ const calculators = await adminClient.deliveryMethods.calculators()
|
|
|
94
94
|
await adminClient.deliveryMethods.create({
|
|
95
95
|
name: 'Standard shipping',
|
|
96
96
|
delivery_zone_id: 'dz_xxx',
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
calculator: {
|
|
98
|
+
type: 'flat_rate',
|
|
99
99
|
preferences: { amount: '7.00', currency: 'USD' },
|
|
100
100
|
},
|
|
101
101
|
})
|
|
@@ -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
|
-
| `
|
|
41
|
+
| `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
|
|
|
@@ -132,13 +132,13 @@ Both controls fall back to the owning [Store](stores.md) when the channel's own
|
|
|
132
132
|
```typescript Admin SDK
|
|
133
133
|
// Gate a channel behind sign-in, and require accounts at checkout
|
|
134
134
|
await adminClient.channels.update('ch_wholesale', {
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
storefront_access: 'login_required',
|
|
136
|
+
guest_checkout: false,
|
|
137
137
|
})
|
|
138
138
|
|
|
139
139
|
// Clear the channel value to inherit the store's default
|
|
140
140
|
await adminClient.channels.update('ch_wholesale', {
|
|
141
|
-
|
|
141
|
+
storefront_access: null,
|
|
142
142
|
})
|
|
143
143
|
```
|
|
144
144
|
|
|
@@ -146,7 +146,7 @@ await adminClient.channels.update('ch_wholesale', {
|
|
|
146
146
|
curl -X PATCH 'https://api.mystore.com/api/v3/admin/channels/ch_wholesale' \
|
|
147
147
|
-H 'X-Spree-API-Key: sk_xxx' \
|
|
148
148
|
-H 'Content-Type: application/json' \
|
|
149
|
-
-d '{ "
|
|
149
|
+
-d '{ "storefront_access": "login_required", "guest_checkout": false }'
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
|
|
@@ -295,14 +295,14 @@ Price history tracking is enabled by default. To disable it (e.g., for non-EU st
|
|
|
295
295
|
|
|
296
296
|
|
|
297
297
|
```typescript Admin SDK
|
|
298
|
-
await client.store.update({
|
|
298
|
+
await client.store.update({ track_price_history: false })
|
|
299
299
|
```
|
|
300
300
|
|
|
301
301
|
```bash cURL
|
|
302
302
|
curl -X PATCH 'https://api.mystore.com/api/v3/admin/store' \
|
|
303
303
|
-H 'X-Spree-API-Key: sk_xxx' \
|
|
304
304
|
-H 'Content-Type: application/json' \
|
|
305
|
-
-d '{ "
|
|
305
|
+
-d '{ "track_price_history": false }'
|
|
306
306
|
```
|
|
307
307
|
|
|
308
308
|
|
|
@@ -39,6 +39,137 @@ end
|
|
|
39
39
|
|
|
40
40
|
Any model inheriting from `Spree.base_class` can declare preferences — the reader and writer methods come with it, so there is nothing to include. The values live in that model's `preferences` column.
|
|
41
41
|
|
|
42
|
+
### Declaring the full type
|
|
43
|
+
|
|
44
|
+
A declaration is also the preference's contract in the API: Spree turns it into a JSON Schema that the API publishes, the SDKs type and every write is checked against. So a declaration states everything a client needs to know about the value:
|
|
45
|
+
|
|
46
|
+
```ruby server/app/models/my_app/promotion/rules/loyalty_tier.rb
|
|
47
|
+
module MyApp
|
|
48
|
+
module Promotion
|
|
49
|
+
module Rules
|
|
50
|
+
class LoyaltyTier < Spree::PromotionRule
|
|
51
|
+
preference :minimum_spend, :money, default: 0
|
|
52
|
+
preference :tier, :string, choices: %w[silver gold platinum], default: 'silver'
|
|
53
|
+
preference :channel_ids, :array, of: :id, model: 'Spree::Channel', default: [],
|
|
54
|
+
scope: ->(rule) { rule.promotion.store.channels }
|
|
55
|
+
preference :country_codes, :array, of: :string, format: :iso_country, default: []
|
|
56
|
+
preference :bonus_amounts, :hash, keys: :currency, values: :money, default: {}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Option | Applies to | What it says |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `of:` | `:array` | The type of each item: `:string`, `:integer`, `:decimal`, `:money`, `:boolean`, `:id` or `:object` |
|
|
66
|
+
| `properties:` | `of: :object` | The fields of each item, e.g. `{ threshold: :money, value: :decimal }` |
|
|
67
|
+
| `keys:` / `values:` | `:hash` | The type of the keys (`:string` or `:currency`) and of the values |
|
|
68
|
+
| `model:` / `scope:` | `of: :id` | The records the ids point at, and the records they must be found in. The API accepts and returns their prefixed ids (`ch_…`); an id of another model, or one outside the scope, is refused |
|
|
69
|
+
| `format:` | `:string`, list items | `:currency`, `:iso_country`, `:timezone`, `:locale`, `:url` or `:color` |
|
|
70
|
+
| `choices:` | any | The fixed set the value must come from; admin forms show a picker |
|
|
71
|
+
|
|
72
|
+
Decimals and amounts travel as exact strings (`"9.99"`), never as numbers. An `:array` without `of:`, a `:hash` without `keys:` and `values:`, or the `:any` type still works in Spree 6.0, with a deprecation warning and a schema that accepts any value. Spree 6.1 refuses them.
|
|
73
|
+
|
|
74
|
+
### Preferences in the API
|
|
75
|
+
|
|
76
|
+
Configurable types — promotion rules and actions, calculators, payment methods, integrations, and price, delivery, commission and order routing rules — send their settings as a `preferences` object next to their `type`. Each family's `/types` endpoint returns the JSON Schema of every registered type's preferences, extensions included, so a client can render a form for a type it has never seen:
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
```typescript Admin SDK
|
|
80
|
+
const { data } = await adminClient.promotionRules.types()
|
|
81
|
+
const itemTotal = data.find((entry) => entry.type === 'item_total')
|
|
82
|
+
itemTotal.schema.properties.amount_min // { type: 'string', format: 'money', default: '100', … }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```bash cURL
|
|
86
|
+
curl 'https://api.mystore.com/api/v3/admin/promotion_rules/types' \
|
|
87
|
+
-H 'X-Spree-API-Key: sk_xxx'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
The SDKs also type the settings of every type Spree ships. `TypedPromotionRule` narrows `preferences` by `type`, and an extension adds its own types to `PromotionRulePreferencesMap` by declaration merging.
|
|
92
|
+
|
|
93
|
+
A write is checked against the schema before anything is saved. An unknown key, a value of the wrong type, a value outside `choices`, or an id of the wrong model or another store is refused with a `422` whose `code` is `invalid_preferences`; `details` names each failing value by its JSON pointer, such as `/preferences/channel_ids/1`. A secret reads back masked; sending the masked value back keeps the stored secret, and `null` clears it.
|
|
94
|
+
|
|
95
|
+
### Exposing a setting in the API
|
|
96
|
+
|
|
97
|
+
Use this when a record has a fixed set of settings that clients edit field by field, as the store, a channel or an import do. A family of types with different settings per type (rules, calculators, payment methods) sends a `preferences` object instead, as described above.
|
|
98
|
+
|
|
99
|
+
The API names such a setting by its plain name (`featured`), never by the `preferred_` method. It takes three declarations, one per layer, and each layer stays in charge of its own job.
|
|
100
|
+
|
|
101
|
+
**1. Expose it on the model.** `exposes_preferences` gives each preference a plain reader, writer and predicate:
|
|
102
|
+
|
|
103
|
+
```ruby server/app/models/spree/brand.rb
|
|
104
|
+
module Spree
|
|
105
|
+
class Brand < Spree.base_class
|
|
106
|
+
preference :featured, :boolean, default: false
|
|
107
|
+
preference :display_name, :string
|
|
108
|
+
|
|
109
|
+
exposes_preferences :featured, :display_name
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
brand.featured # same as brand.preferred_featured
|
|
116
|
+
brand.featured = true # same as brand.preferred_featured = true
|
|
117
|
+
brand.featured? # true
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
It refuses a name the model already uses for something else. Errors on `preferred_featured` are reported to API clients as `featured`.
|
|
121
|
+
|
|
122
|
+
**2. Return it from the serializer.** `preference_attributes` adds every exposed preference, typed from its declaration, so the generated SDK types follow the model. Name some to add only those, or pass `except:` to leave some out:
|
|
123
|
+
|
|
124
|
+
```ruby server/app/serializers/spree/api/v3/admin/brand_serializer.rb
|
|
125
|
+
module Spree
|
|
126
|
+
module Api
|
|
127
|
+
module V3
|
|
128
|
+
module Admin
|
|
129
|
+
class BrandSerializer < Spree::Api::V3::BaseSerializer
|
|
130
|
+
attributes :name, created_at: :iso8601, updated_at: :iso8601
|
|
131
|
+
|
|
132
|
+
preference_attributes Spree::Brand
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**3. Accept it in the controller.** Exposing a setting does not make it writable: the controller decides, as for any attribute (see [permitted attributes](api.md#permitted-attributes)):
|
|
141
|
+
|
|
142
|
+
```ruby server/app/controllers/spree/api/v3/admin/brands_controller.rb
|
|
143
|
+
def resource_permitted_attributes
|
|
144
|
+
[:name, :featured, :display_name]
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The setting is then read and written like a column:
|
|
149
|
+
|
|
150
|
+
**Admin SDK:**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const brand = await adminClient.request('PATCH', `/brands/${brandId}`, {
|
|
154
|
+
body: { featured: true, display_name: 'Wilson' },
|
|
155
|
+
})
|
|
156
|
+
brand.featured // true
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**cURL:**
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
curl -X PATCH 'https://api.mystore.com/api/v3/admin/brands/brand_xxx' \
|
|
163
|
+
-H 'X-Spree-API-Key: sk_xxx' \
|
|
164
|
+
-H 'Content-Type: application/json' \
|
|
165
|
+
-d '{ "featured": true, "display_name": "Wilson" }'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
> **NOTE:** The Seller API keeps its own list of what sellers may write, so exposing a setting to operators never makes it writable by sellers.
|
|
170
|
+
|
|
171
|
+
Strings, integers and booleans are cast the way Rails casts attributes (`"1"`, `"true"` and `"on"` are true; `"42"` is 42). A blank value is `nil` for a `nullable: true` preference, and the type's empty value otherwise.
|
|
172
|
+
|
|
42
173
|
## Accessing Model Preferences
|
|
43
174
|
|
|
44
175
|
Once preferences have been defined for a model, they can be accessed either using the shortcut methods that are generated for each preference or the generic methods that are not specific to a particular preference.
|
|
@@ -146,4 +277,3 @@ Around fifty models use preferences, and the pattern is always the same: a famil
|
|
|
146
277
|
| `Spree::Store`, `Spree::Channel` and `Spree::Market` | Merchant settings — timezone, units, currency behaviour |
|
|
147
278
|
|
|
148
279
|
This is why preferences exist rather than columns: `Spree::Calculator::FlatRate` and `Spree::Calculator::TieredPercent` are rows in one table with entirely different settings, and neither wants a column the other leaves null.
|
|
149
|
-
* `Spree::Theme`
|
|
@@ -73,7 +73,7 @@ dashboard under **Settings → Store**, or through the Admin API:
|
|
|
73
73
|
|
|
74
74
|
```typescript Admin SDK
|
|
75
75
|
await adminClient.store.update({
|
|
76
|
-
|
|
76
|
+
address_requires_phone: false,
|
|
77
77
|
})
|
|
78
78
|
```
|
|
79
79
|
|
|
@@ -81,7 +81,7 @@ await adminClient.store.update({
|
|
|
81
81
|
curl -X PATCH 'https://api.mystore.com/api/v3/admin/store' \
|
|
82
82
|
-H 'X-Spree-API-Key: sk_xxx' \
|
|
83
83
|
-H 'Content-Type: application/json' \
|
|
84
|
-
-d '{ "
|
|
84
|
+
-d '{ "address_requires_phone": false }'
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
|
|
@@ -467,14 +467,14 @@ The gate is enforced by the Store API, not by the storefront, so a storefront ap
|
|
|
467
467
|
```typescript Admin SDK
|
|
468
468
|
// Gate the wholesale channel, and give its shoppers a default agreement
|
|
469
469
|
await adminClient.channels.update('ch_xxx', {
|
|
470
|
-
|
|
471
|
-
|
|
470
|
+
storefront_access: 'login_required',
|
|
471
|
+
guest_checkout: false,
|
|
472
472
|
default_catalog_id: 'cat_xxx',
|
|
473
473
|
})
|
|
474
474
|
|
|
475
475
|
// Or set the fallback for every channel that does not set its own
|
|
476
476
|
await adminClient.store.update({
|
|
477
|
-
|
|
477
|
+
storefront_access: 'prices_hidden',
|
|
478
478
|
})
|
|
479
479
|
```
|
|
480
480
|
|
|
@@ -483,8 +483,8 @@ curl -X PATCH 'https://api.mystore.com/api/v3/admin/channels/ch_xxx' \
|
|
|
483
483
|
-H 'X-Spree-API-Key: sk_xxx' \
|
|
484
484
|
-H 'Content-Type: application/json' \
|
|
485
485
|
-d '{
|
|
486
|
-
"
|
|
487
|
-
"
|
|
486
|
+
"storefront_access": "login_required",
|
|
487
|
+
"guest_checkout": false,
|
|
488
488
|
"default_catalog_id": "cat_xxx"
|
|
489
489
|
}'
|
|
490
490
|
```
|
|
@@ -50,15 +50,15 @@ Three store preferences decide how sellers get paid. All three are writable thro
|
|
|
50
50
|
|
|
51
51
|
| Preference | Default | What it decides |
|
|
52
52
|
|---|---|---|
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
53
|
+
| `payout_provider` | blank | Who moves the money. Blank means core's record-only provider: the ledger is kept correctly and the operator settles by hand. |
|
|
54
|
+
| `default_payouts_schedule_interval` | `monthly` | How often sellers are settled. One of `daily`, `weekly`, `biweekly`, `monthly`, `manual`. A seller can carry its own interval, which wins. |
|
|
55
|
+
| `default_minimum_payout_amount` | `0` | What a seller's balance must reach before a settlement is worth sending. Below it the balance carries to the next period. |
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
```typescript Admin SDK
|
|
59
59
|
await adminClient.store.update({
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
default_payouts_schedule_interval: 'weekly',
|
|
61
|
+
default_minimum_payout_amount: '25.00',
|
|
62
62
|
})
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -67,8 +67,8 @@ curl -X PATCH 'https://api.mystore.com/api/v3/admin/store' \
|
|
|
67
67
|
-H 'X-Spree-API-Key: sk_xxx' \
|
|
68
68
|
-H 'Content-Type: application/json' \
|
|
69
69
|
-d '{
|
|
70
|
-
"
|
|
71
|
-
"
|
|
70
|
+
"default_payouts_schedule_interval": "weekly",
|
|
71
|
+
"default_minimum_payout_amount": "25.00"
|
|
72
72
|
}'
|
|
73
73
|
```
|
|
74
74
|
|
|
@@ -88,18 +88,18 @@ Admin API like the payout ones.
|
|
|
88
88
|
|
|
89
89
|
| Preference | Default | What it decides |
|
|
90
90
|
|---|---|---|
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
91
|
+
| `auto_approve_sellers` | `false` | Admit a seller the moment they finish the checklist, with nobody looking at them. |
|
|
92
|
+
| `auto_approve_seller_products` | `false` | Put a seller's product on sale the moment they submit it. |
|
|
93
|
+
| `send_seller_transactional_emails` | `true` | Whether Spree emails sellers. Turn it off if you front seller communications yourself. |
|
|
94
|
+
| `default_commission_tax_rate` | `0` | Tax on your commission when neither the rate nor the tax provider names one. |
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
```typescript Admin SDK
|
|
98
98
|
await adminClient.store.update({
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
auto_approve_sellers: false,
|
|
100
|
+
auto_approve_seller_products: false,
|
|
101
|
+
send_seller_transactional_emails: true,
|
|
102
|
+
default_commission_tax_rate: '0.23',
|
|
103
103
|
})
|
|
104
104
|
```
|
|
105
105
|
|
|
@@ -108,10 +108,10 @@ curl -X PATCH 'https://api.mystore.com/api/v3/admin/store' \
|
|
|
108
108
|
-H 'X-Spree-API-Key: sk_xxx' \
|
|
109
109
|
-H 'Content-Type: application/json' \
|
|
110
110
|
-d '{
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
111
|
+
"auto_approve_sellers": false,
|
|
112
|
+
"auto_approve_seller_products": false,
|
|
113
|
+
"send_seller_transactional_emails": true,
|
|
114
|
+
"default_commission_tax_rate": "0.23"
|
|
115
115
|
}'
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -402,7 +402,7 @@ curl -X PATCH 'https://api.mystore.com/api/v3/admin/products/prod_xxx/reject' \
|
|
|
402
402
|
|
|
403
403
|
Filter the review queue with a Ransack query on status: `adminClient.products.list({ status_eq: 'proposed' })`.
|
|
404
404
|
|
|
405
|
-
With `
|
|
405
|
+
With `auto_approve_seller_products` on, submitting chains straight into approval. The submission row still gets written and carries an `auto_approved` marker, so a blank reviewer reads as "this store does not review listings" rather than as a lost name.
|
|
406
406
|
|
|
407
407
|
There is no bulk route onto `active` for sellers, and that is deliberate: reaching it is the operator's decision on one listing at a time.
|
|
408
408
|
|
|
@@ -467,7 +467,7 @@ curl 'https://api.mystore.com/api/v3/admin/commission_lines?q[seller_id_eq]=sel_
|
|
|
467
467
|
|
|
468
468
|
There is no write path. Correcting a charge is a reversal, not an edit.
|
|
469
469
|
|
|
470
|
-
In the EU the fee is a separate supply from the sale, so it is taxed separately — that is what `
|
|
470
|
+
In the EU the fee is a separate supply from the sale, so it is taxed separately — that is what `default_commission_tax_rate` from step 1 is for, and a rate or a tax provider can name its own.
|
|
471
471
|
|
|
472
472
|
**Read more:** [Commissions](../core-concepts/commissions.md) covers the four rule types, gross-versus-net, how delivery is treated, currency floors and caps, and commission tax in full. [Commission rates](/user/sellers/commission-rates) is the operator's screen.
|
|
473
473
|
|
|
@@ -584,7 +584,7 @@ curl -X POST 'https://api.mystore.com/api/v3/admin/sellers/sel_xxx/payouts' \
|
|
|
584
584
|
|
|
585
585
|
### Payout providers
|
|
586
586
|
|
|
587
|
-
A provider is a stateless class registered in `Spree.payout_providers` and chosen per store with `
|
|
587
|
+
A provider is a stateless class registered in `Spree.payout_providers` and chosen per store with `payout_provider`.
|
|
588
588
|
|
|
589
589
|
| Provider | Moves money? | Completion |
|
|
590
590
|
|---|---|---|
|
|
@@ -246,7 +246,7 @@ Activate on the whole store:
|
|
|
246
246
|
|
|
247
247
|
```ruby
|
|
248
248
|
Spree::Store.default.update!(
|
|
249
|
-
|
|
249
|
+
order_routing_strategy: 'Acme::Oms::Strategy'
|
|
250
250
|
)
|
|
251
251
|
```
|
|
252
252
|
|
|
@@ -255,11 +255,11 @@ Override on one channel only:
|
|
|
255
255
|
```ruby
|
|
256
256
|
store = Spree::Store.default
|
|
257
257
|
store.channels.find_by(code: 'pos').update!(
|
|
258
|
-
|
|
258
|
+
order_routing_strategy: 'Acme::Oms::Strategy'
|
|
259
259
|
)
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
-
Resolution order:
|
|
262
|
+
Resolution order: the channel's `order_routing_strategy` → the store's `order_routing_strategy` → the default `Strategy::Rules`. Only a value pointing to a **registered** `Strategy::Base` subclass is used; anything unset, unregistered, or invalid is skipped, so a misconfiguration (or a strategy you've since unregistered) falls back to the default instead of breaking checkout.
|
|
263
263
|
|
|
264
264
|
### Step 3: Test the Strategy
|
|
265
265
|
|
|
@@ -506,7 +506,34 @@ If your storefront or webhook receiver reads any of these fields, compare agains
|
|
|
506
506
|
| `payment_source_type` | Store API payment setup sessions, `payment_setup_session.*` webhooks | `Spree::CreditCard` | `credit_card` |
|
|
507
507
|
| `type` | Custom fields on products, variants, categories and collections | `Spree::CustomFields::ShortText` | removed — read `field_type` (`short_text`) |
|
|
508
508
|
|
|
509
|
-
The Admin API follows the same rule everywhere: providers and strategies (`fulfillment_provider: 'manual'`, `
|
|
509
|
+
The Admin API follows the same rule everywhere: providers and strategies (`fulfillment_provider: 'manual'`, `order_routing_strategy: 'rules'`), custom field definition `resource_type` (`product`, `category`), tag `taggable_type`, the owner and originator types on records, the permission subjects `/me` returns (`product`, `category`), 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
|
+
|
|
511
|
+
## Preferences are typed in the API
|
|
512
|
+
|
|
513
|
+
Settings no longer reach the Admin and Seller APIs in the shape of Ruby's preference methods. The Store API is not affected.
|
|
514
|
+
|
|
515
|
+
**Settings use plain names.** Every `preferred_*` field is renamed, on read and on write:
|
|
516
|
+
|
|
517
|
+
| Resource | Before | After |
|
|
518
|
+
|---|---|---|
|
|
519
|
+
| Store (`/admin/store`) | `preferred_timezone`, `preferred_guest_checkout`, `preferred_storefront_url`, … every `preferred_*` field | `timezone`, `guest_checkout`, `storefront_url`, … the same name without the prefix |
|
|
520
|
+
| Channel | `preferred_storefront_access`, `preferred_guest_checkout`, `preferred_order_routing_strategy` | `storefront_access`, `guest_checkout`, `order_routing_strategy` |
|
|
521
|
+
| Import (Admin and Seller) | `preferred_delimiter` | `delimiter` |
|
|
522
|
+
| Seller profile | `preferred_timezone` | `timezone` |
|
|
523
|
+
|
|
524
|
+
`order.preferred_stock_location_id` keeps its name: it is an association, not a setting. Validation errors name the field without the prefix too. Writes still accept the old `preferred_*` names until Spree 6.1, with a deprecation warning in the server log; responses only carry the new names. The store's `storefront_url` is the setting as saved, `null` when unset; `url` is the address customer links use. The store's `default_minimum_payout_amount` and `default_commission_tax_rate` are now exact decimal strings (`"0.23"`) instead of numbers.
|
|
525
|
+
|
|
526
|
+
**`preference_schema` is replaced by `schema`.** Rules, actions, payment methods and integrations no longer carry `preference_schema` on every row. Each family's `/types` endpoint (and the delivery-method and promotion-action calculator lists) returns a `schema` per type instead: the JSON Schema of its `preferences`, with `format`, `enum`, `default` and `x-spree-secret`. Read a row's type, then look up its schema there. `/payment_methods/types` now lists every provider, with `installed: true` on those the store already has, instead of leaving them out.
|
|
527
|
+
|
|
528
|
+
**Writes are validated.** A `preferences` payload with an unknown key, a value of the wrong type, a value outside the declared choices, or an id of another model or another store is refused with a `422` whose `code` is `invalid_preferences`. `details` names each failing value by its JSON pointer (`/preferences/channel_ids/1`, or `/rules/0/preferences/channel_ids/1` inside a list of rules). Before, unknown keys were silently dropped. Send decimals as strings (`"10.0"`) and record ids as prefixed ids (`ch_…`).
|
|
529
|
+
|
|
530
|
+
**Id lists read as prefixed ids.** Preferences listing records, such as a rule's `channel_ids` or `customer_group_ids`, now return prefixed ids rather than database ids, the same form a write accepts.
|
|
531
|
+
|
|
532
|
+
**Delivery methods nest their calculator.** `calculator_type` and `calculator_preferences` are replaced by `calculator: { type, preferences }` on read and write, the shape promotion actions already use. Writes still accept the old two parameters until Spree 6.1, with a deprecation warning; their values follow the new rules (decimals as strings).
|
|
533
|
+
|
|
534
|
+
A preference marked deprecated, such as a flat rate calculator's `minimum_item_total`, is still accepted on write until its removal, with its deprecation warning, but is not read back.
|
|
535
|
+
|
|
536
|
+
**For extension authors:** declare every preference's full type (`of:` on arrays, `keys:` and `values:` on hashes, the `:money` type for amounts, `choices:`, `format:`). An incomplete declaration still works in 6.0 with a deprecation warning, and its schema accepts any value. Replace `normalize_id_preference` with `preference :ids, :array, of: :id, model:, scope:`, and `in:` with `choices:`. See [Model preferences](../customization/model-preferences.md#declaring-the-full-type).
|
|
510
537
|
|
|
511
538
|
## Every event is declared
|
|
512
539
|
|
|
@@ -582,7 +609,7 @@ Saving or touching a product used to bump the `updated_at` of each of its catego
|
|
|
582
609
|
|
|
583
610
|
`Spree::OrderRouting::Strategy::Legacy` — the pre-5.5 escape hatch that delegated straight to `Spree::Stock::Coordinator` and consulted no routing rules — is removed and no longer registered in `Spree.order_routing.strategies`.
|
|
584
611
|
|
|
585
|
-
A store or channel still carrying `
|
|
612
|
+
A store or channel still carrying `order_routing_strategy: 'Spree::OrderRouting::Strategy::Legacy'` **keeps working**: `Order#order_routing_strategy` ignores unregistered classes, logs a warning, and falls back to `Strategy::Rules`. Clear the stale preference to silence the warning — note that saving such a record now fails validation, since the value is no longer in the registry.
|
|
586
613
|
|
|
587
614
|
`Spree::Stock::Coordinator` itself stays — cart fulfillment building, exchanges, and claims still use it.
|
|
588
615
|
|
|
@@ -800,7 +827,7 @@ Other changes that come with it:
|
|
|
800
827
|
|
|
801
828
|
- **Numbers are no longer parsed by locale.** Price, variant, payment, refund, store credit, gift card, discount and fee amounts take a number or a canonical decimal string (`"1234.56"`); any other text raises `Spree::Money::InvalidFormat`, which the API answers with `invalid_money_format`. Before 6.0 they were re-read under the request's language, so a store whose language writes a comma decimal (Dutch, German, French) saved the dashboard's `49.50` as 4950, and an unchanged price grew tenfold on each save. Send canonical decimal strings (`"1234.56"`) and convert localized input in the client. `Spree::LocalizedNumber` still works with a warning until 6.1. If your prices were saved in a comma-decimal store before upgrading, check them.
|
|
802
829
|
- **Rounding follows the currency.** Taxes, percentage discounts, promotion splits and shipping markups round to the currency's own decimal places from the ISO 4217 table: whole yen, three decimals for dinar. Two-decimal currencies are unchanged. Open carts in other currencies may change by less than one minor unit the next time they are recalculated. Placed orders do not change. Spree now gives the Hungarian forint two decimal places, as ISO 4217 does.
|
|
803
|
-
- **Money on the API is written to each currency's decimals.** Every amount in the Store, Admin and Seller APIs, and so in webhook payloads and email variables, is a decimal string with exactly its currency's decimal places: `"10.00"` (was `"10.0"`), `"1000"` in yen (was `"1000.0"`), `"1.500"` in dinar. Unit prices keep up to four decimals. Fields that were JSON numbers are now strings: `cart.order_minimum` and `order_minimum_shortfall`, the product filter price range `min`/`max`, report money metrics and percentages, and the store's `
|
|
830
|
+
- **Money on the API is written to each currency's decimals.** Every amount in the Store, Admin and Seller APIs, and so in webhook payloads and email variables, is a decimal string with exactly its currency's decimal places: `"10.00"` (was `"10.0"`), `"1000"` in yen (was `"1000.0"`), `"1.500"` in dinar. Unit prices keep up to four decimals. Fields that were JSON numbers are now strings: `cart.order_minimum` and `order_minimum_shortfall`, the product filter price range `min`/`max`, report money metrics and percentages, and the store's `default_minimum_payout_amount` and `default_commission_tax_rate`. Clients comparing these strings, or parsing them as numbers, should switch to exact decimal arithmetic; the SDKs export `sumMoney`, `subtractMoney`, `multiplyMoney`, `compareMoney` and `isZeroMoney`.
|
|
804
831
|
- **Admin and Seller writes refuse JSON numbers for money.** Amounts and rates, including those inside `preferences`, must be canonical decimal strings (`"19.99"`). A number, a localized or grouped string, or more decimals than the field holds is answered with `422` and `invalid_money_format`, naming the field.
|
|
805
832
|
- **Amounts may not carry more decimals than they can keep.** Now that the columns hold four decimals, the database no longer rounds away a fraction of a cent, so a payment, refund, store credit, gift card, fee or discount with more decimals than its currency has (`10.0049` dollars) fails validation with `too_many_decimals`. Unit prices and cost prices allow four, tax rates five decimals of the fraction (`rate_percent` `"7.125"`, not `"7.1255"`), and price list percentages three. Round in your own code before saving. A manual order discount's `value` and a commission rate's `value` are read as strictly as other money fields.
|
|
806
833
|
- **The Admin and Seller APIs no longer return `display_*` money fields.** Responses carry the amount (`"29.99"`) with its currency, and the client formats it in the reader's own language. Before 6.0 the server formatted a copy in the request's language, which could disagree with the editable amount beside it. Format with `Intl.NumberFormat` or your platform's equivalent. The Store API, webhook payloads and email templates keep their `display_*` fields, and labels such as `display_name` are unchanged.
|
|
@@ -851,6 +878,9 @@ Before 6.0 a cart was an incomplete order, so extensions written for 5.x (paymen
|
|
|
851
878
|
| `Address#firstname`, `#lastname`, `#zipcode` | `#first_name`, `#last_name`, `#postal_code` (columns renamed) |
|
|
852
879
|
| `Address.normalize_zipcode`, `#normalized_zipcode` | `.normalize_postal_code`, `#normalized_postal_code` |
|
|
853
880
|
| `Order#bill_address_firstname`, `#bill_address_lastname` | `#bill_address_first_name`, `#bill_address_last_name` |
|
|
881
|
+
| `preference … parse_on_set: normalize_id_preference(klass:, scope:)` | `preference …, :array, of: :id, model:, scope:` |
|
|
882
|
+
| `preference …, in: [...]` | `preference …, choices: [...]` |
|
|
883
|
+
| `:array` without `of:`, `:hash` without `keys:`/`values:`, the `:any` type | A complete declaration (see [Model preferences](../customization/model-preferences.md#declaring-the-full-type)) |
|
|
854
884
|
| `OptionType#presentation`, `OptionValue#presentation` | `#label` (column renamed, translations included). Reads and writes on an instance only — `where(presentation:)` and `find_by(presentation:)` raise, because Mobility owns `label` so the bridge cannot be an attribute alias. Query by `label`. |
|
|
855
885
|
| `OptionValue#option_type_presentation` | `#option_type_label` |
|
|
856
886
|
| `q[presentation_cont]` (option type / value filters) | `q[label_cont]` — the Ransack whitelist publishes the column, so the filter key moves with it |
|
|
@@ -24,9 +24,9 @@ const { data: providers } = await adminClient.payoutProviders.list()
|
|
|
24
24
|
// find the row with id 'stripe' — `available` says whether this store can use it
|
|
25
25
|
|
|
26
26
|
await adminClient.store.update({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
payout_provider: 'stripe',
|
|
28
|
+
default_payouts_schedule_interval: 'weekly',
|
|
29
|
+
default_minimum_payout_amount: '50.00',
|
|
30
30
|
})
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -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 '{"
|
|
37
|
+
-d '{"payout_provider": "stripe", "default_payouts_schedule_interval": "weekly", "default_minimum_payout_amount": "50.00"}'
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
|