@putiikkipalvelu/storefront-sdk 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -610,6 +610,138 @@ function createCartResource(fetcher) {
610
610
  };
611
611
  }
612
612
 
613
+ // src/utils/pricing.ts
614
+ function isSaleActive(startDate, endDate) {
615
+ if (!startDate && !endDate) {
616
+ return true;
617
+ }
618
+ const now = /* @__PURE__ */ new Date();
619
+ const start = startDate ? new Date(startDate) : null;
620
+ const end = endDate ? new Date(endDate) : null;
621
+ if (start && !end) {
622
+ return now >= start;
623
+ }
624
+ if (!start && end) {
625
+ return now <= end;
626
+ }
627
+ if (start && end) {
628
+ return now >= start && now <= end;
629
+ }
630
+ return true;
631
+ }
632
+ function getPriceInfo(product, variation) {
633
+ if (variation) {
634
+ const isOnSale2 = isSaleActive(variation.saleStartDate, variation.saleEndDate) && variation.salePrice !== null;
635
+ const originalPrice2 = variation.price ?? product.price;
636
+ const effectivePrice2 = isOnSale2 ? variation.salePrice ?? originalPrice2 : originalPrice2;
637
+ return {
638
+ effectivePrice: effectivePrice2,
639
+ originalPrice: originalPrice2,
640
+ isOnSale: isOnSale2,
641
+ salePercent: isOnSale2 ? variation.salePercent ?? null : null
642
+ };
643
+ }
644
+ const isOnSale = isSaleActive(product.saleStartDate, product.saleEndDate) && product.salePrice !== null;
645
+ const originalPrice = product.price;
646
+ const effectivePrice = isOnSale ? product.salePrice ?? originalPrice : originalPrice;
647
+ return {
648
+ effectivePrice,
649
+ originalPrice,
650
+ isOnSale,
651
+ salePercent: isOnSale ? product.salePercent ?? null : null
652
+ };
653
+ }
654
+
655
+ // src/utils/cart-calculations.ts
656
+ function calculateCartWithCampaigns(items, campaigns) {
657
+ const buyXPayYCampaign = campaigns.find(
658
+ (c) => c.type === "BUY_X_PAY_Y" && c.isActive
659
+ );
660
+ const originalTotal = items.reduce((total, { product, variation, cartQuantity }) => {
661
+ const priceInfo = getPriceInfo(product, variation);
662
+ return total + priceInfo.effectivePrice * cartQuantity;
663
+ }, 0);
664
+ if (!buyXPayYCampaign?.BuyXPayYCampaign) {
665
+ const calculatedItems2 = items.map((item) => ({
666
+ item,
667
+ paidQuantity: item.cartQuantity,
668
+ freeQuantity: 0,
669
+ totalQuantity: item.cartQuantity
670
+ }));
671
+ return {
672
+ calculatedItems: calculatedItems2,
673
+ cartTotal: originalTotal,
674
+ originalTotal,
675
+ totalSavings: 0
676
+ };
677
+ }
678
+ const { buyQuantity, payQuantity, applicableCategories } = buyXPayYCampaign.BuyXPayYCampaign;
679
+ const applicableCategoryIds = new Set(
680
+ applicableCategories.map((c) => c.id)
681
+ );
682
+ const eligibleUnits = items.flatMap((item) => {
683
+ const { product, variation } = item;
684
+ const itemCategories = product.categories?.map((cat) => cat.id) || [];
685
+ const isEligible = itemCategories.some(
686
+ (id) => applicableCategoryIds.has(id)
687
+ );
688
+ if (isEligible) {
689
+ const priceInfo = getPriceInfo(product, variation);
690
+ return Array.from({ length: item.cartQuantity }, () => ({
691
+ price: priceInfo.effectivePrice,
692
+ productId: product.id,
693
+ variationId: variation?.id,
694
+ originalItem: item
695
+ }));
696
+ }
697
+ return [];
698
+ });
699
+ if (eligibleUnits.length < buyQuantity) {
700
+ const calculatedItems2 = items.map((item) => ({
701
+ item,
702
+ paidQuantity: item.cartQuantity,
703
+ freeQuantity: 0,
704
+ totalQuantity: item.cartQuantity
705
+ }));
706
+ return {
707
+ calculatedItems: calculatedItems2,
708
+ cartTotal: originalTotal,
709
+ originalTotal,
710
+ totalSavings: 0
711
+ };
712
+ }
713
+ eligibleUnits.sort((a, b) => a.price - b.price);
714
+ const numToMakeFree = buyQuantity - payQuantity;
715
+ const itemsToMakeFree = eligibleUnits.slice(0, numToMakeFree);
716
+ const totalSavings = itemsToMakeFree.reduce(
717
+ (sum, item) => sum + item.price,
718
+ 0
719
+ );
720
+ const freeCountMap = /* @__PURE__ */ new Map();
721
+ for (const freebie of itemsToMakeFree) {
722
+ const key = `${freebie.productId}${freebie.variationId ? `_${freebie.variationId}` : ""}`;
723
+ freeCountMap.set(key, (freeCountMap.get(key) || 0) + 1);
724
+ }
725
+ const calculatedItems = items.map((item) => {
726
+ const key = `${item.product.id}${item.variation?.id ? `_${item.variation.id}` : ""}`;
727
+ const freeQuantity = freeCountMap.get(key) || 0;
728
+ const paidQuantity = item.cartQuantity - freeQuantity;
729
+ return {
730
+ item,
731
+ paidQuantity: Math.max(0, paidQuantity),
732
+ freeQuantity,
733
+ totalQuantity: item.cartQuantity
734
+ };
735
+ });
736
+ const cartTotal = originalTotal - totalSavings;
737
+ return {
738
+ calculatedItems,
739
+ cartTotal,
740
+ originalTotal,
741
+ totalSavings
742
+ };
743
+ }
744
+
613
745
  // src/resources/shipping.ts
