@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 CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/utils/errors.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/utils/errors.ts
2
2
  var StorefrontError = class _StorefrontError extends Error {
3
3
  constructor(message, status, code) {
4
4
  super(message);
@@ -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 errorMessage = null;
107
+ let errorData = {};
100
108
  try {
101
109
  const json = await response.json();
102
- if (json && typeof json === "object" && "error" in json) {
103
- errorMessage = json.error;
110
+ if (json && typeof json === "object") {
111
+ errorData = json;
104
112
  }
105
113
  } catch (e) {
106
114
  }
107
- const message = errorMessage || response.statusText || "Request failed";
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 (_optionalChain([slugs, 'optionalAccess', _ => _.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 (_optionalChain([params, 'access', _2 => _2.slugs, 'optionalAccess', _3 => _3.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 (_optionalChain([params, 'access', _4 => _4.slugs, 'optionalAccess', _5 => _5.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 (_optionalChain([options, 'optionalAccess', _6 => _6.cartId])) {
421
+ headers["x-cart-id"] = options.cartId;
422
+ }
423
+ if (_optionalChain([options, 'optionalAccess', _7 => _7.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 (_optionalChain([options, 'optionalAccess', _8 => _8.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 (_optionalChain([options, 'optionalAccess', _9 => _9.cartId])) {
1154
+ headers["x-cart-id"] = options.cartId;
1155
+ }
1156
+ if (_optionalChain([options, 'optionalAccess', _10 => _10.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
+ ..._optionalChain([options, 'optionalAccess', _11 => _11.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
+ ..._optionalChain([options, 'optionalAccess', _12 => _12.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,24 +1314,202 @@ 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)
187
1332
  };
188
1333
  }
189
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 = _nullishCoalesce(variation.price, () => ( product.price));
1358
+ const effectivePrice2 = isOnSale2 ? _nullishCoalesce(variation.salePrice, () => ( originalPrice2)) : originalPrice2;
1359
+ return {
1360
+ effectivePrice: effectivePrice2,
1361
+ originalPrice: originalPrice2,
1362
+ isOnSale: isOnSale2,
1363
+ salePercent: isOnSale2 ? _nullishCoalesce(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 ? _nullishCoalesce(product.salePrice, () => ( originalPrice)) : originalPrice;
1369
+ return {
1370
+ effectivePrice,
1371
+ originalPrice,
1372
+ isOnSale,
1373
+ salePercent: isOnSale ? _nullishCoalesce(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 (!_optionalChain([buyXPayYCampaign, 'optionalAccess', _13 => _13.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 = _optionalChain([product, 'access', _14 => _14.categories, 'optionalAccess', _15 => _15.map, 'call', _16 => _16((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: _optionalChain([variation, 'optionalAccess', _17 => _17.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}${_optionalChain([item, 'access', _18 => _18.variation, 'optionalAccess', _19 => _19.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 (!_optionalChain([campaign, 'optionalAccess', _20 => _20.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 = _optionalChain([campaign, 'access', _21 => _21.FreeShippingCampaign, 'access', _22 => _22.shipmentMethods, 'optionalAccess', _23 => _23.map, 'call', _24 => _24(
1493
+ (method) => method.id
1494
+ )]);
1495
+ return {
1496
+ isEligible,
1497
+ minimumSpend,
1498
+ remainingAmount,
1499
+ campaignName: campaign.name,
1500
+ eligibleShipmentMethodIds
1501
+ };
1502
+ }
1503
+
1504
+
1505
+
1506
+
1507
+
190
1508
 
191
1509
 
192
1510
 
193
1511
 
194
1512
 
195
1513
 
196
- exports.AuthError = AuthError; exports.NotFoundError = NotFoundError; exports.RateLimitError = RateLimitError; exports.StorefrontError = StorefrontError; exports.ValidationError = ValidationError; exports.createStorefrontClient = createStorefrontClient;
1514
+ exports.AuthError = AuthError; exports.NotFoundError = NotFoundError; exports.RateLimitError = RateLimitError; exports.StorefrontError = StorefrontError; exports.ValidationError = ValidationError; exports.VerificationRequiredError = VerificationRequiredError; exports.calculateCartWithCampaigns = calculateCartWithCampaigns; exports.createStorefrontClient = createStorefrontClient; exports.getPriceInfo = getPriceInfo; exports.isSaleActive = isSaleActive;
197
1515
  //# sourceMappingURL=index.cjs.map