@spree/docs 0.1.11 → 0.1.13
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/metadata.md +7 -4
- package/dist/api-reference/store.yaml +322 -460
- package/dist/developer/core-concepts/metafields.md +12 -8
- package/dist/developer/customization/dependencies.md +1 -1
- package/dist/developer/customization/metadata.md +26 -15
- package/dist/developer/storefront/nextjs/architecture.md +22 -18
- package/dist/developer/storefront/nextjs/customization.md +3 -3
- package/dist/developer/storefront/nextjs/quickstart.md +1 -1
- package/dist/developer/storefront/nextjs/spree-next-package.md +92 -206
- package/package.json +2 -2
|
@@ -165,18 +165,22 @@ When editing a resource (e.g., a product), metafields appear in a dedicated sect
|
|
|
165
165
|
|
|
166
166
|
## Metafields vs Metadata
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
Spree has two permanent, complementary systems for custom data — **metadata for machines, metafields for humans**. They serve different purposes and are not interchangeable. Neither is going away.
|
|
169
|
+
|
|
170
|
+
| Feature | Metafields | [Metadata](../customization/metadata.md) |
|
|
169
171
|
|---------|-----------|----------|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
172
|
+
| **Purpose** | Merchant-defined structured attributes | Developer escape hatch — integration IDs, sync state |
|
|
173
|
+
| **Schema** | Defined via MetafieldDefinitions | Schemaless JSON — no definition required |
|
|
174
|
+
| **Validation** | Type-specific (text, number, boolean, etc.) | None — accepts any JSON-serializable data |
|
|
175
|
+
| **Visibility** | Configurable (admin-only or public) | Write-only in Store API, readable in Admin API |
|
|
176
|
+
| **Admin UI** | Dedicated management forms | JSON preview |
|
|
177
|
+
| **Data Types** | 6 specific types | Any JSON value |
|
|
178
|
+
| **Organization** | Namespaced (`namespace.key`) | Flat key-value structure |
|
|
179
|
+
| **Queryable** | Via SQL joins, Ransack scopes, search providers | Via JSONB operators (PostgreSQL) |
|
|
176
180
|
|
|
177
181
|
**Use Metafields** when you need type validation, visibility control, admin UI forms, or organized namespacing.
|
|
178
182
|
|
|
179
|
-
**Use Metadata** for external system IDs, tracking attribution, or simple write-and-forget data.
|
|
183
|
+
**Use [Metadata](../customization/metadata.md)** for external system IDs, tracking attribution, syncing with integrations, or simple write-and-forget data that only backend systems need to read.
|
|
180
184
|
|
|
181
185
|
> **WARNING:** Product Properties are deprecated and will be removed in Spree 6.0. For new projects, always use Metafields. For existing projects, plan to migrate using the [migration guide](../upgrades/5.1-to-5.2.md#3-migrate-to-metafields-or-keep-using-properties).
|
|
182
186
|
|
|
@@ -35,7 +35,7 @@ And add the following code to it:
|
|
|
35
35
|
|
|
36
36
|
```ruby
|
|
37
37
|
class MyAddToCartService < Spree::Cart::AddItem
|
|
38
|
-
def call(order:, variant:, quantity: nil,
|
|
38
|
+
def call(order:, variant:, quantity: nil, metadata: {}, options: {})
|
|
39
39
|
ApplicationRecord.transaction do
|
|
40
40
|
run :add_to_line_item
|
|
41
41
|
run Spree.cart_recalculate_service
|
|
@@ -6,6 +6,8 @@ title: Metadata
|
|
|
6
6
|
|
|
7
7
|
Metadata provides simple, unstructured key-value storage on Spree resources — similar to [Stripe's metadata](https://docs.stripe.com/api/metadata). It's ideal for storing integration IDs, tracking data, or any arbitrary information that doesn't need validation or admin UI.
|
|
8
8
|
|
|
9
|
+
Metadata is a **permanent, first-class system** in Spree. It is designed to coexist alongside [Metafields](../core-concepts/metafields.md) (structured, typed, admin-managed). The two systems serve different purposes and are not interchangeable — think of it as **metadata for machines, metafields for humans**.
|
|
10
|
+
|
|
9
11
|
Metadata is **write-only** in the Store API — you can set it when creating or updating resources, but it is never returned in Store API responses. It is visible in Admin API responses for administrative use.
|
|
10
12
|
|
|
11
13
|
> **INFO:** For structured, type-safe custom attributes with admin UI support, use [Metafields](../core-concepts/metafields.md) instead.
|
|
@@ -144,7 +146,7 @@ When there is no metadata, the field is `null`.
|
|
|
144
146
|
|
|
145
147
|
### Reading and writing
|
|
146
148
|
|
|
147
|
-
Every model that includes `Spree::Metadata` has a `metadata` accessor (
|
|
149
|
+
Every model that includes `Spree::Metadata` has a `metadata` accessor. Always use `metadata` in Ruby code — do not call `public_metadata=` or `private_metadata=` directly. (SQL queries still reference the underlying `private_metadata` column name — see querying examples below.)
|
|
148
150
|
|
|
149
151
|
```ruby
|
|
150
152
|
order = Spree::Order.find_by!(number: 'R123456')
|
|
@@ -188,14 +190,17 @@ Metadata updates use **merge semantics** — existing keys are preserved, new ke
|
|
|
188
190
|
|
|
189
191
|
## Metadata vs Metafields
|
|
190
192
|
|
|
193
|
+
Spree has two permanent, complementary systems for custom data. They are not interchangeable and neither is going away.
|
|
194
|
+
|
|
191
195
|
| | Metadata | [Metafields](../core-concepts/metafields.md) |
|
|
192
196
|
|---|---|---|
|
|
193
|
-
| **
|
|
194
|
-
| **
|
|
197
|
+
| **Purpose** | Developer escape hatch — integration data, sync state, ad-hoc flags | Merchant-defined structured attributes with admin UI |
|
|
198
|
+
| **Schema** | Schemaless JSON — no definition required | Defined via MetafieldDefinitions (typed, validated) |
|
|
199
|
+
| **Validation** | None — accepts any JSON-serializable data | Type-specific (text, number, boolean, rich text, JSON) |
|
|
195
200
|
| **Visibility** | Write-only in Store API, readable in Admin API | Configurable (front-end, back-end, both) |
|
|
196
|
-
| **Admin UI** |
|
|
197
|
-
| **
|
|
198
|
-
| **
|
|
201
|
+
| **Admin UI** | JSON preview only | Dedicated management forms |
|
|
202
|
+
| **API pattern** | Stripe-style: `metadata: { key: value }` | Expand-based: `?expand=metafields` |
|
|
203
|
+
| **Queryable** | Via JSONB operators (PostgreSQL) | Via SQL joins, Ransack scopes, search providers |
|
|
199
204
|
|
|
200
205
|
### When to use metadata
|
|
201
206
|
|
|
@@ -203,13 +208,15 @@ Metadata updates use **merge semantics** — existing keys are preserved, new ke
|
|
|
203
208
|
- Tracking attribution data (UTM parameters, referral source)
|
|
204
209
|
- Passing context from the storefront that doesn't need validation
|
|
205
210
|
- Any write-and-forget data that only needs to be read by backend systems
|
|
211
|
+
- Syncing state with external integrations (webhooks, ETL pipelines)
|
|
206
212
|
|
|
207
213
|
### When to use metafields
|
|
208
214
|
|
|
209
|
-
- Custom product specifications shown to customers
|
|
210
|
-
- Admin-managed fields with validation
|
|
211
|
-
- Data that needs to appear in the admin UI
|
|
212
|
-
- Querying/filtering by custom attributes
|
|
215
|
+
- Custom product specifications shown to customers (material, dimensions, certifications)
|
|
216
|
+
- Admin-managed fields with validation and type safety
|
|
217
|
+
- Data that needs to appear in the admin UI with dedicated form inputs
|
|
218
|
+
- Querying/filtering by custom attributes (search facets, product filtering)
|
|
219
|
+
- CSV import/export of structured product data
|
|
213
220
|
|
|
214
221
|
## Supported resources
|
|
215
222
|
|
|
@@ -220,17 +227,21 @@ The Store API currently supports writing metadata on:
|
|
|
220
227
|
- **Carts** — on creation and update (`POST /api/v3/store/carts`, `PATCH /api/v3/store/carts/:id`)
|
|
221
228
|
- **Items** — on create and update (`POST/PATCH /api/v3/store/carts/:id/items`)
|
|
222
229
|
|
|
223
|
-
##
|
|
230
|
+
## Deprecation: public_metadata
|
|
231
|
+
|
|
232
|
+
> **WARNING:** `public_metadata` is deprecated and will be removed in Spree 6.0. Use `metadata` instead.
|
|
233
|
+
|
|
234
|
+
The `public_metadata` column was never exposed in Store API responses and in practice served the same purpose as `private_metadata`. It will be removed in Spree 6.0 and calling `public_metadata=` will emit a deprecation warning.
|
|
224
235
|
|
|
225
|
-
|
|
236
|
+
Always use the single `metadata` accessor for all schemaless key-value storage. If you need data visible to customers on the storefront, use [Metafields](../core-concepts/metafields.md) instead.
|
|
226
237
|
|
|
227
238
|
```ruby
|
|
228
|
-
#
|
|
239
|
+
# Deprecated — will be removed in 6.0
|
|
229
240
|
order.public_metadata = { 'gift_message' => 'Happy Birthday!' }
|
|
230
241
|
|
|
231
|
-
#
|
|
242
|
+
# Use metadata for internal storage
|
|
232
243
|
order.metadata = { 'gift_message' => 'Happy Birthday!' }
|
|
233
244
|
|
|
234
|
-
#
|
|
245
|
+
# Use metafields for customer-visible structured data
|
|
235
246
|
order.set_metafield('custom.gift_message', 'Happy Birthday!')
|
|
236
247
|
```
|
|
@@ -8,14 +8,14 @@ description: Server-first architecture, project structure, and auth flow
|
|
|
8
8
|
The storefront follows a **server-first architecture** where all API calls are made server-side. The Spree API key is never exposed to the browser.
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
Browser → Server Action → @spree/
|
|
12
|
-
(with httpOnly cookies)
|
|
11
|
+
Browser → Server Action → @spree/sdk → Spree API
|
|
12
|
+
(with httpOnly cookies via @spree/next helpers)
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
- **Server Actions** (`src/lib/data/`) —
|
|
15
|
+
- **Server Actions** (`src/lib/data/`) — call `@spree/sdk` directly with auth/cookie helpers from `@spree/next`
|
|
16
16
|
- **httpOnly Cookies** — auth tokens, cart tokens, and locale are stored securely
|
|
17
17
|
- **No Client-Side API Calls** — the Spree API key stays on the server
|
|
18
|
-
- **Auto-Localization** — locale and country are read from cookies
|
|
18
|
+
- **Auto-Localization** — locale and country are read from cookies via `getLocaleOptions()`
|
|
19
19
|
|
|
20
20
|
## Project Structure
|
|
21
21
|
|
|
@@ -59,7 +59,7 @@ src/
|
|
|
59
59
|
└── lib/
|
|
60
60
|
├── analytics/ # GTM integration
|
|
61
61
|
├── constants.ts # App constants
|
|
62
|
-
├── data/ # Server Actions
|
|
62
|
+
├── data/ # Server Actions (call @spree/sdk directly)
|
|
63
63
|
│ ├── addresses.ts # Address CRUD
|
|
64
64
|
│ ├── cart.ts # Cart operations
|
|
65
65
|
│ ├── checkout.ts # Checkout flow
|
|
@@ -71,8 +71,7 @@ src/
|
|
|
71
71
|
│ ├── orders.ts # Order history
|
|
72
72
|
│ ├── payment.ts # Payment processing
|
|
73
73
|
│ ├── products.ts # Product queries
|
|
74
|
-
│ ├──
|
|
75
|
-
│ ├── categories.ts # Categories
|
|
74
|
+
│ ├── categories.ts # Categories
|
|
76
75
|
│ └── utils.ts # Shared helpers (actionResult, withFallback)
|
|
77
76
|
└── utils/ # Client utilities
|
|
78
77
|
├── address.ts # Address formatting
|
|
@@ -85,21 +84,26 @@ src/
|
|
|
85
84
|
## Authentication Flow
|
|
86
85
|
|
|
87
86
|
1. User submits login form
|
|
88
|
-
2. Server action calls `@spree/
|
|
89
|
-
3. JWT token is stored in an httpOnly cookie
|
|
90
|
-
4. Subsequent requests
|
|
87
|
+
2. Server action calls `@spree/sdk` to authenticate
|
|
88
|
+
3. JWT token is stored in an httpOnly cookie via `@spree/next` cookie helpers
|
|
89
|
+
4. Subsequent requests use `withAuthRefresh()` which reads the token from cookies automatically
|
|
91
90
|
5. Token is never accessible to client-side JavaScript
|
|
92
91
|
|
|
93
92
|
```typescript
|
|
94
93
|
// src/lib/data/customer.ts
|
|
95
|
-
import {
|
|
94
|
+
import { getClient, withAuthRefresh, setAccessToken, setRefreshToken } from '@spree/next'
|
|
96
95
|
|
|
97
96
|
export async function login(email: string, password: string) {
|
|
98
|
-
|
|
97
|
+
const result = await getClient().auth.login({ email, password })
|
|
98
|
+
await setAccessToken(result.token)
|
|
99
|
+
await setRefreshToken(result.refresh_token)
|
|
100
|
+
return { success: true, user: result.user }
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
export async function getCustomer() {
|
|
102
|
-
return
|
|
104
|
+
return withAuthRefresh(async (options) => {
|
|
105
|
+
return getClient().customer.get(options) // reads token from cookie
|
|
106
|
+
})
|
|
103
107
|
}
|
|
104
108
|
```
|
|
105
109
|
|
|
@@ -117,17 +121,17 @@ A middleware (`src/proxy.ts`) uses `createSpreeMiddleware` from `@spree/next` to
|
|
|
117
121
|
|
|
118
122
|
## Server Actions
|
|
119
123
|
|
|
120
|
-
All data fetching is done through server actions in `src/lib/data/`. These
|
|
124
|
+
All data fetching is done through server actions in `src/lib/data/`. These call `@spree/sdk` directly, using `@spree/next` helpers for auth and locale:
|
|
121
125
|
|
|
122
126
|
```typescript
|
|
123
|
-
// Products
|
|
127
|
+
// Products — use getLocaleOptions() for locale-aware reads
|
|
124
128
|
import { getProducts, getProduct, getProductFilters } from '@/lib/data/products'
|
|
125
129
|
|
|
126
130
|
const products = await getProducts({ limit: 12 })
|
|
127
131
|
const product = await getProduct('product-slug')
|
|
128
132
|
const filters = await getProductFilters()
|
|
129
133
|
|
|
130
|
-
// Cart
|
|
134
|
+
// Cart — use getCartOptions()/requireCartId() for cart operations
|
|
131
135
|
import { getCart, addToCart, updateCartItem, removeCartItem } from '@/lib/data/cart'
|
|
132
136
|
|
|
133
137
|
const cart = await getCart()
|
|
@@ -135,14 +139,14 @@ await addToCart('var_xxx', 1)
|
|
|
135
139
|
await updateCartItem('li_xxx', 2)
|
|
136
140
|
await removeCartItem('li_xxx')
|
|
137
141
|
|
|
138
|
-
// Authentication
|
|
142
|
+
// Authentication — use withAuthRefresh() for authenticated endpoints
|
|
139
143
|
import { login, register, logout, getCustomer } from '@/lib/data/customer'
|
|
140
144
|
|
|
141
145
|
await login('user@example.com', 'password')
|
|
142
146
|
const customer = await getCustomer()
|
|
143
147
|
await logout()
|
|
144
148
|
|
|
145
|
-
// Addresses
|
|
149
|
+
// Addresses — use withAuthRefresh() for customer data
|
|
146
150
|
import { getAddresses, createAddress, updateAddress, deleteAddress } from '@/lib/data/addresses'
|
|
147
151
|
|
|
148
152
|
const addresses = await getAddresses()
|
|
@@ -111,7 +111,7 @@ To customize API behavior, modify the server actions in `src/lib/data/`. Each fi
|
|
|
111
111
|
| `gift-cards.ts` | Gift card management |
|
|
112
112
|
| `utils.ts` | Shared helpers (error handling, fallbacks) |
|
|
113
113
|
|
|
114
|
-
These server actions
|
|
114
|
+
These server actions call `@spree/sdk` directly, using `@spree/next` helpers for auth cookies and locale resolution. You can add custom logic, caching strategies, or additional transformations as needed.
|
|
115
115
|
|
|
116
116
|
## Adding New Pages
|
|
117
117
|
|
|
@@ -122,10 +122,10 @@ src/app/[country]/[locale]/(storefront)/your-new-page/page.tsx
|
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
|
-
import {
|
|
125
|
+
import { getProducts } from '@/lib/data/products';
|
|
126
126
|
|
|
127
127
|
export default async function YourNewPage() {
|
|
128
|
-
const products = await
|
|
128
|
+
const products = await getProducts({ limit: 6 });
|
|
129
129
|
|
|
130
130
|
return (
|
|
131
131
|
<div>
|
|
@@ -12,7 +12,7 @@ The [Spree Storefront](https://github.com/spree/storefront) is a production-read
|
|
|
12
12
|
- **Tailwind CSS 4** - Utility-first styling
|
|
13
13
|
- **TypeScript 5** - Full type safety
|
|
14
14
|
- **[@spree/sdk](https://github.com/spree/spree/tree/main/packages/sdk)** - Official Spree Commerce SDK
|
|
15
|
-
- **[@spree/next](https://github.com/spree/spree/tree/main/packages/next)** -
|
|
15
|
+
- **[@spree/next](https://github.com/spree/spree/tree/main/packages/next)** - Cookie-based auth, middleware, and webhook helpers
|
|
16
16
|
- **Sentry** - Error tracking and performance monitoring
|
|
17
17
|
|
|
18
18
|
## Features
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "@spree/next Package"
|
|
3
|
-
description: Next.js integration library —
|
|
3
|
+
description: Next.js integration library — cookie-based auth, middleware, and webhook helpers
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
The `@spree/next` package provides a
|
|
6
|
+
The `@spree/next` package provides the server-side plumbing for building a Next.js storefront with `@spree/sdk`: JWT token lifecycle, httpOnly cookie management, locale/country middleware, and webhook verification.
|
|
7
|
+
|
|
8
|
+
Your storefront calls `@spree/sdk` directly for all API operations. `@spree/next` handles the Next.js-specific concerns — cookies, auth refresh, and middleware — so you don't have to.
|
|
7
9
|
|
|
8
10
|
## Installation
|
|
9
11
|
|
|
@@ -42,211 +44,137 @@ initSpreeNext({
|
|
|
42
44
|
});
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
##
|
|
47
|
+
## Architecture
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
`@spree/next` provides four things:
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
1. **`getClient()`** — singleton `@spree/sdk` Client instance (auto-inits from env vars)
|
|
52
|
+
2. **Auth helpers** — `withAuthRefresh()` for JWT lifecycle, cookie read/write for tokens
|
|
53
|
+
3. **Locale resolution** — `getLocaleOptions()` reads country/locale from cookies
|
|
54
|
+
4. **Framework helpers** — `createSpreeMiddleware()` for URL routing, `createWebhookHandler()` for webhooks
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
import { listProducts, getProduct, getProductFilters } from '@spree/next';
|
|
56
|
+
Your storefront's server actions call `@spree/sdk` directly, using these helpers for auth and cookies:
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
```
|
|
59
|
+
Server Action → getClient().products.list(params, localeOptions)
|
|
60
|
+
Server Action → withAuthRefresh(fn) → getClient().customer.get(options)
|
|
61
|
+
Server Action → getCartOptions() → getClient().carts.items.create(...)
|
|
57
62
|
```
|
|
58
63
|
|
|
59
|
-
|
|
64
|
+
## Auth Helpers
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
import { listCategories, getCategory, listCategoryProducts } from '@spree/next';
|
|
66
|
+
### `withAuthRefresh(fn)`
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
const category = await getCategory('clothing/shirts');
|
|
66
|
-
const products = await listCategoryProducts('clothing/shirts', { limit: 12 });
|
|
67
|
-
```
|
|
68
|
+
Wraps an authenticated SDK call with automatic token management:
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
1. Reads JWT from cookie
|
|
71
|
+
2. Proactively refreshes if expiring within 5 minutes
|
|
72
|
+
3. Executes your function with `{ token }` options
|
|
73
|
+
4. On 401: refreshes token and retries once
|
|
74
|
+
5. On refresh failure: clears cookies and throws
|
|
70
75
|
|
|
71
76
|
```typescript
|
|
72
|
-
|
|
77
|
+
'use server';
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
const countries = await listCountries();
|
|
76
|
-
const usa = await getCountry('US');
|
|
77
|
-
const currencies = await listCurrencies();
|
|
78
|
-
const locales = await listLocales();
|
|
79
|
-
```
|
|
79
|
+
import { getClient, withAuthRefresh } from '@spree/next';
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
export async function getCustomer() {
|
|
82
|
+
return withAuthRefresh(async (options) => {
|
|
83
|
+
return getClient().customer.get(options);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const products = await listProducts({ limit: 12 });
|
|
88
|
-
const categories = await listCategories({ depth_eq: 1 });
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<div>
|
|
92
|
-
{products.data.map((product) => (
|
|
93
|
-
<div key={product.id}>{product.name}</div>
|
|
94
|
-
))}
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
87
|
+
export async function listAddresses() {
|
|
88
|
+
return withAuthRefresh(async (options) => {
|
|
89
|
+
return getClient().customer.addresses.list(undefined, options);
|
|
90
|
+
});
|
|
97
91
|
}
|
|
98
92
|
```
|
|
99
93
|
|
|
100
|
-
|
|
94
|
+
### `getAuthOptions()`
|
|
101
95
|
|
|
102
|
-
|
|
96
|
+
Lower-level helper that returns `{ token }` or `{}` with proactive refresh. Use when you need the token but want to handle errors yourself.
|
|
103
97
|
|
|
104
|
-
|
|
98
|
+
## Cookie Management
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
import { getCart, getOrCreateCart, addItem, updateItem, removeItem, clearCart, associateCart } from '@spree/next';
|
|
108
|
-
|
|
109
|
-
const cart = await getCart(); // null if no cart
|
|
110
|
-
const cart = await getOrCreateCart(); // creates if needed
|
|
111
|
-
await addItem(variantId, quantity);
|
|
112
|
-
await updateItem(lineItemId, { quantity: 2 });
|
|
113
|
-
await removeItem(lineItemId);
|
|
114
|
-
await clearCart();
|
|
115
|
-
await associateCart(); // link guest cart to logged-in user
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Checkout
|
|
119
|
-
|
|
120
|
-
Checkout functions use the implicit cart resolved from cookies. No `orderId` is needed.
|
|
100
|
+
All cookie helpers are async (they use Next.js `cookies()` API):
|
|
121
101
|
|
|
122
102
|
```typescript
|
|
123
103
|
import {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
104
|
+
// Cart
|
|
105
|
+
getCartToken, getCartId, setCartCookies, clearCartCookies,
|
|
106
|
+
getCartOptions, // { spreeToken, token } for cart SDK calls
|
|
107
|
+
requireCartId, // throws if no cart
|
|
108
|
+
|
|
109
|
+
// Auth
|
|
110
|
+
getAccessToken, setAccessToken, clearAccessToken,
|
|
111
|
+
getRefreshToken, setRefreshToken, clearRefreshToken,
|
|
132
112
|
} from '@spree/next';
|
|
133
|
-
|
|
134
|
-
const checkout = await getCheckout();
|
|
135
|
-
await updateCheckout({ shipping_address: { ... }, billing_address: { ... } });
|
|
136
|
-
await selectDeliveryRate(fulfillmentId, deliveryRateId);
|
|
137
|
-
await applyDiscountCode('SAVE20');
|
|
138
|
-
await removeDiscountCode('SAVE20');
|
|
139
|
-
await applyGiftCard('GC-ABCD-1234');
|
|
140
|
-
await removeGiftCard('gc_abc123');
|
|
141
|
-
await complete();
|
|
142
113
|
```
|
|
143
114
|
|
|
144
|
-
###
|
|
115
|
+
### Cart operations pattern
|
|
145
116
|
|
|
146
117
|
```typescript
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
await login(email, password);
|
|
150
|
-
await register(email, password, passwordConfirmation);
|
|
151
|
-
await logout();
|
|
152
|
-
const customer = await getCustomer(); // null if not logged in
|
|
153
|
-
await updateCustomer({ first_name: 'John' });
|
|
154
|
-
```
|
|
118
|
+
'use server';
|
|
155
119
|
|
|
156
|
-
|
|
120
|
+
import { getClient, getCartOptions, requireCartId } from '@spree/next';
|
|
157
121
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await createAddress({ first_name: 'John', address1: '123 Main St', ... });
|
|
164
|
-
await updateAddress(addressId, { city: 'Brooklyn' });
|
|
165
|
-
await deleteAddress(addressId);
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Orders
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import { listOrders, getOrder } from '@spree/next';
|
|
172
|
-
|
|
173
|
-
const orders = await listOrders();
|
|
174
|
-
const order = await getOrder(orderId);
|
|
122
|
+
export async function updateCartItem(lineItemId: string, quantity: number) {
|
|
123
|
+
const options = await getCartOptions();
|
|
124
|
+
const cartId = await requireCartId();
|
|
125
|
+
return getClient().carts.items.update(cartId, lineItemId, { quantity }, options);
|
|
126
|
+
}
|
|
175
127
|
```
|
|
176
128
|
|
|
177
|
-
###
|
|
178
|
-
|
|
179
|
-
Payment session functions use the implicit cart. No `orderId` is needed.
|
|
129
|
+
### Auth operations pattern
|
|
180
130
|
|
|
181
131
|
```typescript
|
|
182
|
-
|
|
183
|
-
createPaymentSession,
|
|
184
|
-
getPaymentSession,
|
|
185
|
-
updatePaymentSession,
|
|
186
|
-
completePaymentSession,
|
|
187
|
-
} from '@spree/next';
|
|
188
|
-
|
|
189
|
-
const session = await createPaymentSession({ payment_method_id: 'pm_123' });
|
|
190
|
-
|
|
191
|
-
// Access provider data (e.g., Stripe client secret)
|
|
192
|
-
const clientSecret = session.external_data.client_secret;
|
|
193
|
-
|
|
194
|
-
const current = await getPaymentSession(sessionId);
|
|
195
|
-
await updatePaymentSession(sessionId, { amount: '50.00' });
|
|
196
|
-
await completePaymentSession(sessionId, { session_result: 'success' });
|
|
197
|
-
```
|
|
132
|
+
'use server';
|
|
198
133
|
|
|
199
|
-
|
|
134
|
+
import { getClient, setAccessToken, setRefreshToken, getCartToken, getCartId } from '@spree/next';
|
|
200
135
|
|
|
201
|
-
|
|
136
|
+
export async function login(email: string, password: string) {
|
|
137
|
+
const result = await getClient().auth.login({ email, password });
|
|
138
|
+
await setAccessToken(result.token);
|
|
139
|
+
await setRefreshToken(result.refresh_token);
|
|
202
140
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
141
|
+
// Associate guest cart if one exists
|
|
142
|
+
const cartToken = await getCartToken();
|
|
143
|
+
const cartId = await getCartId();
|
|
144
|
+
if (cartToken && cartId) {
|
|
145
|
+
await getClient().carts.associate(cartId, {
|
|
146
|
+
token: result.token,
|
|
147
|
+
spreeToken: cartToken,
|
|
148
|
+
}).catch(() => {}); // non-fatal
|
|
149
|
+
}
|
|
209
150
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
await completePaymentSetupSession(sessionId, { session_result: 'success' });
|
|
151
|
+
return { success: true, user: result.user };
|
|
152
|
+
}
|
|
213
153
|
```
|
|
214
154
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
import { listCreditCards, deleteCreditCard } from '@spree/next';
|
|
219
|
-
import { listGiftCards, getGiftCard } from '@spree/next';
|
|
155
|
+
## Locale Resolution
|
|
220
156
|
|
|
221
|
-
|
|
222
|
-
await deleteCreditCard(cardId);
|
|
223
|
-
|
|
224
|
-
const giftCards = await listGiftCards();
|
|
225
|
-
const giftCard = await getGiftCard(giftCardId);
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Using Server Actions in Forms
|
|
157
|
+
`getLocaleOptions()` reads country and locale from cookies (set by middleware or explicitly). All data-fetching server actions should use this:
|
|
229
158
|
|
|
230
159
|
```typescript
|
|
231
|
-
|
|
160
|
+
'use server';
|
|
232
161
|
|
|
233
|
-
|
|
234
|
-
const cart = await getCart();
|
|
162
|
+
import { getClient, getLocaleOptions } from '@spree/next';
|
|
235
163
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
164
|
+
export async function getProducts(params?: { limit?: number }) {
|
|
165
|
+
const options = await getLocaleOptions();
|
|
166
|
+
return getClient().products.list(params, options);
|
|
167
|
+
}
|
|
240
168
|
|
|
241
|
-
|
|
169
|
+
export async function getCategory(idOrPermalink: string) {
|
|
170
|
+
const options = await getLocaleOptions();
|
|
171
|
+
return getClient().categories.get(idOrPermalink, undefined, options);
|
|
242
172
|
}
|
|
243
173
|
```
|
|
244
174
|
|
|
245
|
-
##
|
|
246
|
-
|
|
247
|
-
### Automatic (recommended)
|
|
175
|
+
## Middleware
|
|
248
176
|
|
|
249
|
-
|
|
177
|
+
Handles URL-based country/locale routing. Detects country from cookies, geo headers (Vercel/Cloudflare), or defaults, then redirects to `/{country}/{locale}/...`:
|
|
250
178
|
|
|
251
179
|
```typescript
|
|
252
180
|
// middleware.ts
|
|
@@ -262,31 +190,6 @@ export const config = {
|
|
|
262
190
|
};
|
|
263
191
|
```
|
|
264
192
|
|
|
265
|
-
Then data functions work without any locale arguments:
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
// Locale/country auto-read from cookies — no options needed
|
|
269
|
-
const products = await listProducts({ limit: 10 });
|
|
270
|
-
const category = await getCategory('clothing/shirts');
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
Use the `setLocale` server action in country/language switchers:
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
import { setLocale } from '@spree/next';
|
|
277
|
-
|
|
278
|
-
await setLocale({ country: 'de', locale: 'de' });
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### Manual override
|
|
282
|
-
|
|
283
|
-
You can still pass locale options explicitly — they override the auto-detected values:
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
const products = await listProducts({ limit: 10 }, { locale: 'fr', country: 'FR' });
|
|
287
|
-
const category = await getCategory('clothing/shirts', {}, { locale: 'de', country: 'DE' });
|
|
288
|
-
```
|
|
289
|
-
|
|
290
193
|
## Webhooks
|
|
291
194
|
|
|
292
195
|
Handle Spree webhook events in your Next.js app with `@spree/next/webhooks`:
|
|
@@ -331,31 +234,14 @@ For more details, see [Webhooks](../../core-concepts/webhooks.md).
|
|
|
331
234
|
|
|
332
235
|
## TypeScript
|
|
333
236
|
|
|
334
|
-
|
|
237
|
+
Import types directly from `@spree/sdk`:
|
|
335
238
|
|
|
336
239
|
```typescript
|
|
337
240
|
import type {
|
|
338
|
-
Product,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
Currency,
|
|
345
|
-
Locale,
|
|
346
|
-
Address,
|
|
347
|
-
Customer,
|
|
348
|
-
CreditCard,
|
|
349
|
-
GiftCard,
|
|
350
|
-
Fulfillment,
|
|
351
|
-
DeliveryRate,
|
|
352
|
-
Payment,
|
|
353
|
-
PaymentMethod,
|
|
354
|
-
PaymentSession,
|
|
355
|
-
PaymentSetupSession,
|
|
356
|
-
PaginatedResponse,
|
|
357
|
-
ProductFiltersResponse,
|
|
358
|
-
AddressParams,
|
|
359
|
-
SpreeError,
|
|
360
|
-
} from '@spree/next';
|
|
241
|
+
Product, Order, Cart, LineItem, Variant,
|
|
242
|
+
Category, Country, Currency, Address, Customer,
|
|
243
|
+
CreditCard, GiftCard, Fulfillment, DeliveryRate,
|
|
244
|
+
Payment, PaymentSession, PaginatedResponse,
|
|
245
|
+
AddressParams, SpreeError,
|
|
246
|
+
} from '@spree/sdk';
|
|
361
247
|
```
|