614
746
  function calculateCartWeight(items) {
615
747
  return items.reduce((total, item) => {
@@ -617,81 +749,70 @@ function calculateCartWeight(items) {
617
749
  return total + itemWeight * item.cartQuantity;
618
750
  }, 0);
619
751
  }
752
+ function calculateCartTotal(items) {
753
+ return items.reduce((total, item) => {
754
+ const itemPrice = item.variation ? item.variation.salePrice ?? item.variation.price : item.product.salePrice ?? item.product.price;
755
+ return total + itemPrice * item.cartQuantity;
756
+ }, 0);
757
+ }
620
758
  function createShippingResource(fetcher) {
621
759
  return {
622
760
  /**
623
- * Get all available shipment methods for the store.
624
- * Returns methods without pickup locations - use `getWithLocations` for postal code specific data.
761
+ * Get shipping options for a specific postal code.
762
+ * Returns pickup points and home delivery options in a unified format.
763
+ *
764
+ * **Pickup points are returned first** as they are more popular in Finland.
625
765
  *
766
+ * @param postalCode - Customer's postal code (e.g., "00100")
626
767
  * @param options - Fetch options including optional cartItems for weight-based filtering
627
- * @returns Available shipment methods
768
+ * @returns Unified shipping options (pickupPoints sorted by distance, homeDelivery sorted by price)
628
769
  *
629
770
  * @example
630
771
  * ```typescript
631
- * const { shipmentMethods } = await client.shipping.getMethods();
772
+ * const { pickupPoints, homeDelivery } = await client.shipping.getOptions("00100");
773
+ *
774
+ * // Show pickup points (more popular in Finland)
775
+ * pickupPoints.forEach(point => {
776
+ * console.log(`${point.name} - ${point.carrier}`);
777
+ * console.log(` ${point.address}, ${point.city}`);
778
+ * console.log(` ${(point.distance! / 1000).toFixed(1)} km away`);
779
+ * console.log(` Price: ${point.price / 100}€`);
780
+ * });
632
781
  *
633
- * shipmentMethods.forEach(method => {
634
- * console.log(`${method.name}: ${method.price / 100}€`);
782
+ * // Show home delivery options
783
+ * homeDelivery.forEach(option => {
784
+ * console.log(`${option.name}: ${option.price / 100}€`);
785
+ * if (option.estimatedDelivery) {
786
+ * console.log(` Delivery: ${option.estimatedDelivery} days`);
787
+ * }
635
788
  * });
636
789
  * ```
637
790
  *
638
791
  * @example Weight-based filtering
639
792
  * ```typescript
640
- * // Pass cart items - SDK calculates weight automatically
641
- * const { shipmentMethods } = await client.shipping.getMethods({
793
+ * const options = await client.shipping.getOptions("00100", {
642
794
  * cartItems: cartItems
643
795
  * });
796
+ * // Only shows methods that support the cart's total weight
644
797
  * ```
645
- */
646
- async getMethods(options) {
647
- const params = new URLSearchParams();
648
- if (options?.cartItems?.length) {
649
- const cartWeight = calculateCartWeight(options.cartItems);
650
- params.set("cartWeight", cartWeight.toString());
651
- }
652
- const queryString = params.toString();
653
- const url = `/api/storefront/v1/shipment-methods${queryString ? `?${queryString}` : ""}`;
654
- return fetcher.request(url, {
655
- method: "GET",
656
- ...options
657
- });
658
- },
659
- /**
660
- * Get shipment methods with pickup locations for a specific postal code.
661
- * Calls the Shipit API to fetch nearby pickup points (parcel lockers, etc.)
662
- *
663
- * @param postalCode - Customer's postal code (e.g., "00100")
664
- * @param options - Fetch options including optional cartItems for weight-based filtering
665
- * @returns Shipment methods and nearby pickup locations with pricing
666
798
  *
667
- * @example
799
+ * @example International shipping
668
800
  * ```typescript
669
- * const { shipmentMethods, pricedLocations } = await client.shipping.getWithLocations("00100");
670
- *
671
- * // Show pickup locations
672
- * pricedLocations.forEach(location => {
673
- * console.log(`${location.name} - ${location.carrier}`);
674
- * console.log(` ${location.address1}, ${location.city}`);
675
- * console.log(` ${location.distanceInKilometers.toFixed(1)} km away`);
676
- * console.log(` Price: ${(location.merchantPrice ?? 0) / 100}€`);
801
+ * const options = await client.shipping.getOptions("112 22", {
802
+ * country: "SE"
677
803
  * });
678
804
  * ```
679
- *
680
- * @example Weight-based filtering with postal code
681
- * ```typescript
682
- * const { shipmentMethods, pricedLocations } = await client.shipping.getWithLocations(
683
- * "00100",
684
- * { cartItems: cartItems }
685
- * );
686
- *
687
- * // Only shows methods that support the cart's total weight
688
- * ```
689
805
  */
690
- async getWithLocations(postalCode, options) {
806
+ async getOptions(postalCode, options) {
691
807
  const params = new URLSearchParams();
692
808
  if (options?.cartItems?.length) {
693
809
  const cartWeight = calculateCartWeight(options.cartItems);
694
810
  params.set("cartWeight", cartWeight.toString());
811
+ const cartTotal = options.campaigns ? calculateCartWithCampaigns(options.cartItems, options.campaigns).cartTotal : calculateCartTotal(options.cartItems);
812
+ params.set("cartTotal", cartTotal.toString());
813
+ }
814
+ if (options?.country) {
815
+ params.set("country", options.country);
695
816
  }
696
817
  const queryString = params.toString();
697
818
  const url = `/api/storefront/v1/shipment-methods/${encodeURIComponent(postalCode)}${queryString ? `?${queryString}` : ""}`;
@@ -699,6 +820,12 @@ function createShippingResource(fetcher) {
699
820
  method: "GET",
700
821
  ...options
701
822
  });
823
+ },
824
+ /**
825
+ * @deprecated Use getOptions() instead. This method is kept for backwards compatibility.
826
+ */
827
+ async getWithLocations(postalCode, options) {
828
+ return this.getOptions(postalCode, options);
702
829
  }
703
830
  };
704
831
  }
@@ -1426,146 +1553,6 @@ function createStorefrontClient(config) {
1426
1553
  checkout: createCheckoutResource(fetcher)
1427
1554
  };
1428
1555
  }
