@putiikkipalvelu/storefront-sdk 0.3.0 → 0.4.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 +368 -154
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +325 -13
- package/dist/index.d.ts +325 -13
- package/dist/index.js +365 -151
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -116,19 +116,24 @@ async function handleErrorResponse(response) {
|
|
|
116
116
|
if (errorData.requiresVerification && errorData.customerId) {
|
|
117
117
|
throw new VerificationRequiredError(message, errorData.customerId);
|
|
118
118
|
}
|
|
119
|
+
const code = errorData.code;
|
|
119
120
|
switch (response.status) {
|
|
120
121
|
case 401:
|
|
121
|
-
throw new
|
|
122
|
+
throw new StorefrontError(message, 401, code || "UNAUTHORIZED");
|
|
122
123
|
case 404:
|
|
123
|
-
throw new
|
|
124
|
+
throw new StorefrontError(message, 404, code || "NOT_FOUND");
|
|
124
125
|
case 429: {
|
|
125
126
|
const retryAfter = response.headers.get("Retry-After");
|
|
126
|
-
|
|
127
|
+
const error = new RateLimitError(message, retryAfter ? parseInt(retryAfter, 10) : null);
|
|
128
|
+
if (code) {
|
|
129
|
+
error.code = code;
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
127
132
|
}
|
|
128
133
|
case 400:
|
|
129
|
-
throw new
|
|
134
|
+
throw new StorefrontError(message, 400, code || "VALIDATION_ERROR");
|
|
130
135
|
default:
|
|
131
|
-
throw new StorefrontError(message, response.status, "API_ERROR");
|
|
136
|
+
throw new StorefrontError(message, response.status, code || "API_ERROR");
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
|
|
@@ -414,6 +419,138 @@ function createCategoriesResource(fetcher) {
|
|
|
414
419
|
};
|
|
415
420
|
}
|
|
416
421
|
|
|
422
|
+
// src/utils/pricing.ts
|
|
423
|
+
function isSaleActive(startDate, endDate) {
|
|
424
|
+
if (!startDate && !endDate) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
const now = /* @__PURE__ */ new Date();
|
|
428
|
+
const start = startDate ? new Date(startDate) : null;
|
|
429
|
+
const end = endDate ? new Date(endDate) : null;
|
|
430
|
+
if (start && !end) {
|
|
431
|
+
return now >= start;
|
|
432
|
+
}
|
|
433
|
+
if (!start && end) {
|
|
434
|
+
return now <= end;
|
|
435
|
+
}
|
|
436
|
+
if (start && end) {
|
|
437
|
+
return now >= start && now <= end;
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
function getPriceInfo(product, variation) {
|
|
442
|
+
if (variation) {
|
|
443
|
+
const isOnSale2 = isSaleActive(variation.saleStartDate, variation.saleEndDate) && variation.salePrice !== null;
|
|
444
|
+
const originalPrice2 = variation.price ?? product.price;
|
|
445
|
+
const effectivePrice2 = isOnSale2 ? variation.salePrice ?? originalPrice2 : originalPrice2;
|
|
446
|
+
return {
|
|
447
|
+
effectivePrice: effectivePrice2,
|
|
448
|
+
originalPrice: originalPrice2,
|
|
449
|
+
isOnSale: isOnSale2,
|
|
450
|
+
salePercent: isOnSale2 ? variation.salePercent ?? null : null
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const isOnSale = isSaleActive(product.saleStartDate, product.saleEndDate) && product.salePrice !== null;
|
|
454
|
+
const originalPrice = product.price;
|
|
455
|
+
const effectivePrice = isOnSale ? product.salePrice ?? originalPrice : originalPrice;
|
|
456
|
+
return {
|
|
457
|
+
effectivePrice,
|
|
458
|
+
originalPrice,
|
|
459
|
+
isOnSale,
|
|
460
|
+
salePercent: isOnSale ? product.salePercent ?? null : null
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/utils/cart-calculations.ts
|
|
465
|
+
function calculateCartWithCampaigns(items, campaigns) {
|
|
466
|
+
const buyXPayYCampaign = campaigns.find(
|
|
467
|
+
(c) => c.type === "BUY_X_PAY_Y" && c.isActive
|
|
468
|
+
);
|
|
469
|
+
const originalTotal = items.reduce((total, { product, variation, cartQuantity }) => {
|
|
470
|
+
const priceInfo = getPriceInfo(product, variation);
|
|
471
|
+
return total + priceInfo.effectivePrice * cartQuantity;
|
|
472
|
+
}, 0);
|
|
473
|
+
if (!buyXPayYCampaign?.BuyXPayYCampaign) {
|
|
474
|
+
const calculatedItems2 = items.map((item) => ({
|
|
475
|
+
item,
|
|
476
|
+
paidQuantity: item.cartQuantity,
|
|
477
|
+
freeQuantity: 0,
|
|
478
|
+
totalQuantity: item.cartQuantity
|
|
479
|
+
}));
|
|
480
|
+
return {
|
|
481
|
+
calculatedItems: calculatedItems2,
|
|
482
|
+
cartTotal: originalTotal,
|
|
483
|
+
originalTotal,
|
|
484
|
+
totalSavings: 0
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const { buyQuantity, payQuantity, applicableCategories } = buyXPayYCampaign.BuyXPayYCampaign;
|
|
488
|
+
const applicableCategoryIds = new Set(
|
|
489
|
+
applicableCategories.map((c) => c.id)
|
|
490
|
+
);
|
|
491
|
+
const eligibleUnits = items.flatMap((item) => {
|
|
492
|
+
const { product, variation } = item;
|
|
493
|
+
const itemCategories = product.categories?.map((cat) => cat.id) || [];
|
|
494
|
+
const isEligible = itemCategories.some(
|
|
495
|
+
(id) => applicableCategoryIds.has(id)
|
|
496
|
+
);
|
|
497
|
+
if (isEligible) {
|
|
498
|
+
const priceInfo = getPriceInfo(product, variation);
|
|
499
|
+
return Array.from({ length: item.cartQuantity }, () => ({
|
|
500
|
+
price: priceInfo.effectivePrice,
|
|
501
|
+
productId: product.id,
|
|
502
|
+
variationId: variation?.id,
|
|
503
|
+
originalItem: item
|
|
504
|
+
}));
|
|
505
|
+
}
|
|
506
|
+
return [];
|
|
507
|
+
});
|
|
508
|
+
if (eligibleUnits.length < buyQuantity) {
|
|
509
|
+
const calculatedItems2 = items.map((item) => ({
|
|
510
|
+
item,
|
|
511
|
+
paidQuantity: item.cartQuantity,
|
|
512
|
+
freeQuantity: 0,
|
|
513
|
+
totalQuantity: item.cartQuantity
|
|
514
|
+
}));
|
|
515
|
+
return {
|
|
516
|
+
calculatedItems: calculatedItems2,
|
|
517
|
+
cartTotal: originalTotal,
|
|
518
|
+
originalTotal,
|
|
519
|
+
totalSavings: 0
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
eligibleUnits.sort((a, b) => a.price - b.price);
|
|
523
|
+
const numToMakeFree = buyQuantity - payQuantity;
|
|
524
|
+
const itemsToMakeFree = eligibleUnits.slice(0, numToMakeFree);
|
|
525
|
+
const totalSavings = itemsToMakeFree.reduce(
|
|
526
|
+
(sum, item) => sum + item.price,
|
|
527
|
+
0
|
|
528
|
+
);
|
|
529
|
+
const freeCountMap = /* @__PURE__ */ new Map();
|
|
530
|
+
for (const freebie of itemsToMakeFree) {
|
|
531
|
+
const key = `${freebie.productId}${freebie.variationId ? `_${freebie.variationId}` : ""}`;
|
|
532
|
+
freeCountMap.set(key, (freeCountMap.get(key) || 0) + 1);
|
|
533
|
+
}
|
|
534
|
+
const calculatedItems = items.map((item) => {
|
|
535
|
+
const key = `${item.product.id}${item.variation?.id ? `_${item.variation.id}` : ""}`;
|
|
536
|
+
const freeQuantity = freeCountMap.get(key) || 0;
|
|
537
|
+
const paidQuantity = item.cartQuantity - freeQuantity;
|
|
538
|
+
return {
|
|
539
|
+
item,
|
|
540
|
+
paidQuantity: Math.max(0, paidQuantity),
|
|
541
|
+
freeQuantity,
|
|
542
|
+
totalQuantity: item.cartQuantity
|
|
543
|
+
};
|
|
544
|
+
});
|
|
545
|
+
const cartTotal = originalTotal - totalSavings;
|
|
546
|
+
return {
|
|
547
|
+
calculatedItems,
|
|
548
|
+
cartTotal,
|
|
549
|
+
originalTotal,
|
|
550
|
+
totalSavings
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
417
554
|
// src/resources/cart.ts
|
|
418
555
|
function buildCartHeaders(options) {
|
|
419
556
|
const headers = {};
|
|
@@ -576,33 +713,49 @@ function createCartResource(fetcher) {
|
|
|
576
713
|
* Checks product availability, stock levels, and prices.
|
|
577
714
|
* Auto-fixes issues (removes unavailable items, adjusts quantities).
|
|
578
715
|
*
|
|
716
|
+
* Campaign conflict detection:
|
|
717
|
+
* - SDK calculates if BuyXPayY campaigns apply using calculateCartWithCampaigns()
|
|
718
|
+
* - Sends x-campaigns-apply header to backend
|
|
719
|
+
* - If campaigns apply AND discount code exists, backend removes it
|
|
720
|
+
* - Returns changes.discountCouponRemoved = true
|
|
721
|
+
*
|
|
579
722
|
* @param options - Cart session options
|
|
723
|
+
* @param cartItems - Current cart items for campaign calculation
|
|
724
|
+
* @param campaigns - Active campaigns for conflict check
|
|
580
725
|
* @param fetchOptions - Fetch options
|
|
581
726
|
* @returns Validated cart with change metadata
|
|
582
727
|
*
|
|
583
728
|
* @example Validate before checkout
|
|
584
729
|
* ```typescript
|
|
585
|
-
* const
|
|
730
|
+
* const result = await client.cart.validate(
|
|
731
|
+
* { cartId },
|
|
732
|
+
* cartItems,
|
|
733
|
+
* storeConfig.campaigns
|
|
734
|
+
* );
|
|
586
735
|
*
|
|
587
|
-
* if (hasChanges) {
|
|
588
|
-
* if (changes.
|
|
589
|
-
* notify(
|
|
590
|
-
* }
|
|
591
|
-
* if (changes.quantityAdjusted > 0) {
|
|
592
|
-
* notify('Some quantities were adjusted');
|
|
593
|
-
* }
|
|
594
|
-
* if (changes.priceChanged > 0) {
|
|
595
|
-
* notify('Some prices have changed');
|
|
736
|
+
* if (result.hasChanges) {
|
|
737
|
+
* if (result.changes.discountCouponRemoved) {
|
|
738
|
+
* notify("Alennuskoodi poistettu - kampanja-alennus aktivoitui");
|
|
596
739
|
* }
|
|
740
|
+
* // Handle other changes...
|
|
597
741
|
* }
|
|
598
742
|
* ```
|
|
599
743
|
*/
|
|
600
|
-
async validate(options, fetchOptions) {
|
|
744
|
+
async validate(options, cartItems, campaigns, fetchOptions) {
|
|
745
|
+
let campaignsApply = false;
|
|
746
|
+
if (cartItems && campaigns && cartItems.length > 0) {
|
|
747
|
+
const campaignResult = calculateCartWithCampaigns(cartItems, campaigns);
|
|
748
|
+
campaignsApply = campaignResult.totalSavings > 0;
|
|
749
|
+
}
|
|
750
|
+
const headers = {
|
|
751
|
+
...buildCartHeaders(options),
|
|
752
|
+
"x-campaigns-apply": campaignsApply ? "true" : "false"
|
|
753
|
+
};
|
|
601
754
|
return fetcher.request(
|
|
602
755
|
"/api/storefront/v1/cart/validate",
|
|
603
756
|
{
|
|
604
757
|
method: "GET",
|
|
605
|
-
headers
|
|
758
|
+
headers,
|
|
606
759
|
...fetchOptions
|
|
607
760
|
}
|
|
608
761
|
);
|
|
@@ -610,138 +763,6 @@ function createCartResource(fetcher) {
|
|
|
610
763
|
};
|
|
611
764
|
}
|
|
612
765
|
|
|
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
|
-
|
|
745
766
|
// src/resources/shipping.ts
|
|
746
767
|
function calculateCartWeight(items) {
|
|
747
768
|
return items.reduce((total, item) => {
|
|
@@ -809,7 +830,8 @@ function createShippingResource(fetcher) {
|
|
|
809
830
|
const cartWeight = calculateCartWeight(options.cartItems);
|
|
810
831
|
params.set("cartWeight", cartWeight.toString());
|
|
811
832
|
const cartTotal = options.campaigns ? calculateCartWithCampaigns(options.cartItems, options.campaigns).cartTotal : calculateCartTotal(options.cartItems);
|
|
812
|
-
|
|
833
|
+
const finalCartTotal = options.discountAmount ? Math.max(0, cartTotal - options.discountAmount) : cartTotal;
|
|
834
|
+
params.set("cartTotal", finalCartTotal.toString());
|
|
813
835
|
}
|
|
814
836
|
if (options?.country) {
|
|
815
837
|
params.set("country", options.country);
|
|
@@ -1527,6 +1549,102 @@ function createCheckoutResource(fetcher) {
|
|
|
1527
1549
|
};
|
|
1528
1550
|
}
|
|
1529
1551
|
|
|
1552
|
+
// src/resources/discount-code.ts
|
|
1553
|
+
function createDiscountCodeResource(fetcher) {
|
|
1554
|
+
return {
|
|
1555
|
+
/**
|
|
1556
|
+
* Apply a discount code to the cart
|
|
1557
|
+
*
|
|
1558
|
+
* Checks for BuyXPayY campaign conflict before calling API.
|
|
1559
|
+
* Discount codes cannot be used when a campaign discount is active.
|
|
1560
|
+
*
|
|
1561
|
+
* @param params - Parameters including code, session info, and cart/campaign data for conflict check
|
|
1562
|
+
* @returns Applied discount details
|
|
1563
|
+
* @throws {StorefrontError} With code "CAMPAIGN_ACTIVE" if a BuyXPayY campaign is active
|
|
1564
|
+
*
|
|
1565
|
+
* @example
|
|
1566
|
+
* ```typescript
|
|
1567
|
+
* const result = await client.discountCode.apply({
|
|
1568
|
+
* code: "SUMMER20",
|
|
1569
|
+
* cartId: cartId,
|
|
1570
|
+
* cartItems: cart.items,
|
|
1571
|
+
* campaigns: storeConfig.campaigns,
|
|
1572
|
+
* });
|
|
1573
|
+
* ```
|
|
1574
|
+
*/
|
|
1575
|
+
async apply(params) {
|
|
1576
|
+
const { code, cartId, sessionId, cartItems, campaigns } = params;
|
|
1577
|
+
if (cartItems && campaigns && cartItems.length > 0) {
|
|
1578
|
+
const campaignResult = calculateCartWithCampaigns(cartItems, campaigns);
|
|
1579
|
+
if (campaignResult.totalSavings > 0) {
|
|
1580
|
+
throw new StorefrontError(
|
|
1581
|
+
"Alennuskoodia ei voi k\xE4ytt\xE4\xE4 kun kampanja-alennus on voimassa",
|
|
1582
|
+
400,
|
|
1583
|
+
"CAMPAIGN_ACTIVE"
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const cartTotal = cartItems ? cartItems.reduce((sum, item) => {
|
|
1588
|
+
const priceInfo = getPriceInfo(item.product, item.variation);
|
|
1589
|
+
return sum + priceInfo.effectivePrice * item.cartQuantity;
|
|
1590
|
+
}, 0) : 0;
|
|
1591
|
+
return fetcher.request("/api/storefront/v1/discount-code/apply", {
|
|
1592
|
+
method: "POST",
|
|
1593
|
+
body: { code, cartTotal },
|
|
1594
|
+
headers: {
|
|
1595
|
+
...cartId && { "x-cart-id": cartId },
|
|
1596
|
+
...sessionId && { "x-session-id": sessionId }
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
},
|
|
1600
|
+
/**
|
|
1601
|
+
* Get the currently applied discount code
|
|
1602
|
+
*
|
|
1603
|
+
* @param params - Session info (cartId or sessionId)
|
|
1604
|
+
* @returns Current discount or null if none applied
|
|
1605
|
+
*
|
|
1606
|
+
* @example
|
|
1607
|
+
* ```typescript
|
|
1608
|
+
* const { discount } = await client.discountCode.get({ cartId });
|
|
1609
|
+
* if (discount) {
|
|
1610
|
+
* console.log(`Code ${discount.code}: ${discount.discountValue}${discount.discountType === 'PERCENTAGE' ? '%' : '¢'} off`);
|
|
1611
|
+
* }
|
|
1612
|
+
* ```
|
|
1613
|
+
*/
|
|
1614
|
+
async get(params = {}) {
|
|
1615
|
+
const { cartId, sessionId } = params;
|
|
1616
|
+
return fetcher.request("/api/storefront/v1/discount-code/apply", {
|
|
1617
|
+
method: "GET",
|
|
1618
|
+
headers: {
|
|
1619
|
+
...cartId && { "x-cart-id": cartId },
|
|
1620
|
+
...sessionId && { "x-session-id": sessionId }
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
},
|
|
1624
|
+
/**
|
|
1625
|
+
* Remove the currently applied discount code
|
|
1626
|
+
*
|
|
1627
|
+
* @param params - Session info (cartId or sessionId)
|
|
1628
|
+
* @returns Success status
|
|
1629
|
+
*
|
|
1630
|
+
* @example
|
|
1631
|
+
* ```typescript
|
|
1632
|
+
* await client.discountCode.remove({ cartId });
|
|
1633
|
+
* ```
|
|
1634
|
+
*/
|
|
1635
|
+
async remove(params = {}) {
|
|
1636
|
+
const { cartId, sessionId } = params;
|
|
1637
|
+
return fetcher.request("/api/storefront/v1/discount-code/apply", {
|
|
1638
|
+
method: "DELETE",
|
|
1639
|
+
headers: {
|
|
1640
|
+
...cartId && { "x-cart-id": cartId },
|
|
1641
|
+
...sessionId && { "x-session-id": sessionId }
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1530
1648
|
// src/client.ts
|
|
1531
1649
|
function createStorefrontClient(config) {
|
|
1532
1650
|
if (!config.apiKey) {
|
|
@@ -1550,9 +1668,101 @@ function createStorefrontClient(config) {
|
|
|
1550
1668
|
shipping: createShippingResource(fetcher),
|
|
1551
1669
|
customer: createCustomerResource(fetcher),
|
|
1552
1670
|
order: createOrderResource(fetcher),
|
|
1553
|
-
checkout: createCheckoutResource(fetcher)
|
|
1671
|
+
checkout: createCheckoutResource(fetcher),
|
|
1672
|
+
discountCode: createDiscountCodeResource(fetcher)
|
|
1554
1673
|
};
|
|
1555
1674
|
}
|
|
1675
|
+
|
|
1676
|
+
// src/utils/discount.ts
|
|
1677
|
+
function formatDiscountValue(discount, options = {}) {
|
|
1678
|
+
const {
|
|
1679
|
+
currencySymbol = "\u20AC",
|
|
1680
|
+
currencyPosition = "after",
|
|
1681
|
+
decimals = 2,
|
|
1682
|
+
showMinus = true
|
|
1683
|
+
} = options;
|
|
1684
|
+
const prefix = showMinus ? "-" : "";
|
|
1685
|
+
if (discount.discountType === "PERCENTAGE") {
|
|
1686
|
+
return `${prefix}${discount.discountValue}%`;
|
|
1687
|
+
}
|
|
1688
|
+
const amount = (discount.discountValue / 100).toFixed(decimals);
|
|
1689
|
+
const formattedAmount = amount.replace(".", ",");
|
|
1690
|
+
if (currencyPosition === "before") {
|
|
1691
|
+
return `${prefix}${currencySymbol}${formattedAmount}`;
|
|
1692
|
+
}
|
|
1693
|
+
return `${prefix}${formattedAmount} ${currencySymbol}`;
|
|
1694
|
+
}
|
|
1695
|
+
function calculateDiscountAmount(subtotal, discount) {
|
|
1696
|
+
if (discount.discountType === "PERCENTAGE") {
|
|
1697
|
+
return Math.round(subtotal * discount.discountValue / 100);
|
|
1698
|
+
}
|
|
1699
|
+
return Math.min(discount.discountValue, subtotal);
|
|
1700
|
+
}
|
|
1701
|
+
var REMOVAL_MESSAGES = {
|
|
1702
|
+
CAMPAIGN_ACTIVE: {
|
|
1703
|
+
fi: "Alennuskoodi poistettu - kampanja-alennus aktivoitui",
|
|
1704
|
+
en: "Discount code removed - campaign discount activated"
|
|
1705
|
+
},
|
|
1706
|
+
MIN_ORDER_NOT_MET: {
|
|
1707
|
+
fi: "Alennuskoodi poistettu - ostoskorin summa alittaa minimitilauksen",
|
|
1708
|
+
en: "Discount code removed - cart total below minimum order"
|
|
1709
|
+
},
|
|
1710
|
+
CODE_INVALID: {
|
|
1711
|
+
fi: "Alennuskoodi poistettu - koodi ei ole en\xE4\xE4 voimassa",
|
|
1712
|
+
en: "Discount code removed - code is no longer valid"
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
var DEFAULT_REMOVAL_MESSAGE = {
|
|
1716
|
+
fi: "Alennuskoodi poistettu",
|
|
1717
|
+
en: "Discount code removed"
|
|
1718
|
+
};
|
|
1719
|
+
function getDiscountRemovalMessage(reason, locale = "fi") {
|
|
1720
|
+
if (!reason) {
|
|
1721
|
+
return DEFAULT_REMOVAL_MESSAGE[locale];
|
|
1722
|
+
}
|
|
1723
|
+
return REMOVAL_MESSAGES[reason]?.[locale] ?? DEFAULT_REMOVAL_MESSAGE[locale];
|
|
1724
|
+
}
|
|
1725
|
+
var APPLY_ERROR_MESSAGES = {
|
|
1726
|
+
NOT_FOUND: {
|
|
1727
|
+
fi: "Alennuskoodia ei l\xF6ydy",
|
|
1728
|
+
en: "Discount code not found"
|
|
1729
|
+
},
|
|
1730
|
+
INACTIVE: {
|
|
1731
|
+
fi: "Alennuskoodi ei ole k\xE4yt\xF6ss\xE4",
|
|
1732
|
+
en: "Discount code is not active"
|
|
1733
|
+
},
|
|
1734
|
+
NOT_STARTED: {
|
|
1735
|
+
fi: "Alennuskoodi ei ole viel\xE4 voimassa",
|
|
1736
|
+
en: "Discount code is not yet valid"
|
|
1737
|
+
},
|
|
1738
|
+
EXPIRED: {
|
|
1739
|
+
fi: "Alennuskoodi on vanhentunut",
|
|
1740
|
+
en: "Discount code has expired"
|
|
1741
|
+
},
|
|
1742
|
+
MAX_USES_REACHED: {
|
|
1743
|
+
fi: "Alennuskoodi on k\xE4ytetty loppuun",
|
|
1744
|
+
en: "Discount code usage limit reached"
|
|
1745
|
+
},
|
|
1746
|
+
MIN_ORDER_NOT_MET: {
|
|
1747
|
+
fi: "Ostoskorin summa alittaa alennuskoodin minimitilauksen",
|
|
1748
|
+
en: "Cart total is below the minimum order for this code"
|
|
1749
|
+
},
|
|
1750
|
+
CAMPAIGN_ACTIVE: {
|
|
1751
|
+
fi: "Alennuskoodia ei voi k\xE4ytt\xE4\xE4 kun kampanja-alennus on voimassa",
|
|
1752
|
+
en: "Discount code cannot be used when a campaign discount is active"
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
var DEFAULT_APPLY_ERROR = {
|
|
1756
|
+
fi: "Alennuskoodin k\xE4ytt\xF6 ep\xE4onnistui",
|
|
1757
|
+
en: "Failed to apply discount code"
|
|
1758
|
+
};
|
|
1759
|
+
function getDiscountApplyErrorMessage(errorCode, locale = "fi") {
|
|
1760
|
+
if (!errorCode) {
|
|
1761
|
+
return DEFAULT_APPLY_ERROR[locale];
|
|
1762
|
+
}
|
|
1763
|
+
const messages = APPLY_ERROR_MESSAGES[errorCode];
|
|
1764
|
+
return messages?.[locale] ?? DEFAULT_APPLY_ERROR[locale];
|
|
1765
|
+
}
|
|
1556
1766
|
export {
|
|
1557
1767
|
AuthError,
|
|
1558
1768
|
NotFoundError,
|
|
@@ -1561,7 +1771,11 @@ export {
|
|
|
1561
1771
|
ValidationError,
|
|
1562
1772
|
VerificationRequiredError,
|
|
1563
1773
|
calculateCartWithCampaigns,
|
|
1774
|
+
calculateDiscountAmount,
|
|
1564
1775
|
createStorefrontClient,
|
|
1776
|
+
formatDiscountValue,
|
|
1777
|
+
getDiscountApplyErrorMessage,
|
|
1778
|
+
getDiscountRemovalMessage,
|
|
1565
1779
|
getPriceInfo,
|
|
1566
1780
|
isSaleActive
|
|
1567
1781
|
};
|