@putiikkipalvelu/storefront-sdk 0.1.1 → 0.2.1
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/index.cjs +1327 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2201 -160
- package/dist/index.d.ts +2201 -160
- package/dist/index.js +1326 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -36,6 +36,14 @@ var ValidationError = class extends StorefrontError {
|
|
|
36
36
|
this.name = "ValidationError";
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
+
var VerificationRequiredError = class extends StorefrontError {
|
|
40
|
+
constructor(message, customerId) {
|
|
41
|
+
super(message, 403, "VERIFICATION_REQUIRED");
|
|
42
|
+
this.requiresVerification = true;
|
|
43
|
+
this.name = "VerificationRequiredError";
|
|
44
|
+
this.customerId = customerId;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
39
47
|
|
|
40
48
|
// src/utils/fetch.ts
|
|
41
49
|
var SDK_VERSION = "0.1.0";
|
|
@@ -96,15 +104,18 @@ function createFetcher(config) {
|
|
|
96
104
|
return { request };
|
|
97
105
|
}
|
|
98
106
|
async function handleErrorResponse(response) {
|
|
99
|
-
let
|
|
107
|
+
let errorData = {};
|
|
100
108
|
try {
|
|
101
109
|
const json = await response.json();
|
|
102
|
-
if (json && typeof json === "object"
|
|
103
|
-
|
|
110
|
+
if (json && typeof json === "object") {
|
|
111
|
+
errorData = json;
|
|
104
112
|
}
|
|
105
113
|
} catch {
|
|
106
114
|
}
|
|
107
|
-
const message =
|
|
115
|
+
const message = errorData.error || response.statusText || "Request failed";
|
|
116
|
+
if (errorData.requiresVerification && errorData.customerId) {
|
|
117
|
+
throw new VerificationRequiredError(message, errorData.customerId);
|
|
118
|
+
}
|
|
108
119
|
switch (response.status) {
|
|
109
120
|
case 401:
|
|
110
121
|
throw new AuthError(message);
|
|
@@ -165,6 +176,1135 @@ function createStoreResource(fetcher) {
|
|
|
165
176
|
};
|
|
166
177
|
}
|
|
167
178
|
|
|
179
|
+
// src/resources/products.ts
|
|
180
|
+
function createProductsResource(fetcher) {
|
|
181
|
+
return {
|
|
182
|
+
/**
|
|
183
|
+
* Get latest products ordered by creation date (newest first).
|
|
184
|
+
*
|
|
185
|
+
* @param take - Number of products to return (required, must be >= 1)
|
|
186
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
187
|
+
* @returns Array of products
|
|
188
|
+
*
|
|
189
|
+
* @example Basic usage
|
|
190
|
+
* ```typescript
|
|
191
|
+
* const products = await client.products.latest(6);
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @example Next.js - with caching
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const products = await client.products.latest(6, {
|
|
197
|
+
* next: { revalidate: 3600, tags: ['products'] }
|
|
198
|
+
* });
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
async latest(take, options) {
|
|
202
|
+
return fetcher.request("/api/storefront/v1/latest-products", {
|
|
203
|
+
params: { take },
|
|
204
|
+
...options
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
/**
|
|
208
|
+
* Get a single product by its URL slug.
|
|
209
|
+
*
|
|
210
|
+
* @param slug - Product URL slug
|
|
211
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
212
|
+
* @returns Full product details including categories and variations
|
|
213
|
+
* @throws NotFoundError if product doesn't exist or is not visible
|
|
214
|
+
*
|
|
215
|
+
* @example Basic usage
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const product = await client.products.getBySlug('my-product');
|
|
218
|
+
* console.log(product.name, product.categories);
|
|
219
|
+
* ```
|
|
220
|
+
*
|
|
221
|
+
* @example Next.js - with caching
|
|
222
|
+
* ```typescript
|
|
223
|
+
* const product = await client.products.getBySlug('my-product', {
|
|
224
|
+
* next: { revalidate: 3600, tags: ['product', 'my-product'] }
|
|
225
|
+
* });
|
|
226
|
+
* ```
|
|
227
|
+
*/
|
|
228
|
+
async getBySlug(slug, options) {
|
|
229
|
+
return fetcher.request(
|
|
230
|
+
`/api/storefront/v1/product/${encodeURIComponent(slug)}`,
|
|
231
|
+
{
|
|
232
|
+
...options
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
/**
|
|
237
|
+
* Get total product count, optionally filtered by category.
|
|
238
|
+
*
|
|
239
|
+
* @param slugs - Optional category slugs to filter by
|
|
240
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
241
|
+
* @returns Object with count property
|
|
242
|
+
*
|
|
243
|
+
* @example Get total count
|
|
244
|
+
* ```typescript
|
|
245
|
+
* const { count } = await client.products.count();
|
|
246
|
+
* console.log(`Total products: ${count}`);
|
|
247
|
+
* ```
|
|
248
|
+
*
|
|
249
|
+
* @example Get count for specific category
|
|
250
|
+
* ```typescript
|
|
251
|
+
* const { count } = await client.products.count(['shoes']);
|
|
252
|
+
* console.log(`Products in shoes: ${count}`);
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
async count(slugs, options) {
|
|
256
|
+
const searchParams = new URLSearchParams();
|
|
257
|
+
if (slugs?.length) {
|
|
258
|
+
slugs.forEach((s) => searchParams.append("slugs", s));
|
|
259
|
+
}
|
|
260
|
+
const query = searchParams.toString();
|
|
261
|
+
const endpoint = `/api/storefront/v1/products-count${query ? `?${query}` : ""}`;
|
|
262
|
+
return fetcher.request(endpoint, {
|
|
263
|
+
...options
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
/**
|
|
267
|
+
* Get sorted products with pagination.
|
|
268
|
+
* Uses optimized sorting with pre-computed effective prices.
|
|
269
|
+
*
|
|
270
|
+
* @param params - Query parameters (slugs, page, pageSize, sort)
|
|
271
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
272
|
+
* @returns Products list with totalCount for pagination
|
|
273
|
+
*
|
|
274
|
+
* @example Basic usage
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const { products, totalCount } = await client.products.sorted({
|
|
277
|
+
* page: 1,
|
|
278
|
+
* pageSize: 12,
|
|
279
|
+
* sort: 'newest'
|
|
280
|
+
* });
|
|
281
|
+
* ```
|
|
282
|
+
*
|
|
283
|
+
* @example Filter by category
|
|
284
|
+
* ```typescript
|
|
285
|
+
* const { products, totalCount } = await client.products.sorted({
|
|
286
|
+
* slugs: ['shoes', 'clothing'],
|
|
287
|
+
* page: 1,
|
|
288
|
+
* pageSize: 24,
|
|
289
|
+
* sort: 'price_asc'
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* @example Real-time data (no cache)
|
|
294
|
+
* ```typescript
|
|
295
|
+
* const data = await client.products.sorted(
|
|
296
|
+
* { page: 1, pageSize: 12 },
|
|
297
|
+
* { cache: 'no-store' }
|
|
298
|
+
* );
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
async sorted(params = {}, options) {
|
|
302
|
+
const searchParams = new URLSearchParams();
|
|
303
|
+
if (params.page) searchParams.set("page", params.page.toString());
|
|
304
|
+
if (params.pageSize)
|
|
305
|
+
searchParams.set("pageSize", params.pageSize.toString());
|
|
306
|
+
if (params.sort) searchParams.set("sort", params.sort);
|
|
307
|
+
if (params.slugs?.length) {
|
|
308
|
+
params.slugs.forEach((s) => searchParams.append("slugs", s));
|
|
309
|
+
}
|
|
310
|
+
const query = searchParams.toString();
|
|
311
|
+
const endpoint = `/api/storefront/v1/sorted-products${query ? `?${query}` : ""}`;
|
|
312
|
+
return fetcher.request(endpoint, {
|
|
313
|
+
...options
|
|
314
|
+
});
|
|
315
|
+
},
|
|
316
|
+
/**
|
|
317
|
+
* Get filtered products with pagination.
|
|
318
|
+
* Similar to sorted() but without totalCount in response.
|
|
319
|
+
* Useful for sitemaps and bulk fetches.
|
|
320
|
+
*
|
|
321
|
+
* @param params - Query parameters (slugs, page, pageSize, sort)
|
|
322
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
323
|
+
* @returns Products list with category name
|
|
324
|
+
*
|
|
325
|
+
* @example Fetch all products for sitemap
|
|
326
|
+
* ```typescript
|
|
327
|
+
* const { products } = await client.products.filtered({
|
|
328
|
+
* slugs: ['all-products'],
|
|
329
|
+
* page: 1,
|
|
330
|
+
* pageSize: 1000
|
|
331
|
+
* });
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
async filtered(params = {}, options) {
|
|
335
|
+
const searchParams = new URLSearchParams();
|
|
336
|
+
if (params.page) searchParams.set("page", params.page.toString());
|
|
337
|
+
if (params.pageSize)
|
|
338
|
+
searchParams.set("pageSize", params.pageSize.toString());
|
|
339
|
+
if (params.sort) searchParams.set("sort", params.sort);
|
|
340
|
+
if (params.slugs?.length) {
|
|
341
|
+
params.slugs.forEach((s) => searchParams.append("slugs", s));
|
|
342
|
+
}
|
|
343
|
+
const query = searchParams.toString();
|
|
344
|
+
const endpoint = `/api/storefront/v1/filtered-products${query ? `?${query}` : ""}`;
|
|
345
|
+
return fetcher.request(
|
|
346
|
+
endpoint,
|
|
347
|
+
{
|
|
348
|
+
...options
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/resources/categories.ts
|
|
356
|
+
function createCategoriesResource(fetcher) {
|
|
357
|
+
return {
|
|
358
|
+
/**
|
|
359
|
+
* Get all top-level categories with nested children.
|
|
360
|
+
* Returns a hierarchical tree of categories (up to 5 levels deep).
|
|
361
|
+
*
|
|
362
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
363
|
+
* @returns Array of top-level categories with nested children
|
|
364
|
+
*
|
|
365
|
+
* @example Basic usage
|
|
366
|
+
* ```typescript
|
|
367
|
+
* const categories = await client.categories.list();
|
|
368
|
+
* categories.forEach(cat => {
|
|
369
|
+
* console.log(cat.name, cat.children.length);
|
|
370
|
+
* });
|
|
371
|
+
* ```
|
|
372
|
+
*
|
|
373
|
+
* @example Next.js - with caching
|
|
374
|
+
* ```typescript
|
|
375
|
+
* const categories = await client.categories.list({
|
|
376
|
+
* next: { revalidate: 3600, tags: ['categories'] }
|
|
377
|
+
* });
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
async list(options) {
|
|
381
|
+
return fetcher.request("/api/storefront/v1/categories", {
|
|
382
|
+
...options
|
|
383
|
+
});
|
|
384
|
+
},
|
|
385
|
+
/**
|
|
386
|
+
* Get a single category by its URL slug.
|
|
387
|
+
*
|
|
388
|
+
* @param slug - Category URL slug
|
|
389
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
390
|
+
* @returns Category data
|
|
391
|
+
* @throws NotFoundError if category doesn't exist
|
|
392
|
+
*
|
|
393
|
+
* @example Basic usage
|
|
394
|
+
* ```typescript
|
|
395
|
+
* const { category } = await client.categories.getBySlug('shoes');
|
|
396
|
+
* console.log(category.name);
|
|
397
|
+
* ```
|
|
398
|
+
*
|
|
399
|
+
* @example Next.js - with caching
|
|
400
|
+
* ```typescript
|
|
401
|
+
* const { category } = await client.categories.getBySlug('shoes', {
|
|
402
|
+
* next: { revalidate: 86400, tags: ['category', 'shoes'] }
|
|
403
|
+
* });
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
async getBySlug(slug, options) {
|
|
407
|
+
return fetcher.request(
|
|
408
|
+
`/api/storefront/v1/categories/${encodeURIComponent(slug)}`,
|
|
409
|
+
{
|
|
410
|
+
...options
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/resources/cart.ts
|
|
418
|
+
function buildCartHeaders(options) {
|
|
419
|
+
const headers = {};
|
|
420
|
+
if (options?.cartId) {
|
|
421
|
+
headers["x-cart-id"] = options.cartId;
|
|
422
|
+
}
|
|
423
|
+
if (options?.sessionId) {
|
|
424
|
+
headers["x-session-id"] = options.sessionId;
|
|
425
|
+
}
|
|
426
|
+
return headers;
|
|
427
|
+
}
|
|
428
|
+
function createCartResource(fetcher) {
|
|
429
|
+
return {
|
|
430
|
+
/**
|
|
431
|
+
* Fetch the current cart contents.
|
|
432
|
+
*
|
|
433
|
+
* @param options - Cart session options (cartId for guests, sessionId for logged-in users)
|
|
434
|
+
* @param fetchOptions - Fetch options (caching, headers, etc.)
|
|
435
|
+
* @returns Cart items and cartId (for guests)
|
|
436
|
+
*
|
|
437
|
+
* @example Guest user
|
|
438
|
+
* ```typescript
|
|
439
|
+
* const cartId = localStorage.getItem('cart-id');
|
|
440
|
+
* const { items, cartId: newCartId } = await client.cart.get({ cartId });
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* @example Logged-in user
|
|
444
|
+
* ```typescript
|
|
445
|
+
* const { items } = await client.cart.get({ sessionId });
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
async get(options, fetchOptions) {
|
|
449
|
+
return fetcher.request("/api/storefront/v1/cart", {
|
|
450
|
+
method: "GET",
|
|
451
|
+
headers: buildCartHeaders(options),
|
|
452
|
+
...fetchOptions
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
/**
|
|
456
|
+
* Add an item to the cart.
|
|
457
|
+
* If the item already exists, quantity is incremented.
|
|
458
|
+
*
|
|
459
|
+
* @param params - Add to cart parameters
|
|
460
|
+
* @param fetchOptions - Fetch options
|
|
461
|
+
* @returns Updated cart with new cartId for guests
|
|
462
|
+
* @throws ValidationError if quantity exceeds available stock
|
|
463
|
+
*
|
|
464
|
+
* @example Add product
|
|
465
|
+
* ```typescript
|
|
466
|
+
* const { items, cartId } = await client.cart.addItem({
|
|
467
|
+
* cartId: existingCartId,
|
|
468
|
+
* productId: 'prod_123',
|
|
469
|
+
* quantity: 2
|
|
470
|
+
* });
|
|
471
|
+
* // Save cartId for future requests
|
|
472
|
+
* localStorage.setItem('cart-id', cartId);
|
|
473
|
+
* ```
|
|
474
|
+
*
|
|
475
|
+
* @example Add product with variation
|
|
476
|
+
* ```typescript
|
|
477
|
+
* const { items, cartId } = await client.cart.addItem({
|
|
478
|
+
* cartId,
|
|
479
|
+
* productId: 'prod_123',
|
|
480
|
+
* variationId: 'var_456',
|
|
481
|
+
* quantity: 1
|
|
482
|
+
* });
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
485
|
+
async addItem(params, fetchOptions) {
|
|
486
|
+
const { cartId, sessionId, ...body } = params;
|
|
487
|
+
return fetcher.request("/api/storefront/v1/cart", {
|
|
488
|
+
method: "POST",
|
|
489
|
+
headers: buildCartHeaders({ sessionId }),
|
|
490
|
+
body: {
|
|
491
|
+
cartId,
|
|
492
|
+
...body
|
|
493
|
+
},
|
|
494
|
+
...fetchOptions
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
/**
|
|
498
|
+
* Update item quantity by delta (atomic operation).
|
|
499
|
+
* Use positive delta to increase, negative to decrease.
|
|
500
|
+
* Minimum quantity is 1 (use removeItem to delete).
|
|
501
|
+
*
|
|
502
|
+
* @param params - Update quantity parameters
|
|
503
|
+
* @param fetchOptions - Fetch options
|
|
504
|
+
* @returns Updated cart
|
|
505
|
+
* @throws NotFoundError if item not in cart or quantity would go below 1
|
|
506
|
+
*
|
|
507
|
+
* @example Increment quantity
|
|
508
|
+
* ```typescript
|
|
509
|
+
* const { items } = await client.cart.updateQuantity({
|
|
510
|
+
* cartId,
|
|
511
|
+
* productId: 'prod_123',
|
|
512
|
+
* delta: 1
|
|
513
|
+
* });
|
|
514
|
+
* ```
|
|
515
|
+
*
|
|
516
|
+
* @example Decrement quantity
|
|
517
|
+
* ```typescript
|
|
518
|
+
* const { items } = await client.cart.updateQuantity({
|
|
519
|
+
* cartId,
|
|
520
|
+
* productId: 'prod_123',
|
|
521
|
+
* variationId: 'var_456',
|
|
522
|
+
* delta: -1
|
|
523
|
+
* });
|
|
524
|
+
* ```
|
|
525
|
+
*/
|
|
526
|
+
async updateQuantity(params, fetchOptions) {
|
|
527
|
+
const { cartId, sessionId, ...body } = params;
|
|
528
|
+
return fetcher.request("/api/storefront/v1/cart", {
|
|
529
|
+
method: "PATCH",
|
|
530
|
+
headers: buildCartHeaders({ sessionId }),
|
|
531
|
+
body: {
|
|
532
|
+
cartId,
|
|
533
|
+
...body
|
|
534
|
+
},
|
|
535
|
+
...fetchOptions
|
|
536
|
+
});
|
|
537
|
+
},
|
|
538
|
+
/**
|
|
539
|
+
* Remove an item from the cart.
|
|
540
|
+
*
|
|
541
|
+
* @param params - Remove from cart parameters
|
|
542
|
+
* @param fetchOptions - Fetch options
|
|
543
|
+
* @returns Updated cart
|
|
544
|
+
*
|
|
545
|
+
* @example Remove product
|
|
546
|
+
* ```typescript
|
|
547
|
+
* const { items } = await client.cart.removeItem({
|
|
548
|
+
* cartId,
|
|
549
|
+
* productId: 'prod_123'
|
|
550
|
+
* });
|
|
551
|
+
* ```
|
|
552
|
+
*
|
|
553
|
+
* @example Remove variation
|
|
554
|
+
* ```typescript
|
|
555
|
+
* const { items } = await client.cart.removeItem({
|
|
556
|
+
* cartId,
|
|
557
|
+
* productId: 'prod_123',
|
|
558
|
+
* variationId: 'var_456'
|
|
559
|
+
* });
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
async removeItem(params, fetchOptions) {
|
|
563
|
+
const { cartId, sessionId, ...body } = params;
|
|
564
|
+
return fetcher.request("/api/storefront/v1/cart", {
|
|
565
|
+
method: "DELETE",
|
|
566
|
+
headers: buildCartHeaders({ sessionId }),
|
|
567
|
+
body: {
|
|
568
|
+
cartId,
|
|
569
|
+
...body
|
|
570
|
+
},
|
|
571
|
+
...fetchOptions
|
|
572
|
+
});
|
|
573
|
+
},
|
|
574
|
+
/**
|
|
575
|
+
* Validate cart before checkout.
|
|
576
|
+
* Checks product availability, stock levels, and prices.
|
|
577
|
+
* Auto-fixes issues (removes unavailable items, adjusts quantities).
|
|
578
|
+
*
|
|
579
|
+
* @param options - Cart session options
|
|
580
|
+
* @param fetchOptions - Fetch options
|
|
581
|
+
* @returns Validated cart with change metadata
|
|
582
|
+
*
|
|
583
|
+
* @example Validate before checkout
|
|
584
|
+
* ```typescript
|
|
585
|
+
* const { items, hasChanges, changes } = await client.cart.validate({ cartId });
|
|
586
|
+
*
|
|
587
|
+
* if (hasChanges) {
|
|
588
|
+
* if (changes.removedItems > 0) {
|
|
589
|
+
* notify('Some items were removed (out of stock)');
|
|
590
|
+
* }
|
|
591
|
+
* if (changes.quantityAdjusted > 0) {
|
|
592
|
+
* notify('Some quantities were adjusted');
|
|
593
|
+
* }
|
|
594
|
+
* if (changes.priceChanged > 0) {
|
|
595
|
+
* notify('Some prices have changed');
|
|
596
|
+
* }
|
|
597
|
+
* }
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
async validate(options, fetchOptions) {
|
|
601
|
+
return fetcher.request(
|
|
602
|
+
"/api/storefront/v1/cart/validate",
|
|
603
|
+
{
|
|
604
|
+
method: "GET",
|
|
605
|
+
headers: buildCartHeaders(options),
|
|
606
|
+
...fetchOptions
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/resources/shipping.ts
|
|
614
|
+
function createShippingResource(fetcher) {
|
|
615
|
+
return {
|
|
616
|
+
/**
|
|
617
|
+
* Get all available shipment methods for the store.
|
|
618
|
+
* Returns methods without pickup locations - use `getWithLocations` for postal code specific data.
|
|
619
|
+
*
|
|
620
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
621
|
+
* @returns Available shipment methods
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* ```typescript
|
|
625
|
+
* const { shipmentMethods } = await client.shipping.getMethods();
|
|
626
|
+
*
|
|
627
|
+
* shipmentMethods.forEach(method => {
|
|
628
|
+
* console.log(`${method.name}: ${method.price / 100}€`);
|
|
629
|
+
* });
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
async getMethods(options) {
|
|
633
|
+
return fetcher.request(
|
|
634
|
+
"/api/storefront/v1/shipment-methods",
|
|
635
|
+
{
|
|
636
|
+
method: "GET",
|
|
637
|
+
...options
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
},
|
|
641
|
+
/**
|
|
642
|
+
* Get shipment methods with pickup locations for a specific postal code.
|
|
643
|
+
* Calls the Shipit API to fetch nearby pickup points (parcel lockers, etc.)
|
|
644
|
+
*
|
|
645
|
+
* @param postalCode - Customer's postal code (e.g., "00100")
|
|
646
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
647
|
+
* @returns Shipment methods and nearby pickup locations with pricing
|
|
648
|
+
*
|
|
649
|
+
* @example
|
|
650
|
+
* ```typescript
|
|
651
|
+
* const { shipmentMethods, pricedLocations } = await client.shipping.getWithLocations("00100");
|
|
652
|
+
*
|
|
653
|
+
* // Show pickup locations
|
|
654
|
+
* pricedLocations.forEach(location => {
|
|
655
|
+
* console.log(`${location.name} - ${location.carrier}`);
|
|
656
|
+
* console.log(` ${location.address1}, ${location.city}`);
|
|
657
|
+
* console.log(` ${location.distanceInKilometers.toFixed(1)} km away`);
|
|
658
|
+
* console.log(` Price: ${(location.merchantPrice ?? 0) / 100}€`);
|
|
659
|
+
* });
|
|
660
|
+
* ```
|
|
661
|
+
*
|
|
662
|
+
* @example Filter by carrier
|
|
663
|
+
* ```typescript
|
|
664
|
+
* const { pricedLocations } = await client.shipping.getWithLocations("00100");
|
|
665
|
+
*
|
|
666
|
+
* const postiLocations = pricedLocations.filter(
|
|
667
|
+
* loc => loc.carrier === "Posti"
|
|
668
|
+
* );
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
async getWithLocations(postalCode, options) {
|
|
672
|
+
return fetcher.request(
|
|
673
|
+
`/api/storefront/v1/shipment-methods/${encodeURIComponent(postalCode)}`,
|
|
674
|
+
{
|
|
675
|
+
method: "GET",
|
|
676
|
+
...options
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/resources/customer.ts
|
|
684
|
+
function buildSessionHeaders(sessionId) {
|
|
685
|
+
return { "x-session-id": sessionId };
|
|
686
|
+
}
|
|
687
|
+
function createCustomerResource(fetcher) {
|
|
688
|
+
return {
|
|
689
|
+
/**
|
|
690
|
+
* Register a new customer account.
|
|
691
|
+
* After registration, the customer must verify their email before logging in.
|
|
692
|
+
*
|
|
693
|
+
* @param data - Registration data (firstName, lastName, email, password)
|
|
694
|
+
* @param fetchOptions - Fetch options
|
|
695
|
+
* @returns Created customer with verification token
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```typescript
|
|
699
|
+
* const { customer, message } = await client.customer.register({
|
|
700
|
+
* firstName: 'John',
|
|
701
|
+
* lastName: 'Doe',
|
|
702
|
+
* email: 'john@example.com',
|
|
703
|
+
* password: 'securePassword123'
|
|
704
|
+
* });
|
|
705
|
+
*
|
|
706
|
+
* // Send verification email using customer.emailVerificationToken
|
|
707
|
+
* console.log('Account created:', message);
|
|
708
|
+
* ```
|
|
709
|
+
*/
|
|
710
|
+
async register(data, fetchOptions) {
|
|
711
|
+
return fetcher.request(
|
|
712
|
+
"/api/storefront/v1/customer/(auth)/register",
|
|
713
|
+
{
|
|
714
|
+
method: "POST",
|
|
715
|
+
body: data,
|
|
716
|
+
...fetchOptions
|
|
717
|
+
}
|
|
718
|
+
);
|
|
719
|
+
},
|
|
720
|
+
/**
|
|
721
|
+
* Log in an existing customer.
|
|
722
|
+
* Returns a session ID that must be stored and passed to authenticated endpoints.
|
|
723
|
+
*
|
|
724
|
+
* @param email - Customer's email address
|
|
725
|
+
* @param password - Customer's password
|
|
726
|
+
* @param options - Login options (optional cartId for cart merging)
|
|
727
|
+
* @param fetchOptions - Fetch options
|
|
728
|
+
* @returns Session ID and customer data
|
|
729
|
+
* @throws ValidationError if email is not verified (check error for requiresVerification)
|
|
730
|
+
*
|
|
731
|
+
* @example
|
|
732
|
+
* ```typescript
|
|
733
|
+
* try {
|
|
734
|
+
* const { sessionId, customer, expiresAt } = await client.customer.login(
|
|
735
|
+
* 'john@example.com',
|
|
736
|
+
* 'securePassword123',
|
|
737
|
+
* { cartId: guestCartId } // Optional: merge guest cart
|
|
738
|
+
* );
|
|
739
|
+
*
|
|
740
|
+
* // Store sessionId in a cookie
|
|
741
|
+
* cookies().set('session-id', sessionId, {
|
|
742
|
+
* httpOnly: true,
|
|
743
|
+
* expires: new Date(expiresAt)
|
|
744
|
+
* });
|
|
745
|
+
* } catch (error) {
|
|
746
|
+
* if (error.requiresVerification) {
|
|
747
|
+
* // Prompt user to verify email
|
|
748
|
+
* await client.customer.resendVerification(error.customerId);
|
|
749
|
+
* }
|
|
750
|
+
* }
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
async login(email, password, options, fetchOptions) {
|
|
754
|
+
const headers = {};
|
|
755
|
+
if (options?.cartId) {
|
|
756
|
+
headers["x-cart-id"] = options.cartId;
|
|
757
|
+
}
|
|
758
|
+
return fetcher.request(
|
|
759
|
+
"/api/storefront/v1/customer/(auth)/login",
|
|
760
|
+
{
|
|
761
|
+
method: "POST",
|
|
762
|
+
body: { email, password },
|
|
763
|
+
headers,
|
|
764
|
+
...fetchOptions
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
},
|
|
768
|
+
/**
|
|
769
|
+
* Log out the current customer and invalidate their session.
|
|
770
|
+
* If the customer had items in their cart, they are migrated to a new guest cart.
|
|
771
|
+
*
|
|
772
|
+
* @param sessionId - The customer's session ID
|
|
773
|
+
* @param fetchOptions - Fetch options
|
|
774
|
+
* @returns Logout confirmation with optional new guest cart ID
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* ```typescript
|
|
778
|
+
* const { cartId } = await client.customer.logout(sessionId);
|
|
779
|
+
*
|
|
780
|
+
* // Clear session cookie
|
|
781
|
+
* cookies().delete('session-id');
|
|
782
|
+
*
|
|
783
|
+
* // If cart was migrated, store the new guest cart ID
|
|
784
|
+
* if (cartId) {
|
|
785
|
+
* cookies().set('cart-id', cartId);
|
|
786
|
+
* }
|
|
787
|
+
* ```
|
|
788
|
+
*/
|
|
789
|
+
async logout(sessionId, fetchOptions) {
|
|
790
|
+
return fetcher.request(
|
|
791
|
+
"/api/storefront/v1/customer/(auth)/logout",
|
|
792
|
+
{
|
|
793
|
+
method: "POST",
|
|
794
|
+
headers: buildSessionHeaders(sessionId),
|
|
795
|
+
...fetchOptions
|
|
796
|
+
}
|
|
797
|
+
);
|
|
798
|
+
},
|
|
799
|
+
/**
|
|
800
|
+
* Get the currently authenticated customer's profile.
|
|
801
|
+
*
|
|
802
|
+
* @param sessionId - The customer's session ID
|
|
803
|
+
* @param fetchOptions - Fetch options
|
|
804
|
+
* @returns Current customer data
|
|
805
|
+
* @throws AuthError if session is invalid or expired
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```typescript
|
|
809
|
+
* const sessionId = cookies().get('session-id')?.value;
|
|
810
|
+
* if (sessionId) {
|
|
811
|
+
* const { customer } = await client.customer.getUser(sessionId);
|
|
812
|
+
* console.log(`Welcome back, ${customer.firstName}!`);
|
|
813
|
+
* }
|
|
814
|
+
* ```
|
|
815
|
+
*/
|
|
816
|
+
async getUser(sessionId, fetchOptions) {
|
|
817
|
+
return fetcher.request(
|
|
818
|
+
"/api/storefront/v1/customer/(auth)/get-user",
|
|
819
|
+
{
|
|
820
|
+
method: "GET",
|
|
821
|
+
headers: buildSessionHeaders(sessionId),
|
|
822
|
+
...fetchOptions
|
|
823
|
+
}
|
|
824
|
+
);
|
|
825
|
+
},
|
|
826
|
+
/**
|
|
827
|
+
* Verify a customer's email address using the token sent during registration.
|
|
828
|
+
*
|
|
829
|
+
* @param token - Email verification token
|
|
830
|
+
* @param fetchOptions - Fetch options
|
|
831
|
+
* @returns Verification confirmation
|
|
832
|
+
* @throws ValidationError if token is invalid or expired
|
|
833
|
+
*
|
|
834
|
+
* @example
|
|
835
|
+
* ```typescript
|
|
836
|
+
* // Token comes from the verification email link
|
|
837
|
+
* const token = searchParams.get('token');
|
|
838
|
+
*
|
|
839
|
+
* const { message } = await client.customer.verifyEmail(token);
|
|
840
|
+
* console.log(message); // "Email verified successfully. You can now log in."
|
|
841
|
+
* ```
|
|
842
|
+
*/
|
|
843
|
+
async verifyEmail(token, fetchOptions) {
|
|
844
|
+
return fetcher.request(
|
|
845
|
+
"/api/storefront/v1/customer/(auth)/verify-email",
|
|
846
|
+
{
|
|
847
|
+
method: "GET",
|
|
848
|
+
params: { token },
|
|
849
|
+
...fetchOptions
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
},
|
|
853
|
+
/**
|
|
854
|
+
* Resend the email verification token for an unverified customer.
|
|
855
|
+
* Generates a new token valid for 24 hours.
|
|
856
|
+
*
|
|
857
|
+
* @param customerId - The customer's ID (from failed login response)
|
|
858
|
+
* @param fetchOptions - Fetch options
|
|
859
|
+
* @returns Updated customer with new verification token
|
|
860
|
+
* @throws ValidationError if customer is already verified or not found
|
|
861
|
+
*
|
|
862
|
+
* @example
|
|
863
|
+
* ```typescript
|
|
864
|
+
* // After login fails with requiresVerification
|
|
865
|
+
* const { customer } = await client.customer.resendVerification(customerId);
|
|
866
|
+
*
|
|
867
|
+
* // Send new verification email using customer.emailVerificationToken
|
|
868
|
+
* await sendVerificationEmail(customer.email, customer.emailVerificationToken);
|
|
869
|
+
* ```
|
|
870
|
+
*/
|
|
871
|
+
async resendVerification(customerId, fetchOptions) {
|
|
872
|
+
return fetcher.request(
|
|
873
|
+
"/api/storefront/v1/customer/(auth)/resend-verification",
|
|
874
|
+
{
|
|
875
|
+
method: "POST",
|
|
876
|
+
body: { customerId },
|
|
877
|
+
...fetchOptions
|
|
878
|
+
}
|
|
879
|
+
);
|
|
880
|
+
},
|
|
881
|
+
// =========================================================================
|
|
882
|
+
// Profile Management Methods
|
|
883
|
+
// =========================================================================
|
|
884
|
+
/**
|
|
885
|
+
* Update the authenticated customer's profile.
|
|
886
|
+
*
|
|
887
|
+
* @param sessionId - The customer's session ID
|
|
888
|
+
* @param data - Profile data to update (firstName, lastName, email)
|
|
889
|
+
* @param fetchOptions - Fetch options
|
|
890
|
+
* @returns Updated customer data
|
|
891
|
+
* @throws AuthError if session is invalid
|
|
892
|
+
* @throws ValidationError if email is already taken by another customer
|
|
893
|
+
*
|
|
894
|
+
* @example
|
|
895
|
+
* ```typescript
|
|
896
|
+
* const { customer } = await client.customer.updateProfile(sessionId, {
|
|
897
|
+
* firstName: 'Jane',
|
|
898
|
+
* lastName: 'Smith',
|
|
899
|
+
* email: 'jane.smith@example.com'
|
|
900
|
+
* });
|
|
901
|
+
*
|
|
902
|
+
* console.log('Profile updated:', customer.email);
|
|
903
|
+
* ```
|
|
904
|
+
*/
|
|
905
|
+
async updateProfile(sessionId, data, fetchOptions) {
|
|
906
|
+
return fetcher.request(
|
|
907
|
+
"/api/storefront/v1/customer/edit-user",
|
|
908
|
+
{
|
|
909
|
+
method: "PATCH",
|
|
910
|
+
headers: buildSessionHeaders(sessionId),
|
|
911
|
+
body: data,
|
|
912
|
+
...fetchOptions
|
|
913
|
+
}
|
|
914
|
+
);
|
|
915
|
+
},
|
|
916
|
+
/**
|
|
917
|
+
* Delete the authenticated customer's account.
|
|
918
|
+
* This action is permanent and cannot be undone.
|
|
919
|
+
* All associated data (sessions, wishlist, etc.) will be deleted.
|
|
920
|
+
*
|
|
921
|
+
* @param sessionId - The customer's session ID
|
|
922
|
+
* @param fetchOptions - Fetch options
|
|
923
|
+
* @returns Deletion confirmation
|
|
924
|
+
* @throws AuthError if session is invalid
|
|
925
|
+
*
|
|
926
|
+
* @example
|
|
927
|
+
* ```typescript
|
|
928
|
+
* // Confirm with user before calling
|
|
929
|
+
* if (confirm('Are you sure you want to delete your account?')) {
|
|
930
|
+
* await client.customer.deleteAccount(sessionId);
|
|
931
|
+
*
|
|
932
|
+
* // Clear session cookie
|
|
933
|
+
* cookies().delete('session-id');
|
|
934
|
+
*
|
|
935
|
+
* // Redirect to home page
|
|
936
|
+
* redirect('/');
|
|
937
|
+
* }
|
|
938
|
+
* ```
|
|
939
|
+
*/
|
|
940
|
+
async deleteAccount(sessionId, fetchOptions) {
|
|
941
|
+
return fetcher.request(
|
|
942
|
+
"/api/storefront/v1/customer/delete-user",
|
|
943
|
+
{
|
|
944
|
+
method: "DELETE",
|
|
945
|
+
headers: buildSessionHeaders(sessionId),
|
|
946
|
+
...fetchOptions
|
|
947
|
+
}
|
|
948
|
+
);
|
|
949
|
+
},
|
|
950
|
+
/**
|
|
951
|
+
* Get the customer's order history.
|
|
952
|
+
* Returns all orders with line items and product details.
|
|
953
|
+
*
|
|
954
|
+
* @param sessionId - The customer's session ID
|
|
955
|
+
* @param customerId - The customer's ID
|
|
956
|
+
* @param fetchOptions - Fetch options
|
|
957
|
+
* @returns List of customer orders
|
|
958
|
+
*
|
|
959
|
+
* @example
|
|
960
|
+
* ```typescript
|
|
961
|
+
* const { orders } = await client.customer.getOrders(sessionId, customerId);
|
|
962
|
+
*
|
|
963
|
+
* orders.forEach(order => {
|
|
964
|
+
* console.log(`Order #${order.orderNumber}: ${order.status}`);
|
|
965
|
+
* order.OrderLineItems.forEach(item => {
|
|
966
|
+
* console.log(` - ${item.name} x${item.quantity}`);
|
|
967
|
+
* });
|
|
968
|
+
* });
|
|
969
|
+
* ```
|
|
970
|
+
*/
|
|
971
|
+
async getOrders(sessionId, customerId, fetchOptions) {
|
|
972
|
+
return fetcher.request(
|
|
973
|
+
`/api/storefront/v1/customer/get-orders/${customerId}`,
|
|
974
|
+
{
|
|
975
|
+
method: "GET",
|
|
976
|
+
headers: buildSessionHeaders(sessionId),
|
|
977
|
+
...fetchOptions
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
},
|
|
981
|
+
// =========================================================================
|
|
982
|
+
// Wishlist (Nested Resource)
|
|
983
|
+
// =========================================================================
|
|
984
|
+
/**
|
|
985
|
+
* Wishlist management methods.
|
|
986
|
+
* Access via `client.customer.wishlist.get()`, `.add()`, `.remove()`.
|
|
987
|
+
*/
|
|
988
|
+
wishlist: {
|
|
989
|
+
/**
|
|
990
|
+
* Get the customer's wishlist.
|
|
991
|
+
* Returns all wishlist items with product and variation details.
|
|
992
|
+
*
|
|
993
|
+
* @param sessionId - The customer's session ID
|
|
994
|
+
* @param fetchOptions - Fetch options
|
|
995
|
+
* @returns Wishlist items with product details
|
|
996
|
+
* @throws AuthError if session is invalid
|
|
997
|
+
*
|
|
998
|
+
* @example
|
|
999
|
+
* ```typescript
|
|
1000
|
+
* const { items } = await client.customer.wishlist.get(sessionId);
|
|
1001
|
+
*
|
|
1002
|
+
* items.forEach(item => {
|
|
1003
|
+
* console.log(`${item.product.name} - $${item.product.price / 100}`);
|
|
1004
|
+
* if (item.variation) {
|
|
1005
|
+
* const options = item.variation.options
|
|
1006
|
+
* .map(o => `${o.optionType.name}: ${o.value}`)
|
|
1007
|
+
* .join(', ');
|
|
1008
|
+
* console.log(` Variant: ${options}`);
|
|
1009
|
+
* }
|
|
1010
|
+
* });
|
|
1011
|
+
* ```
|
|
1012
|
+
*/
|
|
1013
|
+
async get(sessionId, fetchOptions) {
|
|
1014
|
+
return fetcher.request(
|
|
1015
|
+
"/api/storefront/v1/customer/wishlist",
|
|
1016
|
+
{
|
|
1017
|
+
method: "GET",
|
|
1018
|
+
headers: buildSessionHeaders(sessionId),
|
|
1019
|
+
...fetchOptions
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
},
|
|
1023
|
+
/**
|
|
1024
|
+
* Add a product to the customer's wishlist.
|
|
1025
|
+
*
|
|
1026
|
+
* @param sessionId - The customer's session ID
|
|
1027
|
+
* @param productId - The product ID to add
|
|
1028
|
+
* @param variationId - Optional variation ID (for products with variations)
|
|
1029
|
+
* @param fetchOptions - Fetch options
|
|
1030
|
+
* @returns Success message
|
|
1031
|
+
* @throws AuthError if session is invalid
|
|
1032
|
+
* @throws ValidationError if product already in wishlist
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* ```typescript
|
|
1036
|
+
* // Add a simple product
|
|
1037
|
+
* await client.customer.wishlist.add(sessionId, 'prod_123');
|
|
1038
|
+
*
|
|
1039
|
+
* // Add a product with a specific variation
|
|
1040
|
+
* await client.customer.wishlist.add(sessionId, 'prod_123', 'var_456');
|
|
1041
|
+
* ```
|
|
1042
|
+
*/
|
|
1043
|
+
async add(sessionId, productId, variationId, fetchOptions) {
|
|
1044
|
+
return fetcher.request(
|
|
1045
|
+
"/api/storefront/v1/customer/wishlist",
|
|
1046
|
+
{
|
|
1047
|
+
method: "POST",
|
|
1048
|
+
headers: buildSessionHeaders(sessionId),
|
|
1049
|
+
body: { productId, variationId },
|
|
1050
|
+
...fetchOptions
|
|
1051
|
+
}
|
|
1052
|
+
);
|
|
1053
|
+
},
|
|
1054
|
+
/**
|
|
1055
|
+
* Remove a product from the customer's wishlist.
|
|
1056
|
+
*
|
|
1057
|
+
* @param sessionId - The customer's session ID
|
|
1058
|
+
* @param productId - The product ID to remove
|
|
1059
|
+
* @param variationId - Optional variation ID (must match if item was added with variation)
|
|
1060
|
+
* @param fetchOptions - Fetch options
|
|
1061
|
+
* @returns Success message
|
|
1062
|
+
* @throws AuthError if session is invalid
|
|
1063
|
+
* @throws NotFoundError if item not in wishlist
|
|
1064
|
+
*
|
|
1065
|
+
* @example
|
|
1066
|
+
* ```typescript
|
|
1067
|
+
* // Remove a simple product
|
|
1068
|
+
* await client.customer.wishlist.remove(sessionId, 'prod_123');
|
|
1069
|
+
*
|
|
1070
|
+
* // Remove a specific variation
|
|
1071
|
+
* await client.customer.wishlist.remove(sessionId, 'prod_123', 'var_456');
|
|
1072
|
+
* ```
|
|
1073
|
+
*/
|
|
1074
|
+
async remove(sessionId, productId, variationId, fetchOptions) {
|
|
1075
|
+
return fetcher.request(
|
|
1076
|
+
"/api/storefront/v1/customer/wishlist",
|
|
1077
|
+
{
|
|
1078
|
+
method: "DELETE",
|
|
1079
|
+
headers: buildSessionHeaders(sessionId),
|
|
1080
|
+
body: { productId, variationId },
|
|
1081
|
+
...fetchOptions
|
|
1082
|
+
}
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/resources/order.ts
|
|
1090
|
+
function createOrderResource(fetcher) {
|
|
1091
|
+
return {
|
|
1092
|
+
/**
|
|
1093
|
+
* Get order details by ID.
|
|
1094
|
+
*
|
|
1095
|
+
* Retrieves complete order information including line items,
|
|
1096
|
+
* customer data, and shipment method with tracking info.
|
|
1097
|
+
*
|
|
1098
|
+
* @param orderId - The order ID to fetch
|
|
1099
|
+
* @param options - Fetch options (caching, headers, etc.)
|
|
1100
|
+
* @returns Complete order details
|
|
1101
|
+
* @throws NotFoundError if order doesn't exist or belongs to different store
|
|
1102
|
+
*
|
|
1103
|
+
* @example Basic usage (order confirmation page)
|
|
1104
|
+
* ```typescript
|
|
1105
|
+
* const order = await client.order.get(orderId);
|
|
1106
|
+
* console.log(`Order #${order.orderNumber} - ${order.status}`);
|
|
1107
|
+
* console.log(`Total: ${order.totalAmount / 100} EUR`);
|
|
1108
|
+
* ```
|
|
1109
|
+
*
|
|
1110
|
+
* @example Next.js - with caching
|
|
1111
|
+
* ```typescript
|
|
1112
|
+
* const order = await client.order.get(orderId, {
|
|
1113
|
+
* next: { revalidate: 60, tags: ['order', orderId] }
|
|
1114
|
+
* });
|
|
1115
|
+
* ```
|
|
1116
|
+
*
|
|
1117
|
+
* @example Display line items
|
|
1118
|
+
* ```typescript
|
|
1119
|
+
* const order = await client.order.get(orderId);
|
|
1120
|
+
* order.OrderLineItems.forEach(item => {
|
|
1121
|
+
* if (item.itemType !== 'SHIPPING') {
|
|
1122
|
+
* console.log(`${item.name} x${item.quantity} = ${item.totalAmount / 100} EUR`);
|
|
1123
|
+
* }
|
|
1124
|
+
* });
|
|
1125
|
+
* ```
|
|
1126
|
+
*
|
|
1127
|
+
* @example Show tracking info
|
|
1128
|
+
* ```typescript
|
|
1129
|
+
* const order = await client.order.get(orderId);
|
|
1130
|
+
* if (order.orderShipmentMethod?.trackingNumber) {
|
|
1131
|
+
* console.log(`Tracking: ${order.orderShipmentMethod.trackingNumber}`);
|
|
1132
|
+
* order.orderShipmentMethod.trackingUrls?.forEach(url => {
|
|
1133
|
+
* console.log(`Track at: ${url}`);
|
|
1134
|
+
* });
|
|
1135
|
+
* }
|
|
1136
|
+
* ```
|
|
1137
|
+
*/
|
|
1138
|
+
async get(orderId, options) {
|
|
1139
|
+
return fetcher.request(
|
|
1140
|
+
`/api/storefront/v1/order/${encodeURIComponent(orderId)}`,
|
|
1141
|
+
{
|
|
1142
|
+
...options
|
|
1143
|
+
}
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/resources/checkout.ts
|
|
1150
|
+
function createCheckoutResource(fetcher) {
|
|
1151
|
+
function buildCheckoutHeaders(options) {
|
|
1152
|
+
const headers = {};
|
|
1153
|
+
if (options?.cartId) {
|
|
1154
|
+
headers["x-cart-id"] = options.cartId;
|
|
1155
|
+
}
|
|
1156
|
+
if (options?.sessionId) {
|
|
1157
|
+
headers["x-session-id"] = options.sessionId;
|
|
1158
|
+
}
|
|
1159
|
+
return headers;
|
|
1160
|
+
}
|
|
1161
|
+
function buildCheckoutBody(params) {
|
|
1162
|
+
return {
|
|
1163
|
+
orderId: params.orderId,
|
|
1164
|
+
chosenShipmentMethod: params.shipmentMethod,
|
|
1165
|
+
customerData: params.customerData,
|
|
1166
|
+
successUrl: params.successUrl,
|
|
1167
|
+
cancelUrl: params.cancelUrl
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
/**
|
|
1172
|
+
* Create a Stripe checkout session.
|
|
1173
|
+
*
|
|
1174
|
+
* Redirects the user to Stripe's hosted checkout page.
|
|
1175
|
+
* Cart items are validated and stock is reserved on the server.
|
|
1176
|
+
*
|
|
1177
|
+
* @param params - Checkout parameters (customer data, shipping, URLs)
|
|
1178
|
+
* @param options - Checkout options including cart/session context
|
|
1179
|
+
* @returns URL to redirect user to Stripe checkout
|
|
1180
|
+
* @throws ValidationError for invalid data or empty cart
|
|
1181
|
+
* @throws StorefrontError for inventory issues
|
|
1182
|
+
*
|
|
1183
|
+
* @example Basic usage with redirect
|
|
1184
|
+
* ```typescript
|
|
1185
|
+
* const { url } = await client.checkout.stripe({
|
|
1186
|
+
* customerData: {
|
|
1187
|
+
* first_name: "John",
|
|
1188
|
+
* last_name: "Doe",
|
|
1189
|
+
* email: "john@example.com",
|
|
1190
|
+
* address: "123 Main St",
|
|
1191
|
+
* postal_code: "00100",
|
|
1192
|
+
* city: "Helsinki",
|
|
1193
|
+
* phone: "+358401234567"
|
|
1194
|
+
* },
|
|
1195
|
+
* shipmentMethod: {
|
|
1196
|
+
* shipmentMethodId: "ship_123",
|
|
1197
|
+
* pickupId: null
|
|
1198
|
+
* },
|
|
1199
|
+
* orderId: "order_abc123",
|
|
1200
|
+
* successUrl: "https://mystore.com/success",
|
|
1201
|
+
* cancelUrl: "https://mystore.com/cancel"
|
|
1202
|
+
* }, {
|
|
1203
|
+
* cartId: "cart_xyz", // For guest users
|
|
1204
|
+
* sessionId: "sess_123" // For logged-in users
|
|
1205
|
+
* });
|
|
1206
|
+
*
|
|
1207
|
+
* // Redirect to Stripe
|
|
1208
|
+
* window.location.href = url;
|
|
1209
|
+
* ```
|
|
1210
|
+
*/
|
|
1211
|
+
async stripe(params, options) {
|
|
1212
|
+
const headers = buildCheckoutHeaders(options);
|
|
1213
|
+
const body = buildCheckoutBody(params);
|
|
1214
|
+
return fetcher.request(
|
|
1215
|
+
"/api/storefront/v1/payments/stripe/checkout",
|
|
1216
|
+
{
|
|
1217
|
+
method: "POST",
|
|
1218
|
+
body,
|
|
1219
|
+
headers: {
|
|
1220
|
+
...options?.headers,
|
|
1221
|
+
...headers
|
|
1222
|
+
},
|
|
1223
|
+
...options
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
},
|
|
1227
|
+
/**
|
|
1228
|
+
* Create a Paytrail checkout session.
|
|
1229
|
+
*
|
|
1230
|
+
* Returns payment providers for Finnish payment methods.
|
|
1231
|
+
* Cart items are validated and stock is reserved on the server.
|
|
1232
|
+
*
|
|
1233
|
+
* @param params - Checkout parameters (customer data, shipping, URLs)
|
|
1234
|
+
* @param options - Checkout options including cart/session context
|
|
1235
|
+
* @returns Paytrail response with available payment providers
|
|
1236
|
+
* @throws ValidationError for invalid data or empty cart
|
|
1237
|
+
* @throws StorefrontError for inventory issues
|
|
1238
|
+
*
|
|
1239
|
+
* @example Display payment providers
|
|
1240
|
+
* ```typescript
|
|
1241
|
+
* const response = await client.checkout.paytrail({
|
|
1242
|
+
* customerData: {
|
|
1243
|
+
* first_name: "Matti",
|
|
1244
|
+
* last_name: "Meikäläinen",
|
|
1245
|
+
* email: "matti@example.fi",
|
|
1246
|
+
* address: "Mannerheimintie 1",
|
|
1247
|
+
* postal_code: "00100",
|
|
1248
|
+
* city: "Helsinki",
|
|
1249
|
+
* phone: "+358401234567"
|
|
1250
|
+
* },
|
|
1251
|
+
* shipmentMethod: {
|
|
1252
|
+
* shipmentMethodId: "ship_123",
|
|
1253
|
+
* pickupId: "pickup_456" // For pickup points
|
|
1254
|
+
* },
|
|
1255
|
+
* orderId: "order_abc123",
|
|
1256
|
+
* successUrl: "https://mystore.com/success",
|
|
1257
|
+
* cancelUrl: "https://mystore.com/cancel"
|
|
1258
|
+
* }, {
|
|
1259
|
+
* cartId: "cart_xyz"
|
|
1260
|
+
* });
|
|
1261
|
+
*
|
|
1262
|
+
* // Group providers by type
|
|
1263
|
+
* const banks = response.providers.filter(p => p.group === "bank");
|
|
1264
|
+
* const mobile = response.providers.filter(p => p.group === "mobile");
|
|
1265
|
+
* const cards = response.providers.filter(p => p.group === "creditcard");
|
|
1266
|
+
* ```
|
|
1267
|
+
*
|
|
1268
|
+
* @example Submit payment form
|
|
1269
|
+
* ```typescript
|
|
1270
|
+
* const provider = response.providers.find(p => p.id === "nordea");
|
|
1271
|
+
*
|
|
1272
|
+
* // Create and submit a form
|
|
1273
|
+
* const form = document.createElement("form");
|
|
1274
|
+
* form.method = "POST";
|
|
1275
|
+
* form.action = provider.url;
|
|
1276
|
+
*
|
|
1277
|
+
* provider.parameters.forEach(({ name, value }) => {
|
|
1278
|
+
* const input = document.createElement("input");
|
|
1279
|
+
* input.type = "hidden";
|
|
1280
|
+
* input.name = name;
|
|
1281
|
+
* input.value = value;
|
|
1282
|
+
* form.appendChild(input);
|
|
1283
|
+
* });
|
|
1284
|
+
*
|
|
1285
|
+
* document.body.appendChild(form);
|
|
1286
|
+
* form.submit();
|
|
1287
|
+
* ```
|
|
1288
|
+
*/
|
|
1289
|
+
async paytrail(params, options) {
|
|
1290
|
+
const headers = buildCheckoutHeaders(options);
|
|
1291
|
+
const body = buildCheckoutBody(params);
|
|
1292
|
+
return fetcher.request(
|
|
1293
|
+
"/api/storefront/v1/payments/paytrail/checkout",
|
|
1294
|
+
{
|
|
1295
|
+
method: "POST",
|
|
1296
|
+
body,
|
|
1297
|
+
headers: {
|
|
1298
|
+
...options?.headers,
|
|
1299
|
+
...headers
|
|
1300
|
+
},
|
|
1301
|
+
...options
|
|
1302
|
+
}
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
168
1308
|
// src/client.ts
|
|
169
1309
|
function createStorefrontClient(config) {
|
|
170
1310
|
if (!config.apiKey) {
|
|
@@ -174,16 +1314,190 @@ function createStorefrontClient(config) {
|
|
|
174
1314
|
throw new Error("baseUrl is required");
|
|
175
1315
|
}
|
|
176
1316
|
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
177
|
-
const maskedApiKey = config.apiKey.length > 8 ? `${config.apiKey.slice(0, 8)}...` : config.apiKey;
|
|
178
1317
|
const fetcher = createFetcher({
|
|
179
1318
|
apiKey: config.apiKey,
|
|
180
1319
|
baseUrl,
|
|
181
1320
|
timeout: config.timeout
|
|
182
1321
|
});
|
|
183
1322
|
return {
|
|
184
|
-
apiKey: maskedApiKey,
|
|
185
1323
|
baseUrl,
|
|
186
|
-
store: createStoreResource(fetcher)
|
|
1324
|
+
store: createStoreResource(fetcher),
|
|
1325
|
+
products: createProductsResource(fetcher),
|
|
1326
|
+
categories: createCategoriesResource(fetcher),
|
|
1327
|
+
cart: createCartResource(fetcher),
|
|
1328
|
+
shipping: createShippingResource(fetcher),
|
|
1329
|
+
customer: createCustomerResource(fetcher),
|
|
1330
|
+
order: createOrderResource(fetcher),
|
|
1331
|
+
checkout: createCheckoutResource(fetcher)
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/utils/pricing.ts
|
|
1336
|
+
function isSaleActive(startDate, endDate) {
|
|
1337
|
+
if (!startDate && !endDate) {
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1340
|
+
const now = /* @__PURE__ */ new Date();
|
|
1341
|
+
const start = startDate ? new Date(startDate) : null;
|
|
1342
|
+
const end = endDate ? new Date(endDate) : null;
|
|
1343
|
+
if (start && !end) {
|
|
1344
|
+
return now >= start;
|
|
1345
|
+
}
|
|
1346
|
+
if (!start && end) {
|
|
1347
|
+
return now <= end;
|
|
1348
|
+
}
|
|
1349
|
+
if (start && end) {
|
|
1350
|
+
return now >= start && now <= end;
|
|
1351
|
+
}
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
function getPriceInfo(product, variation) {
|
|
1355
|
+
if (variation) {
|
|
1356
|
+
const isOnSale2 = isSaleActive(variation.saleStartDate, variation.saleEndDate) && variation.salePrice !== null;
|
|
1357
|
+
const originalPrice2 = variation.price ?? product.price;
|
|
1358
|
+
const effectivePrice2 = isOnSale2 ? variation.salePrice ?? originalPrice2 : originalPrice2;
|
|
1359
|
+
return {
|
|
1360
|
+
effectivePrice: effectivePrice2,
|
|
1361
|
+
originalPrice: originalPrice2,
|
|
1362
|
+
isOnSale: isOnSale2,
|
|
1363
|
+
salePercent: isOnSale2 ? variation.salePercent ?? null : null
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
const isOnSale = isSaleActive(product.saleStartDate, product.saleEndDate) && product.salePrice !== null;
|
|
1367
|
+
const originalPrice = product.price;
|
|
1368
|
+
const effectivePrice = isOnSale ? product.salePrice ?? originalPrice : originalPrice;
|
|
1369
|
+
return {
|
|
1370
|
+
effectivePrice,
|
|
1371
|
+
originalPrice,
|
|
1372
|
+
isOnSale,
|
|
1373
|
+
salePercent: isOnSale ? product.salePercent ?? null : null
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// src/utils/cart-calculations.ts
|
|
1378
|
+
function calculateCartWithCampaigns(items, campaigns) {
|
|
1379
|
+
const freeShippingCampaign = campaigns.find(
|
|
1380
|
+
(c) => c.type === "FREE_SHIPPING" && c.isActive
|
|
1381
|
+
);
|
|
1382
|
+
const buyXPayYCampaign = campaigns.find(
|
|
1383
|
+
(c) => c.type === "BUY_X_PAY_Y" && c.isActive
|
|
1384
|
+
);
|
|
1385
|
+
const originalTotal = items.reduce((total, { product, variation, cartQuantity }) => {
|
|
1386
|
+
const priceInfo = getPriceInfo(product, variation);
|
|
1387
|
+
return total + priceInfo.effectivePrice * cartQuantity;
|
|
1388
|
+
}, 0);
|
|
1389
|
+
if (!buyXPayYCampaign?.BuyXPayYCampaign) {
|
|
1390
|
+
const calculatedItems2 = items.map((item) => ({
|
|
1391
|
+
item,
|
|
1392
|
+
paidQuantity: item.cartQuantity,
|
|
1393
|
+
freeQuantity: 0,
|
|
1394
|
+
totalQuantity: item.cartQuantity
|
|
1395
|
+
}));
|
|
1396
|
+
const freeShipping2 = calculateFreeShipping(
|
|
1397
|
+
originalTotal,
|
|
1398
|
+
freeShippingCampaign
|
|
1399
|
+
);
|
|
1400
|
+
return {
|
|
1401
|
+
calculatedItems: calculatedItems2,
|
|
1402
|
+
cartTotal: originalTotal,
|
|
1403
|
+
originalTotal,
|
|
1404
|
+
totalSavings: 0,
|
|
1405
|
+
freeShipping: freeShipping2
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
const { buyQuantity, payQuantity, applicableCategories } = buyXPayYCampaign.BuyXPayYCampaign;
|
|
1409
|
+
const applicableCategoryIds = new Set(
|
|
1410
|
+
applicableCategories.map((c) => c.id)
|
|
1411
|
+
);
|
|
1412
|
+
const eligibleUnits = items.flatMap((item) => {
|
|
1413
|
+
const { product, variation } = item;
|
|
1414
|
+
const itemCategories = product.categories?.map((cat) => cat.id) || [];
|
|
1415
|
+
const isEligible = itemCategories.some(
|
|
1416
|
+
(id) => applicableCategoryIds.has(id)
|
|
1417
|
+
);
|
|
1418
|
+
if (isEligible) {
|
|
1419
|
+
const priceInfo = getPriceInfo(product, variation);
|
|
1420
|
+
return Array.from({ length: item.cartQuantity }, () => ({
|
|
1421
|
+
price: priceInfo.effectivePrice,
|
|
1422
|
+
productId: product.id,
|
|
1423
|
+
variationId: variation?.id,
|
|
1424
|
+
originalItem: item
|
|
1425
|
+
}));
|
|
1426
|
+
}
|
|
1427
|
+
return [];
|
|
1428
|
+
});
|
|
1429
|
+
if (eligibleUnits.length < buyQuantity) {
|
|
1430
|
+
const calculatedItems2 = items.map((item) => ({
|
|
1431
|
+
item,
|
|
1432
|
+
paidQuantity: item.cartQuantity,
|
|
1433
|
+
freeQuantity: 0,
|
|
1434
|
+
totalQuantity: item.cartQuantity
|
|
1435
|
+
}));
|
|
1436
|
+
const freeShipping2 = calculateFreeShipping(
|
|
1437
|
+
originalTotal,
|
|
1438
|
+
freeShippingCampaign
|
|
1439
|
+
);
|
|
1440
|
+
return {
|
|
1441
|
+
calculatedItems: calculatedItems2,
|
|
1442
|
+
cartTotal: originalTotal,
|
|
1443
|
+
originalTotal,
|
|
1444
|
+
totalSavings: 0,
|
|
1445
|
+
freeShipping: freeShipping2
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
eligibleUnits.sort((a, b) => a.price - b.price);
|
|
1449
|
+
const numToMakeFree = buyQuantity - payQuantity;
|
|
1450
|
+
const itemsToMakeFree = eligibleUnits.slice(0, numToMakeFree);
|
|
1451
|
+
const totalSavings = itemsToMakeFree.reduce(
|
|
1452
|
+
(sum, item) => sum + item.price,
|
|
1453
|
+
0
|
|
1454
|
+
);
|
|
1455
|
+
const freeCountMap = /* @__PURE__ */ new Map();
|
|
1456
|
+
for (const freebie of itemsToMakeFree) {
|
|
1457
|
+
const key = `${freebie.productId}${freebie.variationId ? `_${freebie.variationId}` : ""}`;
|
|
1458
|
+
freeCountMap.set(key, (freeCountMap.get(key) || 0) + 1);
|
|
1459
|
+
}
|
|
1460
|
+
const calculatedItems = items.map((item) => {
|
|
1461
|
+
const key = `${item.product.id}${item.variation?.id ? `_${item.variation.id}` : ""}`;
|
|
1462
|
+
const freeQuantity = freeCountMap.get(key) || 0;
|
|
1463
|
+
const paidQuantity = item.cartQuantity - freeQuantity;
|
|
1464
|
+
return {
|
|
1465
|
+
item,
|
|
1466
|
+
paidQuantity: Math.max(0, paidQuantity),
|
|
1467
|
+
freeQuantity,
|
|
1468
|
+
totalQuantity: item.cartQuantity
|
|
1469
|
+
};
|
|
1470
|
+
});
|
|
1471
|
+
const cartTotal = originalTotal - totalSavings;
|
|
1472
|
+
const freeShipping = calculateFreeShipping(cartTotal, freeShippingCampaign);
|
|
1473
|
+
return {
|
|
1474
|
+
calculatedItems,
|
|
1475
|
+
cartTotal,
|
|
1476
|
+
originalTotal,
|
|
1477
|
+
totalSavings,
|
|
1478
|
+
freeShipping
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
function calculateFreeShipping(cartTotal, campaign) {
|
|
1482
|
+
if (!campaign?.FreeShippingCampaign) {
|
|
1483
|
+
return {
|
|
1484
|
+
isEligible: false,
|
|
1485
|
+
minimumSpend: 0,
|
|
1486
|
+
remainingAmount: 0
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
const minimumSpend = campaign.FreeShippingCampaign.minimumSpend;
|
|
1490
|
+
const isEligible = cartTotal >= minimumSpend;
|
|
1491
|
+
const remainingAmount = isEligible ? 0 : minimumSpend - cartTotal;
|
|
1492
|
+
const eligibleShipmentMethodIds = campaign.FreeShippingCampaign.shipmentMethods?.map(
|
|
1493
|
+
(method) => method.id
|
|
1494
|
+
);
|
|
1495
|
+
return {
|
|
1496
|
+
isEligible,
|
|
1497
|
+
minimumSpend,
|
|
1498
|
+
remainingAmount,
|
|
1499
|
+
campaignName: campaign.name,
|
|
1500
|
+
eligibleShipmentMethodIds
|
|
187
1501
|
};
|
|
188
1502
|
}
|
|
189
1503
|
export {
|
|
@@ -192,6 +1506,10 @@ export {
|
|
|
192
1506
|
RateLimitError,
|
|
193
1507
|
StorefrontError,
|
|
194
1508
|
ValidationError,
|
|
195
|
-
|
|
1509
|
+
VerificationRequiredError,
|
|
1510
|
+
calculateCartWithCampaigns,
|
|
1511
|
+
createStorefrontClient,
|
|
1512
|
+
getPriceInfo,
|
|
1513
|
+
isSaleActive
|
|
196
1514
|
};
|
|
197
1515
|
//# sourceMappingURL=index.js.map
|