@cartbase/storefront 0.17.0 → 0.18.0
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/LICENSE +21 -21
- package/package.json +258 -256
- package/src/api/auth.ts +108 -108
- package/src/api/carts.ts +523 -523
- package/src/api/categories.ts +184 -184
- package/src/api/checkout.ts +526 -526
- package/src/api/collections.ts +130 -130
- package/src/api/consent.ts +75 -75
- package/src/api/content.ts +125 -125
- package/src/api/customers.ts +303 -303
- package/src/api/gift-cards.ts +112 -112
- package/src/api/http.ts +180 -180
- package/src/api/index.ts +30 -30
- package/src/api/menus.ts +77 -77
- package/src/api/metaobjects.ts +136 -136
- package/src/api/orders.ts +290 -290
- package/src/api/products.ts +43 -1
- package/src/api/redirects.ts +37 -37
- package/src/api/regions.ts +200 -200
- package/src/api/reviews.ts +259 -259
- package/src/api/search.ts +163 -163
- package/src/api/store.ts +35 -35
- package/src/api/types.ts +91 -91
- package/src/cart-drawer/context.tsx +277 -164
- package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
- package/src/cart-drawer/gift-wrap.tsx +82 -82
- package/src/cart-drawer/index.ts +71 -68
- package/src/cart-drawer/item/index.tsx +162 -162
- package/src/cart-drawer/item/quantity.tsx +8 -14
- package/src/cart-drawer/item/upsell.tsx +110 -110
- package/src/cart-drawer/labels.ts +123 -123
- package/src/cart-drawer/mutation-queue.ts +78 -0
- package/src/cart-drawer/notes.tsx +131 -131
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/cart-drawer/summary-breakdown.tsx +197 -197
- package/src/checkout/address-error-copy.ts +117 -117
- package/src/checkout/boxnow-locker-selector.tsx +410 -410
- package/src/checkout/compare-addresses.ts +40 -40
- package/src/checkout/discount-section.tsx +218 -218
- package/src/checkout/error-copy-codes.ts +63 -63
- package/src/checkout/geocode.ts +154 -154
- package/src/checkout/gift-card-section.tsx +224 -224
- package/src/checkout/index.ts +72 -72
- package/src/checkout/labels.ts +495 -495
- package/src/checkout/payment-button.tsx +372 -372
- package/src/checkout/payment-error-copy.ts +154 -154
- package/src/checkout/promotion-error-copy.ts +91 -91
- package/src/checkout/use-checkout-orchestration.ts +1592 -1592
- package/src/common/country-flag.tsx +52 -52
- package/src/common/country-select.tsx +11 -11
- package/src/common/index.ts +20 -20
- package/src/common/market-select.tsx +57 -57
- package/src/index.ts +12 -12
- package/src/lib/country-name.ts +59 -59
- package/src/lib/get-product-price.ts +133 -133
- package/src/lib/media-image.tsx +39 -39
- package/src/lib/payment-constants.ts +53 -53
- package/src/lib/platform.ts +13 -13
- package/src/lib/price.tsx +39 -39
- package/src/lib/store-api-error.ts +36 -36
- package/src/lib/variant-caption.ts +32 -32
- package/src/locales/bg.ts +467 -467
- package/src/locales/context.ts +37 -37
- package/src/locales/en.ts +26 -26
- package/src/locales/es.ts +466 -466
- package/src/locales/index.ts +19 -19
- package/src/locales/provider.tsx +59 -59
- package/src/locales/types.ts +77 -77
- package/src/order/index.ts +62 -62
- package/src/order/labels.ts +79 -79
- package/src/order/order-totals.tsx +250 -250
- package/src/primitives/select-field.tsx +93 -93
- package/src/products/image-gallery.tsx +43 -43
- package/src/products/index.ts +68 -59
- package/src/products/option-select.tsx +67 -67
- package/src/products/product-actions-wrapper.tsx +63 -62
- package/src/products/product-actions.tsx +151 -150
- package/src/products/product-price.tsx +69 -69
- package/src/products/product-promises.tsx +61 -61
- package/src/products/product-specs.tsx +65 -65
- package/src/products/product-tabs.tsx +123 -123
- package/src/products/product-template.tsx +163 -162
- package/src/products/purchase-options.tsx +130 -130
- package/src/products/sets.ts +110 -0
- package/src/products/use-product-actions.ts +199 -164
- package/src/products/variant-matching.ts +71 -71
- package/src/products/variant-url.ts +74 -74
- package/src/reviews-ui/index.ts +73 -73
- package/src/store/category-template.tsx +136 -136
- package/src/store/index.ts +40 -40
- package/src/tracking/chatgpt-pixel.tsx +99 -99
- package/src/tracking/consent-init.tsx +62 -62
- package/src/tracking/events.ts +348 -348
- package/src/tracking/ga4.tsx +93 -93
- package/src/tracking/google-ads.ts +84 -84
- package/src/tracking/gtm.tsx +60 -60
- package/src/tracking/inline-script.ts +49 -49
- package/src/tracking/oaiq.ts +206 -206
- package/src/tracking/tiktok-pixel.tsx +91 -91
- package/src/tracking/track-init.tsx +56 -56
- package/src/tracking/track-order-purchase.tsx +122 -122
- package/src/tracking/ttq.ts +180 -180
- package/src/tracking/use-tracking-config.ts +54 -54
- package/theme/index.css +25 -25
- package/theme/theme.css +150 -150
- package/theme/tokens.css +106 -106
package/src/api/orders.ts
CHANGED
|
@@ -1,290 +1,290 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @cartbase/storefront/api/orders — the authenticated customer's orders.
|
|
3
|
-
*
|
|
4
|
-
* EVERY function here requires a customer session
|
|
5
|
-
* (`authorization: Bearer <jwt>` via the client's `getAuthToken`) — there
|
|
6
|
-
* is no anonymous order read; a guest's only order handle is the
|
|
7
|
-
* `completeCart()` response. Missing/invalid JWT → 401 `unauthenticated`.
|
|
8
|
-
*
|
|
9
|
-
* Route ground truth: src/app/api/store/orders/**. Doc:
|
|
10
|
-
* docs/storefront/orders.md.
|
|
11
|
-
*
|
|
12
|
-
* `GET /api/store/orders/display/:displayId` was contract-listed but
|
|
13
|
-
* missing from the codebase when this module was first written; the route
|
|
14
|
-
* was built at the batch-9 merge and `retrieveOrderByDisplayId` wraps it.
|
|
15
|
-
*/
|
|
16
|
-
import type { StorefrontClient } from "./http"
|
|
17
|
-
import type { IsoDateString, ListEnvelope, MajorUnitAmount } from "./types"
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// DTOs — ground truth: orders/route.ts (list select `*`), orders/[id]/route.ts
|
|
21
|
-
// (STORE_ORDER_DETAIL_SELECT), orders/[id]/transfer/*.
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
/** Order row as listed (plain `orders` columns, no embeds). */
|
|
25
|
-
export interface StoreOrderSummaryRow {
|
|
26
|
-
id: string
|
|
27
|
-
/** Human-facing autoincrement. */
|
|
28
|
-
display_id: number
|
|
29
|
-
status: string
|
|
30
|
-
email: string | null
|
|
31
|
-
currency_code: string
|
|
32
|
-
customer_id: string | null
|
|
33
|
-
sales_channel_id: string | null
|
|
34
|
-
region_id: string | null
|
|
35
|
-
metadata: Record<string, unknown> | null
|
|
36
|
-
created_at: IsoDateString
|
|
37
|
-
updated_at: IsoDateString
|
|
38
|
-
[key: string]: unknown
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** `order_line_items` row (title/prices/product snapshot at purchase time). */
|
|
42
|
-
export interface StoreOrderLineItem {
|
|
43
|
-
id: string
|
|
44
|
-
title: string | null
|
|
45
|
-
subtitle: string | null
|
|
46
|
-
product_title: string | null
|
|
47
|
-
product_handle: string | null
|
|
48
|
-
thumbnail: string | null
|
|
49
|
-
variant_id: string | null
|
|
50
|
-
variant_title: string | null
|
|
51
|
-
variant_sku: string | null
|
|
52
|
-
unit_price: MajorUnitAmount
|
|
53
|
-
is_giftcard?: boolean
|
|
54
|
-
requires_shipping?: boolean
|
|
55
|
-
metadata: Record<string, unknown> | null
|
|
56
|
-
[key: string]: unknown
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Version pivot row: quantity + the embedded line item. */
|
|
60
|
-
export interface StoreOrderItem {
|
|
61
|
-
id: string
|
|
62
|
-
order_id: string
|
|
63
|
-
quantity: number
|
|
64
|
-
line_item: StoreOrderLineItem | null
|
|
65
|
-
[key: string]: unknown
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface StoreFulfillmentLabel {
|
|
69
|
-
tracking_number: string | null
|
|
70
|
-
tracking_url: string | null
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Store-safe fulfillment subset: lifecycle timestamps + tracking labels. */
|
|
74
|
-
export interface StoreOrderFulfillment {
|
|
75
|
-
fulfillment: {
|
|
76
|
-
id: string
|
|
77
|
-
packed_at: IsoDateString | null
|
|
78
|
-
shipped_at: IsoDateString | null
|
|
79
|
-
delivered_at: IsoDateString | null
|
|
80
|
-
canceled_at: IsoDateString | null
|
|
81
|
-
labels: StoreFulfillmentLabel[]
|
|
82
|
-
} | null
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** `order_addresses` row. */
|
|
86
|
-
export interface StoreOrderAddress {
|
|
87
|
-
id: string
|
|
88
|
-
first_name: string | null
|
|
89
|
-
last_name: string | null
|
|
90
|
-
company: string | null
|
|
91
|
-
address_1: string | null
|
|
92
|
-
address_2: string | null
|
|
93
|
-
city: string | null
|
|
94
|
-
country_code: string | null
|
|
95
|
-
province: string | null
|
|
96
|
-
postal_code: string | null
|
|
97
|
-
phone: string | null
|
|
98
|
-
[key: string]: unknown
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Order detail — a store-safe subset of the admin select: items (with
|
|
103
|
-
* embedded line_item), fulfillments with tracking, both addresses. No
|
|
104
|
-
* internal joins (customer row, payment internals) and NO internal staff
|
|
105
|
-
* notes (timeline comments never cross this surface).
|
|
106
|
-
*/
|
|
107
|
-
export interface StoreOrderDetail extends StoreOrderSummaryRow {
|
|
108
|
-
items: StoreOrderItem[]
|
|
109
|
-
fulfillments: StoreOrderFulfillment[]
|
|
110
|
-
shipping_address: StoreOrderAddress | null
|
|
111
|
-
billing_address: StoreOrderAddress | null
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface ListOrdersQuery {
|
|
115
|
-
/** 1–200, default 20. */
|
|
116
|
-
limit?: number
|
|
117
|
-
/** Default 0. */
|
|
118
|
-
offset?: number
|
|
119
|
-
/** Exact-match filter on `orders.status` (e.g. `pending`, `completed`, `canceled`). */
|
|
120
|
-
status?: string
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export interface OrderListResponse extends ListEnvelope {
|
|
124
|
-
orders: StoreOrderSummaryRow[]
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface OrderResponse {
|
|
128
|
-
order: StoreOrderDetail
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
|
-
// Reads
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* List MY orders (newest first). → 200 `{orders, count, offset, limit}` —
|
|
137
|
-
* plain order rows, no embeds (fetch the detail for items/tracking).
|
|
138
|
-
* Auth: REQUIRED Bearer JWT (+ `x-client-id`). Scoped to the session
|
|
139
|
-
* customer — another customer's orders are invisible, not 403.
|
|
140
|
-
* Errors: 401 `unauthenticated`, 400 `validation_failed` (bad
|
|
141
|
-
* limit/offset).
|
|
142
|
-
*/
|
|
143
|
-
export async function listOrders(
|
|
144
|
-
client: StorefrontClient,
|
|
145
|
-
query: ListOrdersQuery = {}
|
|
146
|
-
): Promise<OrderListResponse> {
|
|
147
|
-
return client.get("/api/store/orders", { query: { ...query } })
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Retrieve MY order with items, fulfillments (+ tracking labels) and
|
|
152
|
-
* addresses. → 200 `{order}`.
|
|
153
|
-
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
154
|
-
* Errors: 401 `unauthenticated`, 404 `not_found` (unknown id OR an order
|
|
155
|
-
* belonging to another customer — ownership is part of the lookup, so
|
|
156
|
-
* cross-customer reads are indistinguishable from missing).
|
|
157
|
-
*/
|
|
158
|
-
export async function retrieveOrder(
|
|
159
|
-
client: StorefrontClient,
|
|
160
|
-
orderId: string
|
|
161
|
-
): Promise<OrderResponse> {
|
|
162
|
-
return client.get(`/api/store/orders/${orderId}`)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Retrieve MY order by its human number: the autoincrement `display_id`
|
|
167
|
-
* (numeric segment) or a `custom_display_id` (any segment; a numeric
|
|
168
|
-
* custom id wins over a colliding display_id — it is the number the store
|
|
169
|
-
* showed the customer). Same detail embeds + ownership scope as
|
|
170
|
-
* `retrieveOrder`.
|
|
171
|
-
* → 200 `{order}`.
|
|
172
|
-
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
173
|
-
* Errors: 401 `unauthenticated`, 404 `not_found` (unknown number OR another
|
|
174
|
-
* customer's order — indistinguishable by design).
|
|
175
|
-
*/
|
|
176
|
-
export async function retrieveOrderByDisplayId(
|
|
177
|
-
client: StorefrontClient,
|
|
178
|
-
displayId: string | number
|
|
179
|
-
): Promise<OrderResponse> {
|
|
180
|
-
return client.get(`/api/store/orders/display/${displayId}`)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ---------------------------------------------------------------------------
|
|
184
|
-
// Transfers — move a (guest) order to the authenticated customer.
|
|
185
|
-
// Flow: the CLAIMING customer calls request → a token is issued for the
|
|
186
|
-
// order's email holder → the claimer (whose email must match the order's)
|
|
187
|
-
// calls accept with the token; decline/cancel end a pending transfer.
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
|
|
190
|
-
export interface RequestOrderTransferInput {
|
|
191
|
-
/** Free-text shown to the recipient; stored on the transfer action. */
|
|
192
|
-
description?: string
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* request → `{order:{id, transfer:{requested:true}}}` — ALWAYS this shape,
|
|
197
|
-
* for the first request and for duplicates alike. The transfer token is
|
|
198
|
-
* email-delivered only and never crosses the store surface.
|
|
199
|
-
*/
|
|
200
|
-
export interface RequestOrderTransferResponse {
|
|
201
|
-
order: {
|
|
202
|
-
id: string
|
|
203
|
-
transfer: { requested: true }
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Request the order be transferred to ME (the authenticated customer).
|
|
209
|
-
* Idempotent: an existing pending transfer is returned, not duplicated.
|
|
210
|
-
* → 200.
|
|
211
|
-
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
212
|
-
* Errors: 401 `unauthenticated`, 404 `not_found`, 400 `already_owned`
|
|
213
|
-
* (the order already belongs to this customer).
|
|
214
|
-
*/
|
|
215
|
-
export async function requestOrderTransfer(
|
|
216
|
-
client: StorefrontClient,
|
|
217
|
-
orderId: string,
|
|
218
|
-
input: RequestOrderTransferInput = {}
|
|
219
|
-
): Promise<RequestOrderTransferResponse> {
|
|
220
|
-
return client.post(`/api/store/orders/${orderId}/transfer/request`, input)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export interface CancelOrderTransferResponse {
|
|
224
|
-
order: { id: string; transfer: { canceled: true } }
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Cancel MY pending transfer request (only the requester may cancel).
|
|
229
|
-
* → 200.
|
|
230
|
-
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
231
|
-
* Errors: 401 `unauthenticated`, 404 `not_found` (order or no pending
|
|
232
|
-
* transfer), 403 `forbidden` (not the requester).
|
|
233
|
-
*/
|
|
234
|
-
export async function cancelOrderTransfer(
|
|
235
|
-
client: StorefrontClient,
|
|
236
|
-
orderId: string
|
|
237
|
-
): Promise<CancelOrderTransferResponse> {
|
|
238
|
-
return client.post(`/api/store/orders/${orderId}/transfer/cancel`)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export interface AcceptOrderTransferInput {
|
|
242
|
-
/** The transfer token received by email (≥16 chars). */
|
|
243
|
-
token: string
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** accept → the full updated order row (now owned by the caller). */
|
|
247
|
-
export interface AcceptOrderTransferResponse {
|
|
248
|
-
order: StoreOrderSummaryRow
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Accept a pending transfer with the emailed token — assigns the order to
|
|
253
|
-
* the authenticated customer. The caller's email must equal the order's
|
|
254
|
-
* original email (a leaked token alone is not enough). → 200 `{order}`.
|
|
255
|
-
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
256
|
-
* Errors: 401 `unauthenticated`, 404 `not_found` (order / no pending
|
|
257
|
-
* transfer), 403 `invalid_token` | `email_mismatch`, 400
|
|
258
|
-
* `validation_failed`.
|
|
259
|
-
*/
|
|
260
|
-
export async function acceptOrderTransfer(
|
|
261
|
-
client: StorefrontClient,
|
|
262
|
-
orderId: string,
|
|
263
|
-
input: AcceptOrderTransferInput
|
|
264
|
-
): Promise<AcceptOrderTransferResponse> {
|
|
265
|
-
return client.post(`/api/store/orders/${orderId}/transfer/accept`, input)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export interface DeclineOrderTransferInput {
|
|
269
|
-
/** The transfer token received by email (≥16 chars). */
|
|
270
|
-
token: string
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export interface DeclineOrderTransferResponse {
|
|
274
|
-
order: { id: string; transfer: { declined: true } }
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Decline a pending transfer (the order's original email holder rejects
|
|
279
|
-
* it) — validates the token, then removes the pending action. → 200.
|
|
280
|
-
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
281
|
-
* Errors: 401 `unauthenticated`, 404 `not_found`, 403 `invalid_token`,
|
|
282
|
-
* 400 `validation_failed`.
|
|
283
|
-
*/
|
|
284
|
-
export async function declineOrderTransfer(
|
|
285
|
-
client: StorefrontClient,
|
|
286
|
-
orderId: string,
|
|
287
|
-
input: DeclineOrderTransferInput
|
|
288
|
-
): Promise<DeclineOrderTransferResponse> {
|
|
289
|
-
return client.post(`/api/store/orders/${orderId}/transfer/decline`, input)
|
|
290
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @cartbase/storefront/api/orders — the authenticated customer's orders.
|
|
3
|
+
*
|
|
4
|
+
* EVERY function here requires a customer session
|
|
5
|
+
* (`authorization: Bearer <jwt>` via the client's `getAuthToken`) — there
|
|
6
|
+
* is no anonymous order read; a guest's only order handle is the
|
|
7
|
+
* `completeCart()` response. Missing/invalid JWT → 401 `unauthenticated`.
|
|
8
|
+
*
|
|
9
|
+
* Route ground truth: src/app/api/store/orders/**. Doc:
|
|
10
|
+
* docs/storefront/orders.md.
|
|
11
|
+
*
|
|
12
|
+
* `GET /api/store/orders/display/:displayId` was contract-listed but
|
|
13
|
+
* missing from the codebase when this module was first written; the route
|
|
14
|
+
* was built at the batch-9 merge and `retrieveOrderByDisplayId` wraps it.
|
|
15
|
+
*/
|
|
16
|
+
import type { StorefrontClient } from "./http"
|
|
17
|
+
import type { IsoDateString, ListEnvelope, MajorUnitAmount } from "./types"
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// DTOs — ground truth: orders/route.ts (list select `*`), orders/[id]/route.ts
|
|
21
|
+
// (STORE_ORDER_DETAIL_SELECT), orders/[id]/transfer/*.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/** Order row as listed (plain `orders` columns, no embeds). */
|
|
25
|
+
export interface StoreOrderSummaryRow {
|
|
26
|
+
id: string
|
|
27
|
+
/** Human-facing autoincrement. */
|
|
28
|
+
display_id: number
|
|
29
|
+
status: string
|
|
30
|
+
email: string | null
|
|
31
|
+
currency_code: string
|
|
32
|
+
customer_id: string | null
|
|
33
|
+
sales_channel_id: string | null
|
|
34
|
+
region_id: string | null
|
|
35
|
+
metadata: Record<string, unknown> | null
|
|
36
|
+
created_at: IsoDateString
|
|
37
|
+
updated_at: IsoDateString
|
|
38
|
+
[key: string]: unknown
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `order_line_items` row (title/prices/product snapshot at purchase time). */
|
|
42
|
+
export interface StoreOrderLineItem {
|
|
43
|
+
id: string
|
|
44
|
+
title: string | null
|
|
45
|
+
subtitle: string | null
|
|
46
|
+
product_title: string | null
|
|
47
|
+
product_handle: string | null
|
|
48
|
+
thumbnail: string | null
|
|
49
|
+
variant_id: string | null
|
|
50
|
+
variant_title: string | null
|
|
51
|
+
variant_sku: string | null
|
|
52
|
+
unit_price: MajorUnitAmount
|
|
53
|
+
is_giftcard?: boolean
|
|
54
|
+
requires_shipping?: boolean
|
|
55
|
+
metadata: Record<string, unknown> | null
|
|
56
|
+
[key: string]: unknown
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Version pivot row: quantity + the embedded line item. */
|
|
60
|
+
export interface StoreOrderItem {
|
|
61
|
+
id: string
|
|
62
|
+
order_id: string
|
|
63
|
+
quantity: number
|
|
64
|
+
line_item: StoreOrderLineItem | null
|
|
65
|
+
[key: string]: unknown
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface StoreFulfillmentLabel {
|
|
69
|
+
tracking_number: string | null
|
|
70
|
+
tracking_url: string | null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Store-safe fulfillment subset: lifecycle timestamps + tracking labels. */
|
|
74
|
+
export interface StoreOrderFulfillment {
|
|
75
|
+
fulfillment: {
|
|
76
|
+
id: string
|
|
77
|
+
packed_at: IsoDateString | null
|
|
78
|
+
shipped_at: IsoDateString | null
|
|
79
|
+
delivered_at: IsoDateString | null
|
|
80
|
+
canceled_at: IsoDateString | null
|
|
81
|
+
labels: StoreFulfillmentLabel[]
|
|
82
|
+
} | null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** `order_addresses` row. */
|
|
86
|
+
export interface StoreOrderAddress {
|
|
87
|
+
id: string
|
|
88
|
+
first_name: string | null
|
|
89
|
+
last_name: string | null
|
|
90
|
+
company: string | null
|
|
91
|
+
address_1: string | null
|
|
92
|
+
address_2: string | null
|
|
93
|
+
city: string | null
|
|
94
|
+
country_code: string | null
|
|
95
|
+
province: string | null
|
|
96
|
+
postal_code: string | null
|
|
97
|
+
phone: string | null
|
|
98
|
+
[key: string]: unknown
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Order detail — a store-safe subset of the admin select: items (with
|
|
103
|
+
* embedded line_item), fulfillments with tracking, both addresses. No
|
|
104
|
+
* internal joins (customer row, payment internals) and NO internal staff
|
|
105
|
+
* notes (timeline comments never cross this surface).
|
|
106
|
+
*/
|
|
107
|
+
export interface StoreOrderDetail extends StoreOrderSummaryRow {
|
|
108
|
+
items: StoreOrderItem[]
|
|
109
|
+
fulfillments: StoreOrderFulfillment[]
|
|
110
|
+
shipping_address: StoreOrderAddress | null
|
|
111
|
+
billing_address: StoreOrderAddress | null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ListOrdersQuery {
|
|
115
|
+
/** 1–200, default 20. */
|
|
116
|
+
limit?: number
|
|
117
|
+
/** Default 0. */
|
|
118
|
+
offset?: number
|
|
119
|
+
/** Exact-match filter on `orders.status` (e.g. `pending`, `completed`, `canceled`). */
|
|
120
|
+
status?: string
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface OrderListResponse extends ListEnvelope {
|
|
124
|
+
orders: StoreOrderSummaryRow[]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface OrderResponse {
|
|
128
|
+
order: StoreOrderDetail
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Reads
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* List MY orders (newest first). → 200 `{orders, count, offset, limit}` —
|
|
137
|
+
* plain order rows, no embeds (fetch the detail for items/tracking).
|
|
138
|
+
* Auth: REQUIRED Bearer JWT (+ `x-client-id`). Scoped to the session
|
|
139
|
+
* customer — another customer's orders are invisible, not 403.
|
|
140
|
+
* Errors: 401 `unauthenticated`, 400 `validation_failed` (bad
|
|
141
|
+
* limit/offset).
|
|
142
|
+
*/
|
|
143
|
+
export async function listOrders(
|
|
144
|
+
client: StorefrontClient,
|
|
145
|
+
query: ListOrdersQuery = {}
|
|
146
|
+
): Promise<OrderListResponse> {
|
|
147
|
+
return client.get("/api/store/orders", { query: { ...query } })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Retrieve MY order with items, fulfillments (+ tracking labels) and
|
|
152
|
+
* addresses. → 200 `{order}`.
|
|
153
|
+
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
154
|
+
* Errors: 401 `unauthenticated`, 404 `not_found` (unknown id OR an order
|
|
155
|
+
* belonging to another customer — ownership is part of the lookup, so
|
|
156
|
+
* cross-customer reads are indistinguishable from missing).
|
|
157
|
+
*/
|
|
158
|
+
export async function retrieveOrder(
|
|
159
|
+
client: StorefrontClient,
|
|
160
|
+
orderId: string
|
|
161
|
+
): Promise<OrderResponse> {
|
|
162
|
+
return client.get(`/api/store/orders/${orderId}`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Retrieve MY order by its human number: the autoincrement `display_id`
|
|
167
|
+
* (numeric segment) or a `custom_display_id` (any segment; a numeric
|
|
168
|
+
* custom id wins over a colliding display_id — it is the number the store
|
|
169
|
+
* showed the customer). Same detail embeds + ownership scope as
|
|
170
|
+
* `retrieveOrder`.
|
|
171
|
+
* → 200 `{order}`.
|
|
172
|
+
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
173
|
+
* Errors: 401 `unauthenticated`, 404 `not_found` (unknown number OR another
|
|
174
|
+
* customer's order — indistinguishable by design).
|
|
175
|
+
*/
|
|
176
|
+
export async function retrieveOrderByDisplayId(
|
|
177
|
+
client: StorefrontClient,
|
|
178
|
+
displayId: string | number
|
|
179
|
+
): Promise<OrderResponse> {
|
|
180
|
+
return client.get(`/api/store/orders/display/${displayId}`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Transfers — move a (guest) order to the authenticated customer.
|
|
185
|
+
// Flow: the CLAIMING customer calls request → a token is issued for the
|
|
186
|
+
// order's email holder → the claimer (whose email must match the order's)
|
|
187
|
+
// calls accept with the token; decline/cancel end a pending transfer.
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
export interface RequestOrderTransferInput {
|
|
191
|
+
/** Free-text shown to the recipient; stored on the transfer action. */
|
|
192
|
+
description?: string
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* request → `{order:{id, transfer:{requested:true}}}` — ALWAYS this shape,
|
|
197
|
+
* for the first request and for duplicates alike. The transfer token is
|
|
198
|
+
* email-delivered only and never crosses the store surface.
|
|
199
|
+
*/
|
|
200
|
+
export interface RequestOrderTransferResponse {
|
|
201
|
+
order: {
|
|
202
|
+
id: string
|
|
203
|
+
transfer: { requested: true }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Request the order be transferred to ME (the authenticated customer).
|
|
209
|
+
* Idempotent: an existing pending transfer is returned, not duplicated.
|
|
210
|
+
* → 200.
|
|
211
|
+
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
212
|
+
* Errors: 401 `unauthenticated`, 404 `not_found`, 400 `already_owned`
|
|
213
|
+
* (the order already belongs to this customer).
|
|
214
|
+
*/
|
|
215
|
+
export async function requestOrderTransfer(
|
|
216
|
+
client: StorefrontClient,
|
|
217
|
+
orderId: string,
|
|
218
|
+
input: RequestOrderTransferInput = {}
|
|
219
|
+
): Promise<RequestOrderTransferResponse> {
|
|
220
|
+
return client.post(`/api/store/orders/${orderId}/transfer/request`, input)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface CancelOrderTransferResponse {
|
|
224
|
+
order: { id: string; transfer: { canceled: true } }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Cancel MY pending transfer request (only the requester may cancel).
|
|
229
|
+
* → 200.
|
|
230
|
+
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
231
|
+
* Errors: 401 `unauthenticated`, 404 `not_found` (order or no pending
|
|
232
|
+
* transfer), 403 `forbidden` (not the requester).
|
|
233
|
+
*/
|
|
234
|
+
export async function cancelOrderTransfer(
|
|
235
|
+
client: StorefrontClient,
|
|
236
|
+
orderId: string
|
|
237
|
+
): Promise<CancelOrderTransferResponse> {
|
|
238
|
+
return client.post(`/api/store/orders/${orderId}/transfer/cancel`)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface AcceptOrderTransferInput {
|
|
242
|
+
/** The transfer token received by email (≥16 chars). */
|
|
243
|
+
token: string
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** accept → the full updated order row (now owned by the caller). */
|
|
247
|
+
export interface AcceptOrderTransferResponse {
|
|
248
|
+
order: StoreOrderSummaryRow
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Accept a pending transfer with the emailed token — assigns the order to
|
|
253
|
+
* the authenticated customer. The caller's email must equal the order's
|
|
254
|
+
* original email (a leaked token alone is not enough). → 200 `{order}`.
|
|
255
|
+
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
256
|
+
* Errors: 401 `unauthenticated`, 404 `not_found` (order / no pending
|
|
257
|
+
* transfer), 403 `invalid_token` | `email_mismatch`, 400
|
|
258
|
+
* `validation_failed`.
|
|
259
|
+
*/
|
|
260
|
+
export async function acceptOrderTransfer(
|
|
261
|
+
client: StorefrontClient,
|
|
262
|
+
orderId: string,
|
|
263
|
+
input: AcceptOrderTransferInput
|
|
264
|
+
): Promise<AcceptOrderTransferResponse> {
|
|
265
|
+
return client.post(`/api/store/orders/${orderId}/transfer/accept`, input)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface DeclineOrderTransferInput {
|
|
269
|
+
/** The transfer token received by email (≥16 chars). */
|
|
270
|
+
token: string
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface DeclineOrderTransferResponse {
|
|
274
|
+
order: { id: string; transfer: { declined: true } }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Decline a pending transfer (the order's original email holder rejects
|
|
279
|
+
* it) — validates the token, then removes the pending action. → 200.
|
|
280
|
+
* Auth: REQUIRED Bearer JWT (+ `x-client-id`).
|
|
281
|
+
* Errors: 401 `unauthenticated`, 404 `not_found`, 403 `invalid_token`,
|
|
282
|
+
* 400 `validation_failed`.
|
|
283
|
+
*/
|
|
284
|
+
export async function declineOrderTransfer(
|
|
285
|
+
client: StorefrontClient,
|
|
286
|
+
orderId: string,
|
|
287
|
+
input: DeclineOrderTransferInput
|
|
288
|
+
): Promise<DeclineOrderTransferResponse> {
|
|
289
|
+
return client.post(`/api/store/orders/${orderId}/transfer/decline`, input)
|
|
290
|
+
}
|
package/src/api/products.ts
CHANGED
|
@@ -125,6 +125,37 @@ export interface StoreProductVariant {
|
|
|
125
125
|
* when absent, components fall back to the optimistic legacy behavior.
|
|
126
126
|
*/
|
|
127
127
|
in_stock?: boolean
|
|
128
|
+
/**
|
|
129
|
+
* The variant's links to stock items, Medusa's inventory kit, present only
|
|
130
|
+
* when the read asked for them with `fields: SET_FIELDS`
|
|
131
|
+
* (`products/sets.ts`). A set's variant has one link per product inside
|
|
132
|
+
* it; a product sold on its own stock has one link to its own item.
|
|
133
|
+
*/
|
|
134
|
+
inventory_items?: StoreInventoryItemLink[]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** A variant drawing on a stock item, as a kit link names it. */
|
|
138
|
+
export interface StoreInventoryLinkedVariant {
|
|
139
|
+
id: string
|
|
140
|
+
product_id: string
|
|
141
|
+
title: string | null
|
|
142
|
+
sku: string | null
|
|
143
|
+
product: { id: string; title: string; handle: string; thumbnail: string | null } | null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** One link from a variant to a stock item, with how many units it takes. */
|
|
147
|
+
export interface StoreInventoryItemLink {
|
|
148
|
+
inventory_item_id: string
|
|
149
|
+
required_quantity: number
|
|
150
|
+
inventory: {
|
|
151
|
+
id: string
|
|
152
|
+
/** Every stock item is born with its own variant's SKU. */
|
|
153
|
+
sku: string | null
|
|
154
|
+
title: string | null
|
|
155
|
+
thumbnail: string | null
|
|
156
|
+
/** Every live variant of a published product drawing on this item. */
|
|
157
|
+
variants: StoreInventoryLinkedVariant[]
|
|
158
|
+
} | null
|
|
128
159
|
}
|
|
129
160
|
|
|
130
161
|
export interface StoreProduct {
|
|
@@ -197,6 +228,17 @@ export interface ListProductsQuery extends PricingContextQuery, PaginationQuery
|
|
|
197
228
|
* the default).
|
|
198
229
|
*/
|
|
199
230
|
order?: string
|
|
231
|
+
/**
|
|
232
|
+
* Medusa's field selection. Only the kit links change the answer: pass
|
|
233
|
+
* `SET_FIELDS` (`products/sets.ts`) to receive `variants[].inventory_items`.
|
|
234
|
+
*/
|
|
235
|
+
fields?: string
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The one product read's query: the pricing context and the field selection. */
|
|
239
|
+
export interface RetrieveProductQuery extends PricingContextQuery {
|
|
240
|
+
/** As on the list: `SET_FIELDS` adds `variants[].inventory_items`. */
|
|
241
|
+
fields?: string
|
|
200
242
|
}
|
|
201
243
|
|
|
202
244
|
export interface ProductListResponse extends ListEnvelope {
|
|
@@ -254,7 +296,7 @@ export async function listProducts(
|
|
|
254
296
|
export async function retrieveProduct(
|
|
255
297
|
client: StorefrontClient,
|
|
256
298
|
idOrHandle: string,
|
|
257
|
-
query?:
|
|
299
|
+
query?: RetrieveProductQuery
|
|
258
300
|
): Promise<ProductResponse> {
|
|
259
301
|
return client.get(`/api/store/products/${encodeURIComponent(idOrHandle)}`, {
|
|
260
302
|
query: { ...query },
|