@wtree/payload-ecommerce-coupon 3.72.2 → 3.76.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/README.md +100 -75
- package/dist/endpoints/applyCoupon.d.ts.map +1 -1
- package/dist/endpoints/validateCoupon.d.ts.map +1 -1
- package/dist/hooks/recalculateCart.d.ts +4 -0
- package/dist/hooks/recalculateCart.d.ts.map +1 -0
- package/dist/index.js +289 -101
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +288 -101
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/utilities/calculateValues.d.ts +13 -0
- package/dist/utilities/calculateValues.d.ts.map +1 -0
- package/dist/utilities/pricing.d.ts +19 -0
- package/dist/utilities/pricing.d.ts.map +1 -0
- package/package.json +15 -18
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
1
2
|
|
|
2
3
|
//#region src/collections/createCouponsCollection.ts
|
|
3
4
|
const createCouponsCollection = (pluginConfig) => {
|
|
@@ -566,9 +567,119 @@ function roundTo2(value) {
|
|
|
566
567
|
return Math.round(value * 100) / 100;
|
|
567
568
|
}
|
|
568
569
|
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/utilities/pricing.ts
|
|
572
|
+
const DEFAULT_PRICE_CURRENCY = "AED";
|
|
573
|
+
function normalizeCurrencyCode(currencyCode) {
|
|
574
|
+
if (!currencyCode) return DEFAULT_PRICE_CURRENCY;
|
|
575
|
+
return currencyCode.toUpperCase();
|
|
576
|
+
}
|
|
577
|
+
function readNumberField(entity, key) {
|
|
578
|
+
if (!entity || typeof entity !== "object") return void 0;
|
|
579
|
+
const value = entity[key];
|
|
580
|
+
return typeof value === "number" ? value : void 0;
|
|
581
|
+
}
|
|
582
|
+
function getPriceFieldKey(currencyCode) {
|
|
583
|
+
return `priceIn${normalizeCurrencyCode(currencyCode)}`;
|
|
584
|
+
}
|
|
585
|
+
function readMoneyField(entity, currencyCode, defaultCurrencyCode = DEFAULT_PRICE_CURRENCY) {
|
|
586
|
+
if (!entity) return void 0;
|
|
587
|
+
const primaryField = getPriceFieldKey(currencyCode);
|
|
588
|
+
const primary = readNumberField(entity, primaryField);
|
|
589
|
+
if (typeof primary === "number") return primary;
|
|
590
|
+
const fallbackField = getPriceFieldKey(defaultCurrencyCode);
|
|
591
|
+
if (fallbackField !== primaryField) {
|
|
592
|
+
const fallback = readNumberField(entity, fallbackField);
|
|
593
|
+
if (typeof fallback === "number") return fallback;
|
|
594
|
+
}
|
|
595
|
+
return typeof entity.price === "number" ? entity.price : void 0;
|
|
596
|
+
}
|
|
597
|
+
function resolveMoneyField(entity, currencyCode, defaultCurrencyCode = DEFAULT_PRICE_CURRENCY) {
|
|
598
|
+
return readMoneyField(entity, currencyCode, defaultCurrencyCode) ?? 0;
|
|
599
|
+
}
|
|
600
|
+
function getCartItemUnitPrice({ item, product, variant, currencyCode, defaultCurrencyCode = DEFAULT_PRICE_CURRENCY }) {
|
|
601
|
+
if (typeof item?.price === "number") return item.price;
|
|
602
|
+
if (typeof item?.unitPrice === "number") return item.unitPrice;
|
|
603
|
+
if (variant) return resolveMoneyField(variant, currencyCode, defaultCurrencyCode);
|
|
604
|
+
return resolveMoneyField(product, currencyCode, defaultCurrencyCode);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
//#endregion
|
|
608
|
+
//#region src/utilities/calculateValues.ts
|
|
609
|
+
function calculateCouponDiscount({ coupon, cartTotal }) {
|
|
610
|
+
let discount = 0;
|
|
611
|
+
if (coupon.type === "percentage") {
|
|
612
|
+
discount = roundTo2(cartTotal * coupon.value / 100);
|
|
613
|
+
if (coupon.maxDiscountAmount != null && discount > coupon.maxDiscountAmount) discount = roundTo2(coupon.maxDiscountAmount);
|
|
614
|
+
} else if (coupon.type === "fixed") {
|
|
615
|
+
discount = roundTo2(coupon.value);
|
|
616
|
+
if (discount > cartTotal) discount = roundTo2(cartTotal);
|
|
617
|
+
}
|
|
618
|
+
return roundTo2(discount);
|
|
619
|
+
}
|
|
620
|
+
function calculateCommissionAndDiscount({ cartItems, program, currencyCode = "AED" }) {
|
|
621
|
+
const rules = program.commissionRules || [];
|
|
622
|
+
if (rules.length === 0) return {
|
|
623
|
+
partnerCommission: 0,
|
|
624
|
+
customerDiscount: 0
|
|
625
|
+
};
|
|
626
|
+
let totalPartnerCommission = 0;
|
|
627
|
+
let totalCustomerDiscount = 0;
|
|
628
|
+
for (const item of cartItems) {
|
|
629
|
+
const rule = findApplicableCommissionRule(rules, item);
|
|
630
|
+
if (!rule) continue;
|
|
631
|
+
const itemPrice = getCartItemUnitPrice({
|
|
632
|
+
item,
|
|
633
|
+
product: typeof item.product === "object" ? item.product : {},
|
|
634
|
+
variant: typeof item.variant === "object" ? item.variant : {},
|
|
635
|
+
currencyCode
|
|
636
|
+
});
|
|
637
|
+
const quantity = item.quantity ?? 1;
|
|
638
|
+
const itemTotal = itemPrice * quantity;
|
|
639
|
+
let itemPartner;
|
|
640
|
+
let itemCustomer;
|
|
641
|
+
if (rule.basis === "shared") {
|
|
642
|
+
if (!rule.totalCommission || rule.referrerSplit == null || rule.refereeSplit == null) continue;
|
|
643
|
+
let totalPot;
|
|
644
|
+
if (rule.totalCommission.type === "percentage") totalPot = itemTotal * rule.totalCommission.value / 100;
|
|
645
|
+
else totalPot = rule.totalCommission.value * quantity;
|
|
646
|
+
if (rule.totalCommission.maxAmount != null && totalPot > rule.totalCommission.maxAmount) totalPot = rule.totalCommission.maxAmount;
|
|
647
|
+
itemPartner = Math.floor(totalPot * (rule.referrerSplit || 0) / 100);
|
|
648
|
+
itemCustomer = Math.floor(totalPot * (rule.refereeSplit || 0) / 100);
|
|
649
|
+
} else {
|
|
650
|
+
if (!rule.referrerReward || !rule.refereeReward) continue;
|
|
651
|
+
if (rule.referrerReward.type === "percentage") itemPartner = itemTotal * rule.referrerReward.value / 100;
|
|
652
|
+
else itemPartner = rule.referrerReward.value * quantity;
|
|
653
|
+
if (rule.referrerReward.maxReward != null && itemPartner > rule.referrerReward.maxReward) itemPartner = rule.referrerReward.maxReward;
|
|
654
|
+
if (rule.refereeReward.type === "percentage") itemCustomer = itemTotal * rule.refereeReward.value / 100;
|
|
655
|
+
else itemCustomer = rule.refereeReward.value * quantity;
|
|
656
|
+
if (rule.refereeReward.maxReward != null && itemCustomer > rule.refereeReward.maxReward) itemCustomer = rule.refereeReward.maxReward;
|
|
657
|
+
}
|
|
658
|
+
totalPartnerCommission += itemPartner;
|
|
659
|
+
totalCustomerDiscount += itemCustomer;
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
partnerCommission: totalPartnerCommission,
|
|
663
|
+
customerDiscount: totalCustomerDiscount
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function findApplicableCommissionRule(rules, item) {
|
|
667
|
+
const productId = typeof item.product === "string" ? item.product : item.product?.id;
|
|
668
|
+
const categoryId = item.category ?? item.product?.category;
|
|
669
|
+
const productRule = rules.find((r) => r.appliesTo === "products" && r.products?.some((p) => (typeof p === "string" ? p : p?.id) === productId));
|
|
670
|
+
if (productRule) return productRule;
|
|
671
|
+
if (categoryId) {
|
|
672
|
+
const categoryRule = rules.find((r) => r.appliesTo === "categories" && r.categories?.some((c) => (typeof c === "string" ? c : c?.id) === categoryId));
|
|
673
|
+
if (categoryRule) return categoryRule;
|
|
674
|
+
}
|
|
675
|
+
return rules.find((r) => r.appliesTo === "all") ?? null;
|
|
676
|
+
}
|
|
677
|
+
|
|
569
678
|
//#endregion
|
|
570
679
|
//#region src/endpoints/applyCoupon.ts
|
|
680
|
+
const globalDebugLogs = [];
|
|
571
681
|
const applyCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
682
|
+
globalDebugLogs.length = 0;
|
|
572
683
|
const { payload } = req;
|
|
573
684
|
const { code, cartID, customerEmail } = req.data || {};
|
|
574
685
|
if (!code || !cartID) return Response.json({
|
|
@@ -578,7 +689,8 @@ const applyCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
|
578
689
|
try {
|
|
579
690
|
const cartQuery = await payload.findByID({
|
|
580
691
|
collection: "carts",
|
|
581
|
-
id: cartID
|
|
692
|
+
id: cartID,
|
|
693
|
+
depth: 2
|
|
582
694
|
});
|
|
583
695
|
if (!cartQuery) return Response.json({
|
|
584
696
|
success: false,
|
|
@@ -627,11 +739,21 @@ const applyCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
|
627
739
|
}
|
|
628
740
|
};
|
|
629
741
|
async function handleCouponCode({ payload, code, cartID, cart, customerEmail, pluginConfig }) {
|
|
630
|
-
|
|
742
|
+
let couponQuery = await payload.find({
|
|
631
743
|
collection: pluginConfig.collections.couponsSlug,
|
|
632
744
|
where: { code: { equals: code } },
|
|
633
745
|
limit: 1
|
|
634
746
|
});
|
|
747
|
+
if (!couponQuery.docs.length) couponQuery = await payload.find({
|
|
748
|
+
collection: pluginConfig.collections.couponsSlug,
|
|
749
|
+
where: { code: { equals: code.toLowerCase() } },
|
|
750
|
+
limit: 1
|
|
751
|
+
});
|
|
752
|
+
if (!couponQuery.docs.length) couponQuery = await payload.find({
|
|
753
|
+
collection: pluginConfig.collections.couponsSlug,
|
|
754
|
+
where: { code: { equals: code.toUpperCase() } },
|
|
755
|
+
limit: 1
|
|
756
|
+
});
|
|
635
757
|
if (!couponQuery.docs.length) return Response.json({
|
|
636
758
|
success: false,
|
|
637
759
|
error: "Invalid coupon code"
|
|
@@ -685,15 +807,10 @@ async function handleCouponCode({ payload, code, cartID, cart, customerEmail, pl
|
|
|
685
807
|
success: false,
|
|
686
808
|
error: `Maximum order value of ${coupon.maxOrderValue} ${pluginConfig.defaultCurrency} exceeded`
|
|
687
809
|
}, { status: 400 });
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
} else if (coupon.type === "fixed") {
|
|
693
|
-
discount = roundTo2(coupon.value);
|
|
694
|
-
if (discount > cartTotal) discount = roundTo2(cartTotal);
|
|
695
|
-
}
|
|
696
|
-
const discountAmount = roundTo2(discount);
|
|
810
|
+
const discountAmount = calculateCouponDiscount({
|
|
811
|
+
coupon,
|
|
812
|
+
cartTotal
|
|
813
|
+
});
|
|
697
814
|
const total = roundTo2(Math.max(0, cartTotal - discountAmount));
|
|
698
815
|
await payload.update({
|
|
699
816
|
collection: "carts",
|
|
@@ -713,16 +830,29 @@ async function handleCouponCode({ payload, code, cartID, cart, customerEmail, pl
|
|
|
713
830
|
value: coupon.value
|
|
714
831
|
},
|
|
715
832
|
discount: discountAmount,
|
|
716
|
-
currency: pluginConfig.defaultCurrency
|
|
833
|
+
currency: pluginConfig.defaultCurrency,
|
|
834
|
+
debug: globalDebugLogs
|
|
717
835
|
});
|
|
718
836
|
}
|
|
719
837
|
async function handleReferralCode({ payload, code, cartID, cart, customerEmail: _customerEmail, pluginConfig }) {
|
|
720
|
-
|
|
838
|
+
let referralQuery = await payload.find({
|
|
721
839
|
collection: pluginConfig.collections.referralCodesSlug,
|
|
722
840
|
where: { code: { equals: code } },
|
|
723
841
|
limit: 1,
|
|
724
842
|
depth: 1
|
|
725
843
|
});
|
|
844
|
+
if (!referralQuery.docs.length) referralQuery = await payload.find({
|
|
845
|
+
collection: pluginConfig.collections.referralCodesSlug,
|
|
846
|
+
where: { code: { equals: code.toLowerCase() } },
|
|
847
|
+
limit: 1,
|
|
848
|
+
depth: 1
|
|
849
|
+
});
|
|
850
|
+
if (!referralQuery.docs.length) referralQuery = await payload.find({
|
|
851
|
+
collection: pluginConfig.collections.referralCodesSlug,
|
|
852
|
+
where: { code: { equals: code.toUpperCase() } },
|
|
853
|
+
limit: 1,
|
|
854
|
+
depth: 1
|
|
855
|
+
});
|
|
726
856
|
if (!referralQuery.docs.length) return Response.json({
|
|
727
857
|
success: false,
|
|
728
858
|
error: "Invalid referral code"
|
|
@@ -768,10 +898,8 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
|
|
|
768
898
|
error: `Minimum order value of ${program.minOrderValue} ${pluginConfig.defaultCurrency} required`
|
|
769
899
|
}, { status: 400 });
|
|
770
900
|
const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
|
|
771
|
-
cart,
|
|
772
|
-
program
|
|
773
|
-
pluginConfig,
|
|
774
|
-
payload
|
|
901
|
+
cartItems: cart.items || [],
|
|
902
|
+
program
|
|
775
903
|
});
|
|
776
904
|
const roundedPartnerCommission = roundTo2(partnerCommission);
|
|
777
905
|
const roundedCustomerDiscount = roundTo2(customerDiscount);
|
|
@@ -792,88 +920,10 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
|
|
|
792
920
|
referralCode: { code: referralCode.code },
|
|
793
921
|
partnerCommission: roundedPartnerCommission,
|
|
794
922
|
customerDiscount: roundedCustomerDiscount,
|
|
795
|
-
currency: pluginConfig.defaultCurrency
|
|
923
|
+
currency: pluginConfig.defaultCurrency,
|
|
924
|
+
debug: globalDebugLogs
|
|
796
925
|
});
|
|
797
926
|
}
|
|
798
|
-
function calculateCommissionAndDiscount({ cart, program, pluginConfig: _pluginConfig, payload: _payload }) {
|
|
799
|
-
const cartTotal = cart.subtotal || cart.total || 0;
|
|
800
|
-
const cartItems = cart.items || [];
|
|
801
|
-
const rules = program.commissionRules || [];
|
|
802
|
-
if (rules.length === 0) return {
|
|
803
|
-
partnerCommission: 0,
|
|
804
|
-
customerDiscount: 0
|
|
805
|
-
};
|
|
806
|
-
let totalPartnerCommission = 0;
|
|
807
|
-
let totalCustomerDiscount = 0;
|
|
808
|
-
for (const item of cartItems) {
|
|
809
|
-
const rule = findApplicableCommissionRule(rules, item);
|
|
810
|
-
if (!rule) continue;
|
|
811
|
-
const itemPrice = item.price ?? item.unitPrice ?? 0;
|
|
812
|
-
const quantity = item.quantity ?? 1;
|
|
813
|
-
const itemTotal = itemPrice * quantity;
|
|
814
|
-
console.log("Calculating Item:", {
|
|
815
|
-
id: item.id,
|
|
816
|
-
itemPrice,
|
|
817
|
-
quantity,
|
|
818
|
-
itemTotal,
|
|
819
|
-
ruleBasis: rule.basis
|
|
820
|
-
});
|
|
821
|
-
let itemPartner = 0;
|
|
822
|
-
let itemCustomer = 0;
|
|
823
|
-
if (rule.basis === "shared") {
|
|
824
|
-
if (!rule.totalCommission || rule.referrerSplit == null || rule.refereeSplit == null) {
|
|
825
|
-
console.error("Missing shared commission fields", rule);
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
let totalPot = 0;
|
|
829
|
-
if (rule.totalCommission.type === "percentage") totalPot = itemTotal * rule.totalCommission.value / 100;
|
|
830
|
-
else totalPot = rule.totalCommission.value * quantity;
|
|
831
|
-
console.log("Shared Commission Pot:", {
|
|
832
|
-
type: rule.totalCommission.type,
|
|
833
|
-
value: rule.totalCommission.value,
|
|
834
|
-
totalPot
|
|
835
|
-
});
|
|
836
|
-
if (rule.totalCommission.maxAmount != null && totalPot > rule.totalCommission.maxAmount) {
|
|
837
|
-
totalPot = rule.totalCommission.maxAmount;
|
|
838
|
-
console.log("Total pot capped at:", totalPot);
|
|
839
|
-
}
|
|
840
|
-
itemPartner = totalPot * rule.referrerSplit / 100;
|
|
841
|
-
itemCustomer = totalPot * rule.refereeSplit / 100;
|
|
842
|
-
console.log("Shared Splits:", {
|
|
843
|
-
referrerSplit: rule.referrerSplit,
|
|
844
|
-
refereeSplit: rule.refereeSplit,
|
|
845
|
-
itemPartner,
|
|
846
|
-
itemCustomer
|
|
847
|
-
});
|
|
848
|
-
} else {
|
|
849
|
-
if (!rule.referrerReward || !rule.refereeReward) continue;
|
|
850
|
-
if (rule.referrerReward.type === "percentage") itemPartner = itemTotal * rule.referrerReward.value / 100;
|
|
851
|
-
else itemPartner = rule.referrerReward.value * quantity;
|
|
852
|
-
if (rule.referrerReward.maxReward != null && itemPartner > rule.referrerReward.maxReward) itemPartner = rule.referrerReward.maxReward;
|
|
853
|
-
if (rule.refereeReward.type === "percentage") itemCustomer = itemTotal * rule.refereeReward.value / 100;
|
|
854
|
-
else itemCustomer = rule.refereeReward.value * quantity;
|
|
855
|
-
if (rule.refereeReward.maxReward != null && itemCustomer > rule.refereeReward.maxReward) itemCustomer = rule.refereeReward.maxReward;
|
|
856
|
-
}
|
|
857
|
-
totalPartnerCommission += itemPartner;
|
|
858
|
-
totalCustomerDiscount += itemCustomer;
|
|
859
|
-
}
|
|
860
|
-
if (totalCustomerDiscount > cartTotal) totalCustomerDiscount = cartTotal;
|
|
861
|
-
return {
|
|
862
|
-
partnerCommission: totalPartnerCommission,
|
|
863
|
-
customerDiscount: totalCustomerDiscount
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
function findApplicableCommissionRule(rules, item) {
|
|
867
|
-
const productId = typeof item.product === "string" ? item.product : item.product?.id;
|
|
868
|
-
const categoryId = item.category ?? item.product?.category;
|
|
869
|
-
const productRule = rules.find((r) => r.appliesTo === "products" && r.products?.some((p) => (typeof p === "string" ? p : p?.id) === productId));
|
|
870
|
-
if (productRule) return productRule;
|
|
871
|
-
if (categoryId) {
|
|
872
|
-
const categoryRule = rules.find((r) => r.appliesTo === "categories" && r.categories?.some((c) => (typeof c === "string" ? c : c?.id) === categoryId));
|
|
873
|
-
if (categoryRule) return categoryRule;
|
|
874
|
-
}
|
|
875
|
-
return rules.find((r) => r.appliesTo === "all") ?? null;
|
|
876
|
-
}
|
|
877
927
|
const applyCouponEndpoint = ({ pluginConfig }) => ({
|
|
878
928
|
path: pluginConfig.endpoints.applyCoupon,
|
|
879
929
|
method: "post",
|
|
@@ -1036,11 +1086,21 @@ const validateCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
|
1036
1086
|
}
|
|
1037
1087
|
};
|
|
1038
1088
|
async function validateCouponCode$1({ payload, code, cartValue, customerEmail, pluginConfig }) {
|
|
1039
|
-
|
|
1089
|
+
let coupon = await payload.find({
|
|
1040
1090
|
collection: pluginConfig.collections.couponsSlug,
|
|
1041
1091
|
where: { code: { equals: code } },
|
|
1042
1092
|
limit: 1
|
|
1043
1093
|
});
|
|
1094
|
+
if (!coupon.docs.length) coupon = await payload.find({
|
|
1095
|
+
collection: pluginConfig.collections.couponsSlug,
|
|
1096
|
+
where: { code: { equals: code.toLowerCase() } },
|
|
1097
|
+
limit: 1
|
|
1098
|
+
});
|
|
1099
|
+
if (!coupon.docs.length) coupon = await payload.find({
|
|
1100
|
+
collection: pluginConfig.collections.couponsSlug,
|
|
1101
|
+
where: { code: { equals: code.toUpperCase() } },
|
|
1102
|
+
limit: 1
|
|
1103
|
+
});
|
|
1044
1104
|
if (!coupon.docs.length) return Response.json({
|
|
1045
1105
|
success: false,
|
|
1046
1106
|
error: "Invalid coupon code"
|
|
@@ -1112,11 +1172,21 @@ async function validateCouponCode$1({ payload, code, cartValue, customerEmail, p
|
|
|
1112
1172
|
});
|
|
1113
1173
|
}
|
|
1114
1174
|
async function validateReferralCode({ payload, code, cartID, pluginConfig }) {
|
|
1115
|
-
|
|
1175
|
+
let referral = await payload.find({
|
|
1116
1176
|
collection: pluginConfig.collections.referralCodesSlug,
|
|
1117
1177
|
where: { code: { equals: code } },
|
|
1118
1178
|
limit: 1
|
|
1119
1179
|
});
|
|
1180
|
+
if (!referral.docs.length) referral = await payload.find({
|
|
1181
|
+
collection: pluginConfig.collections.referralCodesSlug,
|
|
1182
|
+
where: { code: { equals: code.toLowerCase() } },
|
|
1183
|
+
limit: 1
|
|
1184
|
+
});
|
|
1185
|
+
if (!referral.docs.length) referral = await payload.find({
|
|
1186
|
+
collection: pluginConfig.collections.referralCodesSlug,
|
|
1187
|
+
where: { code: { equals: code.toUpperCase() } },
|
|
1188
|
+
limit: 1
|
|
1189
|
+
});
|
|
1120
1190
|
if (!referral.docs.length) return Response.json({
|
|
1121
1191
|
success: false,
|
|
1122
1192
|
error: "Referral code not found"
|
|
@@ -1158,12 +1228,12 @@ async function validateReferralCode({ payload, code, cartID, pluginConfig }) {
|
|
|
1158
1228
|
const itemPrice = item.price ?? item.unitPrice ?? 0;
|
|
1159
1229
|
const quantity = item.quantity ?? 1;
|
|
1160
1230
|
const itemTotal = itemPrice * quantity;
|
|
1161
|
-
let itemPartner
|
|
1231
|
+
let itemPartner;
|
|
1162
1232
|
if (rule.referrerReward.type === "percentage") itemPartner = itemTotal * rule.referrerReward.value / 100;
|
|
1163
1233
|
else itemPartner = rule.referrerReward.value * quantity;
|
|
1164
1234
|
if (rule.referrerReward.maxReward != null && itemPartner > rule.referrerReward.maxReward) itemPartner = rule.referrerReward.maxReward;
|
|
1165
1235
|
totalPartnerCommission += itemPartner;
|
|
1166
|
-
let itemCustomer
|
|
1236
|
+
let itemCustomer;
|
|
1167
1237
|
if (rule.refereeReward.type === "percentage") itemCustomer = itemTotal * rule.refereeReward.value / 100;
|
|
1168
1238
|
else itemCustomer = rule.refereeReward.value * quantity;
|
|
1169
1239
|
if (rule.refereeReward.maxReward != null && itemCustomer > rule.refereeReward.maxReward) itemCustomer = rule.refereeReward.maxReward;
|
|
@@ -1201,6 +1271,115 @@ const validateCouponEndpoint = ({ pluginConfig }) => ({
|
|
|
1201
1271
|
handler: validateCouponHandler({ pluginConfig })
|
|
1202
1272
|
});
|
|
1203
1273
|
|
|
1274
|
+
//#endregion
|
|
1275
|
+
//#region src/hooks/recalculateCart.ts
|
|
1276
|
+
const recalculateCartHook = (pluginConfig) => async ({ data, req, originalDoc }) => {
|
|
1277
|
+
if (!req.payload) return data;
|
|
1278
|
+
const effectiveItems = data.items || originalDoc?.items || [];
|
|
1279
|
+
console.log("[RecalculateCart] Hook triggered", {
|
|
1280
|
+
hasDataItems: !!data.items,
|
|
1281
|
+
dataItemsCount: data.items?.length,
|
|
1282
|
+
originalItemsCount: originalDoc?.items?.length,
|
|
1283
|
+
effectiveItemsCount: effectiveItems.length
|
|
1284
|
+
});
|
|
1285
|
+
if (!effectiveItems.length) return {
|
|
1286
|
+
...data,
|
|
1287
|
+
partnerCommission: 0,
|
|
1288
|
+
customerDiscount: 0,
|
|
1289
|
+
discountAmount: 0,
|
|
1290
|
+
total: 0
|
|
1291
|
+
};
|
|
1292
|
+
const appliedReferralCode = data.appliedReferralCode ?? originalDoc?.appliedReferralCode;
|
|
1293
|
+
const appliedCoupon = data.appliedCoupon ?? originalDoc?.appliedCoupon;
|
|
1294
|
+
if (!appliedReferralCode && !appliedCoupon) {
|
|
1295
|
+
if (data.appliedReferralCode === null || data.appliedCoupon === null) return {
|
|
1296
|
+
...data,
|
|
1297
|
+
partnerCommission: 0,
|
|
1298
|
+
customerDiscount: 0,
|
|
1299
|
+
discountAmount: 0
|
|
1300
|
+
};
|
|
1301
|
+
return data;
|
|
1302
|
+
}
|
|
1303
|
+
const productIds = effectiveItems.map((item) => typeof item.product === "string" ? item.product : item.product?.id).filter(Boolean);
|
|
1304
|
+
if (!productIds.length) return data;
|
|
1305
|
+
const productsQuery = await req.payload.find({
|
|
1306
|
+
collection: "products",
|
|
1307
|
+
where: { id: { in: productIds } },
|
|
1308
|
+
limit: productIds.length
|
|
1309
|
+
});
|
|
1310
|
+
const productsMap = new Map(productsQuery.docs.map((p) => [p.id, p]));
|
|
1311
|
+
let calculatedSubtotal = 0;
|
|
1312
|
+
const enrichedItems = effectiveItems.map((item) => {
|
|
1313
|
+
const productId = typeof item.product === "string" ? item.product : item.product?.id;
|
|
1314
|
+
const product = productsMap.get(productId) || {};
|
|
1315
|
+
const itemPrice = getCartItemUnitPrice({
|
|
1316
|
+
item,
|
|
1317
|
+
product,
|
|
1318
|
+
variant: typeof item.variant === "object" ? item.variant : void 0,
|
|
1319
|
+
currencyCode: pluginConfig.defaultCurrency
|
|
1320
|
+
});
|
|
1321
|
+
calculatedSubtotal += itemPrice * (item.quantity ?? 1);
|
|
1322
|
+
console.log("[RecalculateCart] Item processed", {
|
|
1323
|
+
productId,
|
|
1324
|
+
quantity: item.quantity,
|
|
1325
|
+
priceUsed: itemPrice,
|
|
1326
|
+
currentSubtotal: calculatedSubtotal
|
|
1327
|
+
});
|
|
1328
|
+
return {
|
|
1329
|
+
...item,
|
|
1330
|
+
product,
|
|
1331
|
+
price: itemPrice
|
|
1332
|
+
};
|
|
1333
|
+
});
|
|
1334
|
+
if (appliedReferralCode && pluginConfig.enableReferrals) {
|
|
1335
|
+
const referralQuery = await req.payload.find({
|
|
1336
|
+
collection: pluginConfig.collections.referralCodesSlug,
|
|
1337
|
+
where: { id: { equals: typeof appliedReferralCode === "string" ? appliedReferralCode : appliedReferralCode.id } },
|
|
1338
|
+
limit: 1,
|
|
1339
|
+
depth: 1
|
|
1340
|
+
});
|
|
1341
|
+
if (referralQuery.docs.length) {
|
|
1342
|
+
const referralCode = referralQuery.docs[0];
|
|
1343
|
+
const program = typeof referralCode.program === "object" ? referralCode.program : null;
|
|
1344
|
+
if (program) {
|
|
1345
|
+
const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
|
|
1346
|
+
cartItems: enrichedItems,
|
|
1347
|
+
program,
|
|
1348
|
+
currencyCode: pluginConfig.defaultCurrency
|
|
1349
|
+
});
|
|
1350
|
+
const roundedCustomerDiscount = roundTo2(customerDiscount);
|
|
1351
|
+
data.partnerCommission = roundTo2(partnerCommission);
|
|
1352
|
+
data.customerDiscount = roundedCustomerDiscount;
|
|
1353
|
+
data.total = Math.max(0, calculatedSubtotal - roundedCustomerDiscount);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (appliedCoupon && (!appliedReferralCode || pluginConfig.referralConfig.allowBothSystems)) {
|
|
1358
|
+
const couponQuery = await req.payload.find({
|
|
1359
|
+
collection: pluginConfig.collections.couponsSlug,
|
|
1360
|
+
where: { id: { equals: typeof appliedCoupon === "string" ? appliedCoupon : appliedCoupon.id } },
|
|
1361
|
+
limit: 1
|
|
1362
|
+
});
|
|
1363
|
+
if (couponQuery.docs.length) {
|
|
1364
|
+
const coupon = couponQuery.docs[0];
|
|
1365
|
+
const discountAmount = calculateCouponDiscount({
|
|
1366
|
+
coupon,
|
|
1367
|
+
cartTotal: calculatedSubtotal
|
|
1368
|
+
});
|
|
1369
|
+
console.log("[RecalculateCart] Coupon Logic", {
|
|
1370
|
+
appliedCoupon,
|
|
1371
|
+
couponId: coupon.id,
|
|
1372
|
+
cartTotal: calculatedSubtotal,
|
|
1373
|
+
discountAmount
|
|
1374
|
+
});
|
|
1375
|
+
data.discountAmount = discountAmount;
|
|
1376
|
+
const currentDiscount = data.customerDiscount || 0;
|
|
1377
|
+
data.total = Math.max(0, calculatedSubtotal - currentDiscount - discountAmount);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return data;
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1204
1383
|
//#endregion
|
|
1205
1384
|
//#region src/utilities/sanitizePluginConfig.ts
|
|
1206
1385
|
const sanitizePluginConfig = ({ pluginConfig }) => {
|
|
@@ -1418,6 +1597,15 @@ const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConf
|
|
|
1418
1597
|
}]);
|
|
1419
1598
|
}
|
|
1420
1599
|
}
|
|
1600
|
+
const cartIndex = incomingConfig.collections.findIndex((c) => c.slug === "carts");
|
|
1601
|
+
if (cartIndex > -1) {
|
|
1602
|
+
const collection = incomingConfig.collections[cartIndex];
|
|
1603
|
+
collection.hooks = {
|
|
1604
|
+
...collection.hooks,
|
|
1605
|
+
beforeChange: [...collection.hooks?.beforeChange || [], recalculateCartHook(pluginConfig)]
|
|
1606
|
+
};
|
|
1607
|
+
incomingConfig.collections[cartIndex] = collection;
|
|
1608
|
+
}
|
|
1421
1609
|
return incomingConfig;
|
|
1422
1610
|
};
|
|
1423
1611
|
|