@spree/docs 0.1.57 → 0.1.58
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.
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Migrating from Storefront API v2"
|
|
3
|
+
sidebarTitle: "Migrating from v2"
|
|
4
|
+
description: "Map every Storefront API v2 endpoint, concept, and SDK call to its Store API v3 equivalent."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
The Store API is the successor to the [legacy Storefront API](../v2/introduction.md). It exposes the same surface — products, carts, checkout, customers, wishlists — but with a new transport (flat JSON instead of JSON:API), a new [fully-typed TypeScript SDK](../../developer/sdk/quickstart.md), and resource-oriented routes that line up with the new [Admin API](../admin-api.md). It's faster, safer and easier to work with.
|
|
8
|
+
|
|
9
|
+
This guide walks you through the differences and gives you a one-to-one mapping you can grep against during a migration.
|
|
10
|
+
|
|
11
|
+
> **NOTE:** API v2 is available via [spree_legacy_api_v2](https://github.com/spree/spree_legacy_api_v2) gem and will work with Spree 5. However new features such as [Markets](../../developer/core-concepts/markets.md) or [new Pricing engine](../../developer/core-concepts/pricing.md) are only available in API v3.
|
|
12
|
+
|
|
13
|
+
## TL;DR
|
|
14
|
+
|
|
15
|
+
| | **Storefront API v2** | **Store API v3** |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Path prefix | `/api/v2/storefront/*` | `/api/v3/store/*` |
|
|
18
|
+
| Response format | [JSON:API](https://jsonapi.org/) (`data` / `attributes` / `relationships` / `included`) | Flat JSON (attributes inlined on the resource) |
|
|
19
|
+
| ID format | Numeric (`123`) or slug | Prefixed string (`prod_86Rf07xd4z`, `cart_k5nR8xLq`) |
|
|
20
|
+
| Routing style | Action-based (`/cart/add_item`, `/checkout/next`) | RESTful resources (`POST /carts/:id/items`, no checkout state machine) |
|
|
21
|
+
| Filtering | `filter[...]` params, fixed per endpoint | [Ransack](querying.md) via `q[...]`, plus per-endpoint scopes |
|
|
22
|
+
| Including associations | `?include=variants,images` | `?expand=variants,media` (single param, dot-nested up to 4 levels) |
|
|
23
|
+
| API key | *(none — fully public)* | `X-Spree-Api-Key: pk_xxx` *(publishable key, required on every request)* |
|
|
24
|
+
| Cart token header | `X-Spree-Order-Token` | `X-Spree-Token` |
|
|
25
|
+
| Customer auth | `Authorization: Bearer <oauth_token>` (OAuth via `/spree_oauth/token`) | `Authorization: Bearer <jwt>` (JWT via `/auth/login`) |
|
|
26
|
+
| Checkout model | Step state machine (`address → delivery → payment → confirm → complete`) | Stateless cart + nested resources (addresses, payments, payment sessions) |
|
|
27
|
+
| SDK package | [`@spree/storefront-api-v2-sdk`](https://www.npmjs.com/package/@spree/storefront-api-v2-sdk) *(deprecated)* | [`@spree/sdk`](https://www.npmjs.com/package/@spree/sdk) |
|
|
28
|
+
| SDK factory | `makeClient({ host })` | `createClient({ baseUrl, publishableKey })` |
|
|
29
|
+
| SDK response | `Result<Error, Response>` with `.success()` / `.fail()` | Returns the resource directly; throws `SpreeError` on failure |
|
|
30
|
+
| Type safety | Hand-written interfaces | Auto-generated TypeScript types + Zod runtime validators |
|
|
31
|
+
|
|
32
|
+
## Mental model: what changed and why
|
|
33
|
+
|
|
34
|
+
### v2 — JSON:API with action endpoints
|
|
35
|
+
|
|
36
|
+
Storefront v2 was modelled on JSON:API. Every response had `data`/`attributes`/`relationships`/`included`, and most non-`GET` calls invoked a named action on a singleton resource (`/cart/add_item`, `/checkout/next`, `/checkout/select_shipping_method`). The current cart was implicit — the server resolved it from the `X-Spree-Order-Token` header. Checkout was a five-step state machine; the SPA's job was to drive `PATCH /checkout/next` until the order reached `complete`.
|
|
37
|
+
|
|
38
|
+
This made cart and checkout calls easy to write but hard to reason about. The same payload could land you in different checkout states depending on which step the order happened to be on, and refactoring the front-end meant knowing which actions transitioned which states.
|
|
39
|
+
|
|
40
|
+
### v3 — REST with explicit resources
|
|
41
|
+
|
|
42
|
+
Store API v3 collapses checkout into the cart. There is no checkout state machine, no `/checkout/next`, no `/checkout/advance`. Instead:
|
|
43
|
+
|
|
44
|
+
- The cart is a real resource with a stable prefixed ID (`cart_…`). You `PATCH /carts/:id` to attach an email or addresses, and you `POST /carts/:id/items` to add line items.
|
|
45
|
+
- Delivery rates and payment methods are not separate endpoints — fulfillments are nested under the cart (`PATCH /carts/:id/fulfillments/:fid` to pick a delivery rate), and payments are nested under the cart (`POST /carts/:id/payments` for non-session methods, `POST /carts/:id/payment_sessions` for Stripe/PayPal/Adyen).
|
|
46
|
+
- Completing checkout is a single explicit call: `POST /carts/:id/complete`. It returns the resulting `Order`.
|
|
47
|
+
|
|
48
|
+
The cart can be created, edited, abandoned, completed, and associated with a user from a single URL. No step transitions, no implicit current cart. This makes it possible to ship Shopify-style one-page checkout without fighting the API.
|
|
49
|
+
|
|
50
|
+
### Why JSON:API is gone
|
|
51
|
+
|
|
52
|
+
JSON:API's strengths — sparse fieldsets, relationship graphs, normalized payloads — are real, but most storefront clients flattened the response anyway. The cost was a noisy, hard-to-cache wire format and a two-step deserialization on every call. v3 returns the resource directly with associations inlined when `expand` is requested:
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
```json v2 (JSON:API)
|
|
56
|
+
{
|
|
57
|
+
"data": {
|
|
58
|
+
"id": "96",
|
|
59
|
+
"type": "product",
|
|
60
|
+
"attributes": {
|
|
61
|
+
"name": "Bomber Jacket",
|
|
62
|
+
"slug": "bomber-jacket",
|
|
63
|
+
"available_on": "2021-10-02T11:02:29.288Z",
|
|
64
|
+
"purchasable": true,
|
|
65
|
+
"in_stock": true,
|
|
66
|
+
"currency": "USD",
|
|
67
|
+
"price": "38.99",
|
|
68
|
+
"display_price": "$38.99",
|
|
69
|
+
"compare_at_price": null,
|
|
70
|
+
"display_compare_at_price": null
|
|
71
|
+
},
|
|
72
|
+
"relationships": {
|
|
73
|
+
"variants": { "data": [{ "id": "212", "type": "variant" }] },
|
|
74
|
+
"default_variant": { "data": { "id": "212", "type": "variant" } }
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"included": [
|
|
78
|
+
{
|
|
79
|
+
"id": "212",
|
|
80
|
+
"type": "variant",
|
|
81
|
+
"attributes": { "sku": "JacketsandCoats_bomberjacket_38.99", "price": "38.99" }
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```json v3 (Flat JSON, with ?expand=variants)
|
|
88
|
+
{
|
|
89
|
+
"id": "prod_UkLWZg9DAJ",
|
|
90
|
+
"name": "Bomber Jacket",
|
|
91
|
+
"slug": "bomber-jacket",
|
|
92
|
+
"available_on": "2025-05-13T22:27:22.136Z",
|
|
93
|
+
"purchasable": true,
|
|
94
|
+
"in_stock": true,
|
|
95
|
+
"description": "Dolorem nulla odit nostrum placeat...",
|
|
96
|
+
"description_html": "<p>Dolorem nulla odit nostrum placeat...</p>",
|
|
97
|
+
"default_variant_id": "variant_gbHJdmfrXB",
|
|
98
|
+
"thumbnail_url": null,
|
|
99
|
+
"tags": [],
|
|
100
|
+
"price": {
|
|
101
|
+
"id": "price_gbHJdmfrXB",
|
|
102
|
+
"amount": "38.99",
|
|
103
|
+
"amount_in_cents": 3899,
|
|
104
|
+
"currency": "USD",
|
|
105
|
+
"display_amount": "$38.99",
|
|
106
|
+
"compare_at_amount": null,
|
|
107
|
+
"compare_at_amount_in_cents": null,
|
|
108
|
+
"display_compare_at_amount": null
|
|
109
|
+
},
|
|
110
|
+
"original_price": null,
|
|
111
|
+
"variants": [
|
|
112
|
+
{
|
|
113
|
+
"id": "variant_gbHJdmfrXB",
|
|
114
|
+
"product_id": "prod_UkLWZg9DAJ",
|
|
115
|
+
"sku": "JacketsandCoats_bomberjacket_38.99",
|
|
116
|
+
"options_text": "Size: M",
|
|
117
|
+
"purchasable": true,
|
|
118
|
+
"in_stock": true,
|
|
119
|
+
"price": {
|
|
120
|
+
"amount": "38.99",
|
|
121
|
+
"amount_in_cents": 3899,
|
|
122
|
+
"currency": "USD",
|
|
123
|
+
"display_amount": "$38.99"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
You can still ask for sparse fields (`?fields=name,price`), and you still control association depth (`?expand=variants.media`), but you no longer have to walk `included` to assemble the response. See [Querying](querying.md) and [Relations](relations.md).
|
|
132
|
+
|
|
133
|
+
### Prefixed IDs everywhere
|
|
134
|
+
|
|
135
|
+
Every v3 resource has a Stripe-style prefixed ID — `prod_…`, `variant_…`, `cart_…`, `ord_…`, `addr_…`. The prefix is part of the public surface: pass it back exactly as received, never strip the prefix or cast it to an integer. (Internally, IDs are still numeric, but the API only ever exposes the prefixed form.) See the [Introduction](introduction.md).
|
|
136
|
+
|
|
137
|
+
## SDK: `@spree/storefront-api-v2-sdk` → `@spree/sdk`
|
|
138
|
+
|
|
139
|
+
The two SDKs cover the same ground but differ in shape. The legacy `@spree/storefront-api-v2-sdk` uses a `makeClient` factory, exposes resource namespaces (`account`, `cart`, `checkout`, `products`, `taxons`, `wishlists`), wraps every response in a `Result<Error, Response>` envelope, and passes tokens via an `IToken` (`{ orderToken, bearerToken }`) argument on every method.
|
|
140
|
+
|
|
141
|
+
`@spree/sdk` uses a `createClient` factory, lines its resource namespaces up with the REST tree (`products`, `categories`, `carts`, `carts.items`, `customer.orders`, …), returns the resource directly (no `Result` wrapper), and threads auth through a per-call `RequestOptions` (`{ token, spreeToken }`) — the publishable key is set once at client construction.
|
|
142
|
+
|
|
143
|
+
### Installing
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
```bash @spree/sdk (v3)
|
|
147
|
+
npm install @spree/sdk
|
|
148
|
+
# or pnpm add @spree/sdk
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```bash @spree/storefront-api-v2-sdk (v2 — deprecated)
|
|
152
|
+
npm install @spree/storefront-api-v2-sdk
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
### Creating a client
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
```typescript v3 — @spree/sdk
|
|
160
|
+
import { createClient } from '@spree/sdk'
|
|
161
|
+
|
|
162
|
+
const client = createClient({
|
|
163
|
+
baseUrl: 'https://your-store.com',
|
|
164
|
+
publishableKey: 'pk_xxx',
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```typescript v2 — @spree/storefront-api-v2-sdk
|
|
169
|
+
import { makeClient } from '@spree/storefront-api-v2-sdk'
|
|
170
|
+
|
|
171
|
+
const client = makeClient({
|
|
172
|
+
host: 'https://your-store.com',
|
|
173
|
+
})
|
|
174
|
+
// No API key — the Storefront API v2 was fully public.
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
The Storefront API v2 had no API key concept — anyone with the host could call it. Store API v3 introduces a **publishable key** (`pk_xxx`) that's required on every request and identifies which store the call targets. The key is safe to expose in client-side code; it's how v3 supports multi-store on a single domain and gives you per-key rate limits, scopes, and audit trails.
|
|
179
|
+
|
|
180
|
+
### Calling an endpoint
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
```typescript v3 — @spree/sdk
|
|
184
|
+
// Returns the Product directly — throws on failure
|
|
185
|
+
const product = await client.products.get('spree-tote', {
|
|
186
|
+
expand: ['variants', 'media'],
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
console.log(product.name, product.price)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```typescript v2 — @spree/storefront-api-v2-sdk
|
|
193
|
+
// Returns a Result wrapper
|
|
194
|
+
const response = await client.products.show({
|
|
195
|
+
id: 'spree-tote',
|
|
196
|
+
include: 'variants,images',
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
if (response.isSuccess()) {
|
|
200
|
+
const { data, included } = response.success()
|
|
201
|
+
console.log(data.attributes.name, data.attributes.price)
|
|
202
|
+
// Walk `included` to resolve relationships…
|
|
203
|
+
} else {
|
|
204
|
+
console.error(response.fail())
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
### Auth tokens
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
```typescript v3 — @spree/sdk
|
|
213
|
+
// Guest cart (uses cart's order token as `spreeToken`)
|
|
214
|
+
const cart = await client.carts.create()
|
|
215
|
+
await client.carts.items.create(
|
|
216
|
+
cart.id,
|
|
217
|
+
{ variant_id: 'variant_abc', quantity: 1 },
|
|
218
|
+
{ spreeToken: cart.token },
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// Authenticated customer (JWT)
|
|
222
|
+
const { token } = await client.auth.login({
|
|
223
|
+
email: 'me@example.com',
|
|
224
|
+
password: 'spree123',
|
|
225
|
+
})
|
|
226
|
+
const orders = await client.customer.orders.list({}, { token })
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```typescript v2 — @spree/storefront-api-v2-sdk
|
|
230
|
+
// Guest cart (orderToken)
|
|
231
|
+
const cartResponse = await client.cart.create()
|
|
232
|
+
const orderToken = cartResponse.success().data.attributes.token
|
|
233
|
+
|
|
234
|
+
await client.cart.addItem({
|
|
235
|
+
order_token: orderToken,
|
|
236
|
+
}, { variant_id: 10, quantity: 1 })
|
|
237
|
+
|
|
238
|
+
// Authenticated customer (OAuth via /spree_oauth/token)
|
|
239
|
+
const tokenResponse = await client.authentication.getToken({
|
|
240
|
+
username: 'me@example.com',
|
|
241
|
+
password: 'spree123',
|
|
242
|
+
})
|
|
243
|
+
const bearer = tokenResponse.success().access_token
|
|
244
|
+
|
|
245
|
+
const ordersResponse = await client.account.ordersList({
|
|
246
|
+
bearer_token: bearer,
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
In v3, `token` and `spreeToken` are passed via the `RequestOptions` object on each call — no more per-method `bearer_token` / `order_token` arguments mixed into the body. JWT refresh uses `client.auth.refresh({ refresh_token })`; the old OAuth `refresh_token` grant against `/spree_oauth/token` is gone.
|
|
252
|
+
|
|
253
|
+
### Error handling
|
|
254
|
+
|
|
255
|
+
In v3 the SDK throws a `SpreeError` instance with `code`, `status`, and `details` properties. Wrap calls in `try/catch` or let them bubble. The `Result<Error, Response>` wrapper from v2 is gone — code that branched on `response.isSuccess()` becomes a single happy path plus a `catch`.
|
|
256
|
+
|
|
257
|
+
### TypeScript types
|
|
258
|
+
|
|
259
|
+
v3 ships generated TypeScript types and runtime [Zod](https://zod.dev) schemas that stay in lockstep with the API — every response field is typed, and you can validate payloads at runtime where you need belt-and-braces safety (form submissions, untrusted webhooks). v2's types were hand-maintained interfaces inside the SDK, which drifted from the actual responses over time.
|
|
260
|
+
|
|
261
|
+
## Endpoint mapping
|
|
262
|
+
|
|
263
|
+
The tables below cover every public path in `/api/v2/storefront/*` and where to find its v3 equivalent. Anything not listed is unchanged in scope but follows the new conventions (flat JSON, prefixed IDs, Ransack filters).
|
|
264
|
+
|
|
265
|
+
### Catalog: products, taxons, categories
|
|
266
|
+
|
|
267
|
+
| Storefront API v2 | Store API v3 | Notes |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| `GET /products` | `GET /products` | Filters move from `filter[...]` to [Ransack `q[...]`](/api-reference/store-api/querying); `include` → `expand`. |
|
|
270
|
+
| `GET /products/:slug` | `GET /products/:id_or_slug` | Accepts prefixed ID or slug. |
|
|
271
|
+
| `GET /products/:slug/variants` | `GET /products/:id?expand=variants` | Variants are returned via `expand`, not as a separate route. |
|
|
272
|
+
| `GET /taxons` | `GET /categories` | **Renamed.** v3 calls them Categories everywhere — same tree model, same `permalink`, parametrised by slug or prefixed ID. |
|
|
273
|
+
| `GET /taxons/:id` | `GET /categories/:id_or_permalink` | Permalinks containing slashes (`clothing/shirts`) work as-is. |
|
|
274
|
+
| *(new)* | `GET /products/filters` | Returns price range, in-stock toggle, option values, and category facets with counts — designed for filter sidebars. |
|
|
275
|
+
|
|
276
|
+
This is an area where API v3 has the biggest performance advantage over v2. `GET /products` by default will expose `default_variant_id`, `thumbnail_url` and `price` which are essential for building product lists. You don't need to expand `variants` or `media` (images) like with API v2.
|
|
277
|
+
|
|
278
|
+
### Cart and checkout
|
|
279
|
+
|
|
280
|
+
This is the biggest conceptual change. The v2 cart was a singleton accessed via the order token header; v3 carts have prefixed IDs and live alongside line items, payments, fulfillments, and discount codes as nested resources. **There is no checkout state machine in v3.** Backend will handle that automatically, without any developer action needed. This aligns with [Spree 6 upcoming changes](https://github.com/spree/spree/issues/13930).
|
|
281
|
+
|
|
282
|
+
By default all Cart endpoints will return all associations auto-expanded.
|
|
283
|
+
|
|
284
|
+
| Storefront API v2 | Store API v3 | Notes |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| `POST /cart` | `POST /carts` | Returns a `Cart` with a prefixed `id` and a `token` (use as `X-Spree-Token` for guests). |
|
|
287
|
+
| `GET /cart` | `GET /carts/:id` | Pass the prefixed `id`. Authenticated users can `GET /carts` to list active carts. |
|
|
288
|
+
| `DELETE /cart` | `DELETE /carts/:id` | Same semantics. |
|
|
289
|
+
| `POST /cart/add_item` | `POST /carts/:id/items` | Nested resource, not an action. |
|
|
290
|
+
| `PATCH /cart/set_quantity` | `PATCH /carts/:id/items/:line_item_id` | Updates an explicit line item by ID. |
|
|
291
|
+
| `DELETE /cart/set_quantity` | `DELETE /carts/:id/items/:line_item_id` | Same. |
|
|
292
|
+
| `PATCH /cart/empty` | Iterate `DELETE /carts/:id/items/:line_item_id` | No bulk-empty action; remove line items individually, or `DELETE /carts/:id` to abandon. |
|
|
293
|
+
| `PATCH /cart/apply_coupon_code` | `POST /carts/:id/discount_codes` | Body: `{ code }`. |
|
|
294
|
+
| `DELETE /cart/apply_coupon_code` | `DELETE /carts/:id/discount_codes/:code` | Path-level code. |
|
|
295
|
+
| `DELETE /cart/remove_coupon_code` | Iterate `DELETE /carts/:id/discount_codes/:code` | No remove-all shortcut; remove each code. |
|
|
296
|
+
| `GET /cart/estimate_shipping_rates` | Inspect `cart.fulfillments[].delivery_rates` | Rates are returned inline with the cart. Add an address (`PATCH /carts/:id`) and the cart is recomputed. |
|
|
297
|
+
| `PATCH /cart/associate` | `PATCH /carts/:id/associate` | Pass the JWT for the now-authenticated user. |
|
|
298
|
+
| `PATCH /cart/change_currency` | `PATCH /carts/:id` | Set `currency` directly on the cart. |
|
|
299
|
+
| `PATCH /checkout` | `PATCH /carts/:id` | Email, addresses, special instructions, etc. — all on the cart. |
|
|
300
|
+
| `PATCH /checkout/next` | *(removed)* | No state machine; nothing to advance. |
|
|
301
|
+
| `PATCH /checkout/advance` | *(removed)* | Same. |
|
|
302
|
+
| `PATCH /checkout/complete` | `POST /carts/:id/complete` | Returns the resulting `Order`. |
|
|
303
|
+
| `PATCH /checkout/select_shipping_method` | `PATCH /carts/:id/fulfillments/:fulfillment_id` | Body: `{ selected_delivery_rate_id }`. ShippingMethod is the legacy term — v3 calls them Delivery Methods / Delivery Rates. |
|
|
304
|
+
| `POST /checkout/validate_order_for_payment` | *(removed)* | Validation happens server-side when you call `complete`. |
|
|
305
|
+
| `POST /checkout/create_payment` | `POST /carts/:id/payments` | For offline / non-session methods (cash, check, bank transfer). |
|
|
306
|
+
| `POST /checkout/add_store_credit` | `POST /carts/:id/store_credits` | Body: `{ amount? }`. |
|
|
307
|
+
| `POST /checkout/remove_store_credit` | `DELETE /carts/:id/store_credits` | Same. |
|
|
308
|
+
| `GET /checkout/payment_methods` | `cart.available_payment_methods` | Inlined on the cart. |
|
|
309
|
+
| `GET /checkout/shipping_rates` | `cart.fulfillments[].delivery_rates` | Same — inlined. |
|
|
310
|
+
|
|
311
|
+
New RESTful design allows to implement different usage scenarios like multiple saved carts per customer or organization (company).
|
|
312
|
+
|
|
313
|
+
#### Session-based payments (Stripe, Adyen, PayPal)
|
|
314
|
+
|
|
315
|
+
API v2 had per-gateway endpoints (`/stripe/payment_intents`, `/adyen/payment_sessions`). API v3 unifies these behind a generic [Payment Sessions API](../../developer/core-concepts/payments.md#session-based-flow-stripe-adyen-paypal-etc) — the gateway-specific payload moves into the request body, and Spree dispatches to the right provider based on the `payment_method_id`. This shortens the integration time and allows team to deliver payment integrations faster. Also your frontend code don't need to change per gateway.
|
|
316
|
+
|
|
317
|
+
| Storefront API v2 | Store API v3 |
|
|
318
|
+
|---|---|
|
|
319
|
+
| `POST /stripe/payment_intents` | `POST /carts/:id/payment_sessions` |
|
|
320
|
+
| `GET /stripe/payment_intents/:id` | `GET /carts/:id/payment_sessions/:id` |
|
|
321
|
+
| `PATCH /stripe/payment_intents/:id` | `PATCH /carts/:id/payment_sessions/:id` |
|
|
322
|
+
| `PATCH /stripe/payment_intents/:id` (confirm) | `PATCH /carts/:id/payment_sessions/:id/complete` |
|
|
323
|
+
| `POST /stripe/setup_intents` | `POST /customers/me/payment_setup_sessions` (save card for future use) |
|
|
324
|
+
| `POST /adyen/payment_sessions` | `POST /carts/:id/payment_sessions` |
|
|
325
|
+
| `POST /adyen/payment_sessions/:id/complete` | `PATCH /carts/:id/payment_sessions/:id/complete` |
|
|
326
|
+
|
|
327
|
+
### Customer account
|
|
328
|
+
|
|
329
|
+
API v2 exposed a singleton `/account` endpoint with OAuth tokens minted at `/spree_oauth/token`. API v3 splits the surface into a public registration endpoint (`POST /customers`) and a `/customers/me` namespace for the authenticated customer. Auth moves from OAuth to JWT (`POST /auth/login`).
|
|
330
|
+
|
|
331
|
+
| Storefront API v2 | Store API v3 | Notes |
|
|
332
|
+
|---|---|---|
|
|
333
|
+
| `POST /account` | `POST /customers` | Returns JWT tokens on success. |
|
|
334
|
+
| `GET /account` | `GET /customers/me` | |
|
|
335
|
+
| `PATCH /account` | `PATCH /customers/me` | `current_password` required to change email or password. |
|
|
336
|
+
| `GET /account/addresses` | `GET /customers/me/addresses` | |
|
|
337
|
+
| `POST /account/addresses` | `POST /customers/me/addresses` | |
|
|
338
|
+
| `PATCH /account/addresses/:id` | `PATCH /customers/me/addresses/:id` | |
|
|
339
|
+
| `DELETE /account/addresses/:id` | `DELETE /customers/me/addresses/:id` | |
|
|
340
|
+
| `GET /account/credit_cards` | `GET /customers/me/credit_cards` | |
|
|
341
|
+
| `GET /account/credit_cards/default` | `GET /customers/me/credit_cards?q[default_eq]=true` | Use a Ransack filter; there's no `/default` shortcut. |
|
|
342
|
+
| `DELETE /account/credit_cards/:id` | `DELETE /customers/me/credit_cards/:id` | |
|
|
343
|
+
| `GET /account/orders` | `GET /customers/me/orders` | |
|
|
344
|
+
| `GET /account/orders/:number` | `GET /customers/me/orders/:id` | Use prefixed ID or order number. |
|
|
345
|
+
| `GET /order_status/:number` | `GET /orders/:id` | Guest-accessible with the order token; no separate status endpoint. |
|
|
346
|
+
| *(POST /spree_oauth/token grant=password)* | `POST /auth/login` | Returns a JWT, not an OAuth token. |
|
|
347
|
+
| *(POST /spree_oauth/token grant=refresh_token)* | `POST /auth/refresh` | |
|
|
348
|
+
| *(none)* | `POST /auth/logout` | Server-side revocation of the refresh token. |
|
|
349
|
+
| *(none)* | `POST /password_resets` / `PATCH /password_resets/:token` | First-class password reset flow. |
|
|
350
|
+
|
|
351
|
+
### Geography and store metadata
|
|
352
|
+
|
|
353
|
+
| Storefront API v2 | Store API v3 | Notes |
|
|
354
|
+
|---|---|---|
|
|
355
|
+
| `GET /countries` | `GET /countries` | |
|
|
356
|
+
| `GET /countries/:iso` | `GET /countries/:iso` | Use `?expand=states` for the address form. |
|
|
357
|
+
| `GET /countries/default` | `client.markets.resolve(country)` | The "default country" concept moved into Markets — resolve which market applies to a country, then read `market.default_country`. |
|
|
358
|
+
| `GET /store` | *(removed from the storefront surface)* | Store identity is conveyed via the publishable key; you don't need to fetch the store record. |
|
|
359
|
+
| *(none in v2)* | `GET /markets`, `GET /markets/:id`, `GET /markets/:id/countries`, `GET /markets/resolve` | New in v3 — Markets group countries, currency, and locale. See [Localization](localization.md). |
|
|
360
|
+
| *(none in v2)* | `GET /currencies`, `GET /locales` | Enumerate currencies and locales supported by the store. |
|
|
361
|
+
| `GET /policies` / `GET /policies/:slug` | `GET /policies` / `GET /policies/:id_or_slug` | Same — return policy, privacy, terms, etc. |
|
|
362
|
+
|
|
363
|
+
### Wishlists
|
|
364
|
+
|
|
365
|
+
| Storefront API v2 | Store API v3 |
|
|
366
|
+
|---|---|
|
|
367
|
+
| `GET /wishlists` | `GET /wishlists` |
|
|
368
|
+
| `POST /wishlists` | `POST /wishlists` |
|
|
369
|
+
| `GET /wishlists/:token` | `GET /wishlists/:id` |
|
|
370
|
+
| `PATCH /wishlists/:token` | `PATCH /wishlists/:id` |
|
|
371
|
+
| `DELETE /wishlists/:token` | `DELETE /wishlists/:id` |
|
|
372
|
+
| `GET /wishlists/default` | `GET /wishlists?q[is_default_eq]=true` |
|
|
373
|
+
| `POST /wishlists/:token/add_item` | `POST /wishlists/:wishlist_id/items` |
|
|
374
|
+
| `PATCH /wishlists/:token/set_item_quantity/:id` | `PATCH /wishlists/:wishlist_id/items/:id` |
|
|
375
|
+
| `DELETE /wishlists/:token/remove_item/:id` | `DELETE /wishlists/:wishlist_id/items/:id` |
|
|
376
|
+
| `POST /wishlists/:token/add_items` | Iterate `POST /wishlists/:wishlist_id/items` |
|
|
377
|
+
| `DELETE /wishlists/:token/remove_items` | Iterate `DELETE /wishlists/:wishlist_id/items/:id` |
|
|
378
|
+
|
|
379
|
+
### Digital downloads
|
|
380
|
+
|
|
381
|
+
| Storefront API v2 | Store API v3 |
|
|
382
|
+
|---|---|
|
|
383
|
+
| `GET /digitals/:token` | `GET /digitals/:token` |
|
|
384
|
+
|
|
385
|
+
### Removed without a v3 equivalent
|
|
386
|
+
|
|
387
|
+
A handful of v2 surfaces don't exist in v3:
|
|
388
|
+
|
|
389
|
+
- **Posts** / **Menus** / **CMS Pages** — the blog/CMS surface is not part of v3 or Spree Core anymore. Recommended: use a dedicated CMS like Payload or Strapi
|
|
390
|
+
|
|
391
|
+
## Migration checklist
|
|
392
|
+
|
|
393
|
+
The mechanical bits, in order:
|
|
394
|
+
|
|
395
|
+
1. **Install `@spree/sdk` alongside `@spree/storefront-api-v2-sdk`.** They have different package names, so both can coexist while you cut over endpoints incrementally.
|
|
396
|
+
2. **Create a publishable API key** in Spree Admin → Settings → API Keys (or via `spree api-key create`). v3 requires it on every request — v2 had no API key concept at all.
|
|
397
|
+
3. **Replace `makeClient({ host })` with `createClient({ baseUrl, publishableKey })`** in one entry point at a time. Keep the v2 client wired up for not-yet-migrated calls.
|
|
398
|
+
4. **Switch from `Result<…>` to direct returns + try/catch.** Any code that did `if (response.isSuccess()) { response.success() }` becomes a single statement, with errors thrown as `SpreeError`.
|
|
399
|
+
5. **Update token handling.** Replace `{ bearer_token, order_token }` per-method arguments with the `{ token, spreeToken }` second-argument `RequestOptions`. JWT tokens come from `client.auth.login` / `client.customers.create`; cart tokens come from `cart.token` on the cart resource.
|
|
400
|
+
6. **Convert filters from `filter[...]` to `q[...]`.** Most filters have a direct Ransack equivalent (see the [Querying](querying.md) reference). For products specifically, `taxon_ids` → `in_categories`, `name` → `name_cont` or `search`, `price` range → `price_gte` / `price_lte`.
|
|
401
|
+
7. **Rewrite cart/checkout calls as resource operations.** This is the deepest change. The cleanest path is to delete your checkout step controller wholesale and rebuild it as a single page that PATCHes the cart and POSTs to nested resources, then calls `complete` at the end.
|
|
402
|
+
8. **Stop walking `included`.** Replace JSON:API normalization helpers with direct attribute access. Use `expand` to pull in associations, and accept that they arrive inlined.
|
|
403
|
+
9. **Replace numeric IDs and slugs with prefixed IDs.** Update any code that parsed integers out of IDs, stored IDs as numbers in state, or constructed admin links from raw IDs.
|
|
404
|
+
10. **Switch the OAuth token endpoints for JWT.** The `/spree_oauth/token` endpoints are no longer the customer auth surface; use `/api/v3/store/auth/login` / `/auth/refresh` / `/auth/logout`. Refresh tokens are rotated on each refresh call, and `logout` revokes the token server-side.
|
|
405
|
+
|
|
406
|
+
For the conceptual changes — flat JSON, RESTful checkout, Markets — give yourself a sprint of breathing room rather than treating the migration as a string-replace. The win on the other side is a smaller, more obvious client surface and a Store API that lines up with the Admin API for full-stack work.
|
|
407
|
+
|
|
408
|
+
## See also
|
|
409
|
+
|
|
410
|
+
- [Store API — Introduction](introduction.md)
|
|
411
|
+
- [Authentication](authentication.md)
|
|
412
|
+
- [Querying (Ransack, sorting, pagination, expand, fields)](querying.md)
|
|
413
|
+
- [Relations — `expand` rules and nested associations](relations.md)
|
|
414
|
+
- [Localization — Markets, currencies, locales](localization.md)
|
|
415
|
+
- [Errors](errors.md)
|