1429
-
1430
- // src/utils/pricing.ts
1431
- function isSaleActive(startDate, endDate) {
1432
- if (!startDate && !endDate) {
1433
- return true;
1434
- }
1435
- const now = /* @__PURE__ */ new Date();
1436
- const start = startDate ? new Date(startDate) : null;
1437
- const end = endDate ? new Date(endDate) : null;
1438
- if (start && !end) {
1439
- return now >= start;
1440
- }
1441
- if (!start && end) {
1442
- return now <= end;
1443
- }
1444
- if (start && end) {
1445
- return now >= start && now <= end;
1446
- }
1447
- return true;
1448
- }
1449
- function getPriceInfo(product, variation) {
1450
- if (variation) {
1451
- const isOnSale2 = isSaleActive(variation.saleStartDate, variation.saleEndDate) && variation.salePrice !== null;
1452
- const originalPrice2 = variation.price ?? product.price;
1453
- const effectivePrice2 = isOnSale2 ? variation.salePrice ?? originalPrice2 : originalPrice2;
1454
- return {
1455
- effectivePrice: effectivePrice2,
1456
- originalPrice: originalPrice2,
1457
- isOnSale: isOnSale2,
1458
- salePercent: isOnSale2 ? variation.salePercent ?? null : null
1459
- };
1460
- }
1461
- const isOnSale = isSaleActive(product.saleStartDate, product.saleEndDate) && product.salePrice !== null;
1462
- const originalPrice = product.price;
1463
- const effectivePrice = isOnSale ? product.salePrice ?? originalPrice : originalPrice;
1464
- return {
1465
- effectivePrice,
1466
- originalPrice,
1467
- isOnSale,
1468
- salePercent: isOnSale ? product.salePercent ?? null : null
1469
- };
1470
- }
1471
-
1472
- // src/utils/cart-calculations.ts
1473
- function calculateCartWithCampaigns(items, campaigns) {
1474
- const buyXPayYCampaign = campaigns.find(
1475
- (c) => c.type === "BUY_X_PAY_Y" && c.isActive
1476
- );
1477
- const originalTotal = items.reduce((total, { product, variation, cartQuantity }) => {
1478
- const priceInfo = getPriceInfo(product, variation);
1479
- return total + priceInfo.effectivePrice * cartQuantity;
1480
- }, 0);
1481
- const freeShipping = {
1482
- isEligible: false,
1483
- minimumSpend: 0,
1484
- remainingAmount: 0
1485
- };
1486
- if (!buyXPayYCampaign?.BuyXPayYCampaign) {
1487
- const calculatedItems2 = items.map((item) => ({
1488
- item,
1489
- paidQuantity: item.cartQuantity,
1490
- freeQuantity: 0,
1491
- totalQuantity: item.cartQuantity
1492
- }));
1493
- return {
1494
- calculatedItems: calculatedItems2,
1495
- cartTotal: originalTotal,
1496
- originalTotal,
1497
- totalSavings: 0,
1498
- freeShipping
1499
- };
1500
- }
1501
- const { buyQuantity, payQuantity, applicableCategories } = buyXPayYCampaign.BuyXPayYCampaign;
1502
- const applicableCategoryIds = new Set(
1503
- applicableCategories.map((c) => c.id)
1504
- );
1505
- const eligibleUnits = items.flatMap((item) => {
1506
- const { product, variation } = item;
1507
- const itemCategories = product.categories?.map((cat) => cat.id) || [];
1508
- const isEligible = itemCategories.some(
1509
- (id) => applicableCategoryIds.has(id)
1510
- );
1511
- if (isEligible) {
1512
- const priceInfo = getPriceInfo(product, variation);
1513
- return Array.from({ length: item.cartQuantity }, () => ({
1514
- price: priceInfo.effectivePrice,
1515
- productId: product.id,
1516
- variationId: variation?.id,
1517
- originalItem: item
1518
- }));
1519
- }
1520
- return [];
1521
- });
1522
- if (eligibleUnits.length < buyQuantity) {
1523
- const calculatedItems2 = items.map((item) => ({
1524
- item,
1525
- paidQuantity: item.cartQuantity,
1526
- freeQuantity: 0,
1527
- totalQuantity: item.cartQuantity
1528
- }));
1529
- return {
1530
- calculatedItems: calculatedItems2,
1531
- cartTotal: originalTotal,
1532
- originalTotal,
1533
- totalSavings: 0,
1534
- freeShipping
1535
- };
1536
- }
1537
- eligibleUnits.sort((a, b) => a.price - b.price);
1538
- const numToMakeFree = buyQuantity - payQuantity;
1539
- const itemsToMakeFree = eligibleUnits.slice(0, numToMakeFree);
1540
- const totalSavings = itemsToMakeFree.reduce(
1541
- (sum, item) => sum + item.price,
1542
- 0
1543
- );
1544
- const freeCountMap = /* @__PURE__ */ new Map();
1545
- for (const freebie of itemsToMakeFree) {
1546
- const key = `${freebie.productId}${freebie.variationId ? `_${freebie.variationId}` : ""}`;
1547
- freeCountMap.set(key, (freeCountMap.get(key) || 0) + 1);
1548
- }
1549
- const calculatedItems = items.map((item) => {
1550
- const key = `${item.product.id}${item.variation?.id ? `_${item.variation.id}` : ""}`;
1551
- const freeQuantity = freeCountMap.get(key) || 0;
1552
- const paidQuantity = item.cartQuantity - freeQuantity;
1553
- return {
1554
- item,
1555
- paidQuantity: Math.max(0, paidQuantity),
1556
- freeQuantity,
1557
- totalQuantity: item.cartQuantity
1558
- };
1559
- });
1560
- const cartTotal = originalTotal - totalSavings;
1561
- return {
1562
- calculatedItems,
1563
- cartTotal,
1564
- originalTotal,
1565
- totalSavings,
1566
- freeShipping
1567
- };
1568
- }
1569
1556
  export {
1570
1557
  AuthError,
1571
1558
  NotFoundError,