@spree/docs 0.1.298 → 0.1.300
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 +2 -6
- 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/customization/navigation.md +3 -3
- package/dist/developer/dashboard/customization/permissions.md +8 -8
- package/dist/developer/dashboard/customization/routes.md +1 -1
- package/dist/developer/dashboard/customization/slots.md +1 -1
- package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
- package/dist/developer/dashboard/recipes/page-action-button.md +2 -2
- package/dist/developer/dashboard/recipes/sidebar-widget.md +1 -1
- package/dist/developer/dashboard/slots-catalog.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
|
@@ -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
|
|
@@ -11738,7 +11738,7 @@ components:
|
|
|
11738
11738
|
type: array
|
|
11739
11739
|
items:
|
|
11740
11740
|
type: string
|
|
11741
|
-
description: Subject
|
|
11741
|
+
description: Subject short names, e.g. ["product"] or ["all"]
|
|
11742
11742
|
has_conditions:
|
|
11743
11743
|
type: boolean
|
|
11744
11744
|
description: True if the server-side rule has per-record conditions. The
|
|
@@ -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
|
|
|
@@ -22,7 +22,7 @@ nav.add({
|
|
|
22
22
|
path: '/analytics', // prefixed with /$storeId at render time
|
|
23
23
|
icon: BarChartIcon,
|
|
24
24
|
position: 650,
|
|
25
|
-
subject: '
|
|
25
|
+
subject: 'order', // optional permission subject — hides item without read permission
|
|
26
26
|
})
|
|
27
27
|
```
|
|
28
28
|
|
|
@@ -70,7 +70,7 @@ nav.addChild('products', {
|
|
|
70
70
|
key: 'products.brands',
|
|
71
71
|
label: 'Brands',
|
|
72
72
|
path: '/products/brands',
|
|
73
|
-
subject: '
|
|
73
|
+
subject: 'brand',
|
|
74
74
|
})
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -177,7 +177,7 @@ settingsNav.add({
|
|
|
177
177
|
path: '/integrations/stripe-tax', // prefixed with /$storeId/settings
|
|
178
178
|
group: 'integrations',
|
|
179
179
|
position: 100,
|
|
180
|
-
subject: '
|
|
180
|
+
subject: 'tax_rate',
|
|
181
181
|
})
|
|
182
182
|
```
|
|
183
183
|
|
|
@@ -33,10 +33,10 @@ interface Permissions {
|
|
|
33
33
|
It mirrors the backend ability at the **class level**:
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
permissions.can('read', '
|
|
36
|
+
permissions.can('read', 'order')
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Subjects are
|
|
39
|
+
Subjects are short names: the model's class name without its namespace, underscored (`'order'`, `'product'`, or `'report'` for your own `MyApp::Report`). Use the `Subject` constants from `@spree/dashboard-core` for Spree's own models. There is no client-side record-level check — when a rule is conditional on record attributes, `isConditional` returns `true` and the API is the arbiter: render the control and handle a possible 403.
|
|
40
40
|
|
|
41
41
|
## `subject` shortcut
|
|
42
42
|
|
|
@@ -47,7 +47,7 @@ nav.add({
|
|
|
47
47
|
key: 'reports',
|
|
48
48
|
label: 'Reports',
|
|
49
49
|
path: '/reports',
|
|
50
|
-
subject: '
|
|
50
|
+
subject: 'order', // hides nav item without read:order
|
|
51
51
|
})
|
|
52
52
|
```
|
|
53
53
|
|
|
@@ -58,7 +58,7 @@ routes: [{
|
|
|
58
58
|
key: 'reports',
|
|
59
59
|
path: '/reports',
|
|
60
60
|
component: ReportsPage,
|
|
61
|
-
subject: '
|
|
61
|
+
subject: 'order',
|
|
62
62
|
}],
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -76,7 +76,7 @@ nav.add({
|
|
|
76
76
|
label: 'Reports',
|
|
77
77
|
path: '/reports',
|
|
78
78
|
if: ({ permissions, store }) =>
|
|
79
|
-
permissions.can('read', '
|
|
79
|
+
permissions.can('read', 'order') &&
|
|
80
80
|
!!(store as Store | null)?.setup_tasks?.every((task) => task.done),
|
|
81
81
|
})
|
|
82
82
|
```
|
|
@@ -94,18 +94,18 @@ import { usePermissions } from '@spree/dashboard'
|
|
|
94
94
|
|
|
95
95
|
function ReportsPage() {
|
|
96
96
|
const { permissions } = usePermissions()
|
|
97
|
-
if (!permissions.can('read', '
|
|
97
|
+
if (!permissions.can('read', 'order')) {
|
|
98
98
|
return <Forbidden />
|
|
99
99
|
}
|
|
100
100
|
return <ReportsList />
|
|
101
101
|
}
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
The same `permissions` object backs the nav registry's `if` predicate, so you can move logic between the two without changing behaviour. For declarative gating, `<Can I="update" a="
|
|
104
|
+
The same `permissions` object backs the nav registry's `if` predicate, so you can move logic between the two without changing behaviour. For declarative gating, `<Can I="update" a="order">…</Can>` (also from `@spree/dashboard-core`) renders children only when the check passes.
|
|
105
105
|
|
|
106
106
|
## Custom permissions
|
|
107
107
|
|
|
108
|
-
If your customization introduces a new model on the backend, register it as a permission catalog scope there (`Spree.permissions.register_scope` in your engine's initializer). Its keys then appear in the role editor and the API-key scope picker, and any role granted them makes `permissions.can('read', '
|
|
108
|
+
If your customization introduces a new model on the backend, register it as a permission catalog scope there (`Spree.permissions.register_scope` in your engine's initializer). Its keys then appear in the role editor and the API-key scope picker, and any role granted them makes `permissions.can('read', 'report')` resolve in the dashboard exactly like a first-party check — the abilities ship with the current-user response (`GET /api/v3/admin/me`) at sign-in, alongside `permission_keys`, the flat key list the role editor grants from.
|
|
109
109
|
|
|
110
110
|
## Reference
|
|
111
111
|
|
|
@@ -68,7 +68,7 @@ import { usePermissions } from '@spree/dashboard'
|
|
|
68
68
|
|
|
69
69
|
function AdminOnlyMenuItem() {
|
|
70
70
|
const { permissions } = usePermissions()
|
|
71
|
-
if (!permissions.can('manage', '
|
|
71
|
+
if (!permissions.can('manage', 'customer')) return null
|
|
72
72
|
return <DropdownMenuItem>…</DropdownMenuItem>
|
|
73
73
|
}
|
|
74
74
|
```
|
|
@@ -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",
|
|
@@ -33,7 +33,7 @@ export function SendInvoiceButton({ resource }: Props) {
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
// Only show to users who can act, and only on completed orders.
|
|
36
|
-
if (!permissions.can('update', '
|
|
36
|
+
if (!permissions.can('update', 'order')) return null
|
|
37
37
|
if (resource.status !== 'placed') return null
|
|
38
38
|
|
|
39
39
|
return (
|
|
@@ -95,7 +95,7 @@ export function SyncToErpItem({ resource }: Props) {
|
|
|
95
95
|
successMessage: 'Synced',
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
-
if (!permissions.can('manage', '
|
|
98
|
+
if (!permissions.can('manage', 'order')) return null
|
|
99
99
|
|
|
100
100
|
return (
|
|
101
101
|
<DropdownMenuItem
|
|
@@ -42,7 +42,7 @@ interface Props {
|
|
|
42
42
|
export function LoyaltyStatusCard({ customer }: Props) {
|
|
43
43
|
const { storeId } = useStore()
|
|
44
44
|
const { permissions } = usePermissions()
|
|
45
|
-
const canRead = permissions.can('read', '
|
|
45
|
+
const canRead = permissions.can('read', 'loyalty_record')
|
|
46
46
|
const { data, isLoading } = useQuery({
|
|
47
47
|
queryKey: ['loyalty', storeId, customer.id],
|
|
48
48
|
queryFn: () =>
|
|
@@ -44,7 +44,7 @@ import { usePermissions, useStore } from '@spree/dashboard'
|
|
|
44
44
|
function MyWidget({ product }: { product: Product }) {
|
|
45
45
|
const { permissions } = usePermissions()
|
|
46
46
|
const { store } = useStore()
|
|
47
|
-
if (!permissions.can('read', '
|
|
47
|
+
if (!permissions.can('read', 'loyalty_record')) return null
|
|
48
48
|
// ...
|
|
49
49
|
}
|
|
50
50
|
```
|
|
@@ -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, 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
|
+
|
|
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
|
|