@wtree/payload-ecommerce-coupon 3.78.8 → 3.78.9
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 +118 -77
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +118 -77
- package/dist/index.mjs.map +1 -1
- package/dist/utilities/calculateValues.d.ts +18 -1
- package/dist/utilities/calculateValues.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -721,14 +721,6 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
721
721
|
timestamps: true
|
|
722
722
|
};
|
|
723
723
|
};
|
|
724
|
-
//#endregion
|
|
725
|
-
//#region src/utilities/roundTo2.ts
|
|
726
|
-
/**
|
|
727
|
-
* Rounds a number to 2 decimal places (standard for monetary values).
|
|
728
|
-
*/
|
|
729
|
-
function roundTo2(value) {
|
|
730
|
-
return Math.round(value * 100) / 100;
|
|
731
|
-
}
|
|
732
724
|
function normalizeCurrencyCode(currencyCode) {
|
|
733
725
|
if (!currencyCode) return "AED";
|
|
734
726
|
return currencyCode.toUpperCase();
|
|
@@ -764,16 +756,35 @@ function getCartItemUnitPrice({ item, product, variant, currencyCode, defaultCur
|
|
|
764
756
|
}
|
|
765
757
|
//#endregion
|
|
766
758
|
//#region src/utilities/calculateValues.ts
|
|
759
|
+
/** Convert a normal-currency amount to integer cents. */
|
|
760
|
+
function toCents(amount) {
|
|
761
|
+
return Math.round(amount * 100);
|
|
762
|
+
}
|
|
763
|
+
/** Convert integer cents back to a normal-currency amount (2 dp max). */
|
|
764
|
+
function fromCents(cents) {
|
|
765
|
+
return Math.round(cents) / 100;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Calculate the discount amount for a coupon.
|
|
769
|
+
*
|
|
770
|
+
* @param coupon - Coupon document from DB (values in normal currency).
|
|
771
|
+
* @param cartTotal - Cart subtotal in normal currency.
|
|
772
|
+
* @returns Discount amount in normal currency (2 dp).
|
|
773
|
+
*/
|
|
767
774
|
function calculateCouponDiscount({ coupon, cartTotal }) {
|
|
768
|
-
|
|
775
|
+
const cartCents = toCents(cartTotal);
|
|
776
|
+
let discountCents = 0;
|
|
769
777
|
if (coupon.type === "percentage") {
|
|
770
|
-
|
|
771
|
-
if (coupon.maxDiscountAmount != null
|
|
778
|
+
discountCents = Math.floor(cartCents * coupon.value / 100);
|
|
779
|
+
if (coupon.maxDiscountAmount != null) {
|
|
780
|
+
const maxCents = toCents(coupon.maxDiscountAmount);
|
|
781
|
+
if (discountCents > maxCents) discountCents = maxCents;
|
|
782
|
+
}
|
|
772
783
|
} else if (coupon.type === "fixed") {
|
|
773
|
-
|
|
774
|
-
if (
|
|
784
|
+
discountCents = toCents(coupon.value);
|
|
785
|
+
if (discountCents > cartCents) discountCents = cartCents;
|
|
775
786
|
}
|
|
776
|
-
return
|
|
787
|
+
return fromCents(discountCents);
|
|
777
788
|
}
|
|
778
789
|
function relationId$5(value) {
|
|
779
790
|
if (value == null) return null;
|
|
@@ -794,19 +805,25 @@ function getRuleSplits(rule) {
|
|
|
794
805
|
customerSplit: typeof rule.customerSplit === "number" ? rule.customerSplit : typeof rule.refereeSplit === "number" ? rule.refereeSplit : 100 - partnerRaw
|
|
795
806
|
};
|
|
796
807
|
}
|
|
797
|
-
|
|
808
|
+
/**
|
|
809
|
+
* Calculate partner and customer reward for a single line item.
|
|
810
|
+
*
|
|
811
|
+
* ALL inputs are expected in CENTS.
|
|
812
|
+
* Returns rewards in CENTS, or null if the rule is inapplicable.
|
|
813
|
+
*/
|
|
814
|
+
function calculateItemRewardByRule({ rule, itemTotalCents, quantity, allowedTotalCommissionTypes }) {
|
|
798
815
|
const allowedTypes = allowedCommissionTypesSet(allowedTotalCommissionTypes);
|
|
799
816
|
if (rule.totalCommission) {
|
|
800
817
|
if (!allowedTypes.has(rule.totalCommission.type)) return null;
|
|
801
|
-
const
|
|
818
|
+
const resolvedMaxAmountCents = typeof rule.totalCommission.maxAmount === "number" && Number.isFinite(rule.totalCommission.maxAmount) ? toCents(rule.totalCommission.maxAmount) : null;
|
|
802
819
|
if (rule.totalCommission.type === "fixed" && rule.totalCommission.value == null) {
|
|
803
|
-
const
|
|
804
|
-
const
|
|
805
|
-
if (
|
|
806
|
-
let partner =
|
|
807
|
-
let customer =
|
|
808
|
-
if (
|
|
809
|
-
const maxPotForLine =
|
|
820
|
+
const partnerAmtPerUnitCents = typeof rule.partnerSplit === "number" ? toCents(rule.partnerSplit) : null;
|
|
821
|
+
const customerAmtPerUnitCents = typeof rule.customerSplit === "number" ? toCents(rule.customerSplit) : null;
|
|
822
|
+
if (partnerAmtPerUnitCents == null || customerAmtPerUnitCents == null) return null;
|
|
823
|
+
let partner = partnerAmtPerUnitCents * quantity;
|
|
824
|
+
let customer = customerAmtPerUnitCents * quantity;
|
|
825
|
+
if (resolvedMaxAmountCents != null) {
|
|
826
|
+
const maxPotForLine = resolvedMaxAmountCents * quantity;
|
|
810
827
|
const totalPot = partner + customer;
|
|
811
828
|
if (totalPot > maxPotForLine && totalPot > 0) {
|
|
812
829
|
const ratio = maxPotForLine / totalPot;
|
|
@@ -819,7 +836,6 @@ function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalComm
|
|
|
819
836
|
customer
|
|
820
837
|
};
|
|
821
838
|
}
|
|
822
|
-
let totalPot = 0;
|
|
823
839
|
if (rule.totalCommission.type === "percentage") {
|
|
824
840
|
const commissionValue = typeof rule.totalCommission.value === "number" && Number.isFinite(rule.totalCommission.value) ? rule.totalCommission.value : null;
|
|
825
841
|
if (commissionValue == null) {
|
|
@@ -827,58 +843,63 @@ function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalComm
|
|
|
827
843
|
if (partnerPercentInput == null || partnerPercentInput < 0 || partnerPercentInput > 100) return null;
|
|
828
844
|
const customerPercentInput = typeof rule.customerPercent === "number" ? rule.customerPercent : typeof rule.customerSplit === "number" ? rule.customerSplit : 100 - partnerPercentInput;
|
|
829
845
|
if (customerPercentInput == null || customerPercentInput < 0 || customerPercentInput > 100) return null;
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if (
|
|
833
|
-
const maxPotForLine =
|
|
846
|
+
let partner = Math.floor(itemTotalCents * partnerPercentInput / 100);
|
|
847
|
+
let customer = Math.floor(itemTotalCents * customerPercentInput / 100);
|
|
848
|
+
if (resolvedMaxAmountCents != null) {
|
|
849
|
+
const maxPotForLine = resolvedMaxAmountCents * quantity;
|
|
834
850
|
const totalForLine = partner + customer;
|
|
835
851
|
if (totalForLine > maxPotForLine && totalForLine > 0) {
|
|
836
852
|
const ratio = maxPotForLine / totalForLine;
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
customer: Math.floor(customer * ratio)
|
|
840
|
-
};
|
|
853
|
+
partner = Math.floor(partner * ratio);
|
|
854
|
+
customer = Math.floor(customer * ratio);
|
|
841
855
|
}
|
|
842
856
|
}
|
|
843
857
|
return {
|
|
844
|
-
partner
|
|
845
|
-
customer
|
|
858
|
+
partner,
|
|
859
|
+
customer
|
|
846
860
|
};
|
|
847
861
|
}
|
|
848
|
-
|
|
849
|
-
|
|
862
|
+
let totalPotCents = Math.floor(itemTotalCents * commissionValue / 100);
|
|
863
|
+
if (resolvedMaxAmountCents != null) {
|
|
864
|
+
const maxPotForLine = resolvedMaxAmountCents * quantity;
|
|
865
|
+
if (totalPotCents > maxPotForLine) totalPotCents = maxPotForLine;
|
|
866
|
+
}
|
|
850
867
|
const splits = getRuleSplits(rule);
|
|
851
868
|
if (!splits) return null;
|
|
852
|
-
totalPot = rule.totalCommission.value * quantity;
|
|
853
|
-
if (resolvedMaxAmount != null) {
|
|
854
|
-
const maxPotForLine = resolvedMaxAmount * quantity;
|
|
855
|
-
if (totalPot > maxPotForLine) totalPot = maxPotForLine;
|
|
856
|
-
}
|
|
857
869
|
return {
|
|
858
|
-
partner: Math.floor(
|
|
859
|
-
customer: Math.floor(
|
|
870
|
+
partner: Math.floor(totalPotCents * splits.partnerSplit / 100),
|
|
871
|
+
customer: Math.floor(totalPotCents * splits.customerSplit / 100)
|
|
860
872
|
};
|
|
861
873
|
}
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
if (
|
|
874
|
+
{
|
|
875
|
+
const splits = getRuleSplits(rule);
|
|
876
|
+
if (!splits) return null;
|
|
877
|
+
let totalPotCents = toCents(rule.totalCommission.value) * quantity;
|
|
878
|
+
if (resolvedMaxAmountCents != null) {
|
|
879
|
+
const maxPotForLine = resolvedMaxAmountCents * quantity;
|
|
880
|
+
if (totalPotCents > maxPotForLine) totalPotCents = maxPotForLine;
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
partner: Math.floor(totalPotCents * splits.partnerSplit / 100),
|
|
884
|
+
customer: Math.floor(totalPotCents * splits.customerSplit / 100)
|
|
885
|
+
};
|
|
865
886
|
}
|
|
866
|
-
const splits = getRuleSplits(rule);
|
|
867
|
-
if (!splits) return null;
|
|
868
|
-
return {
|
|
869
|
-
partner: Math.floor(totalPot * splits.partnerSplit / 100),
|
|
870
|
-
customer: Math.floor(totalPot * splits.customerSplit / 100)
|
|
871
|
-
};
|
|
872
887
|
}
|
|
873
888
|
if (rule.referrerReward && rule.refereeReward) {
|
|
874
889
|
let partner = 0;
|
|
875
|
-
if (rule.referrerReward.type === "percentage") partner =
|
|
876
|
-
else partner = rule.referrerReward.value * quantity;
|
|
877
|
-
if (rule.referrerReward.maxReward != null
|
|
890
|
+
if (rule.referrerReward.type === "percentage") partner = Math.floor(itemTotalCents * rule.referrerReward.value / 100);
|
|
891
|
+
else partner = toCents(rule.referrerReward.value) * quantity;
|
|
892
|
+
if (rule.referrerReward.maxReward != null) {
|
|
893
|
+
const maxCents = toCents(rule.referrerReward.maxReward);
|
|
894
|
+
if (partner > maxCents) partner = maxCents;
|
|
895
|
+
}
|
|
878
896
|
let customer = 0;
|
|
879
|
-
if (rule.refereeReward.type === "percentage") customer =
|
|
880
|
-
else customer = rule.refereeReward.value * quantity;
|
|
881
|
-
if (rule.refereeReward.maxReward != null
|
|
897
|
+
if (rule.refereeReward.type === "percentage") customer = Math.floor(itemTotalCents * rule.refereeReward.value / 100);
|
|
898
|
+
else customer = toCents(rule.refereeReward.value) * quantity;
|
|
899
|
+
if (rule.refereeReward.maxReward != null) {
|
|
900
|
+
const maxCents = toCents(rule.refereeReward.maxReward);
|
|
901
|
+
if (customer > maxCents) customer = maxCents;
|
|
902
|
+
}
|
|
882
903
|
return {
|
|
883
904
|
partner,
|
|
884
905
|
customer
|
|
@@ -894,12 +915,12 @@ function getItemCategoryIds(item) {
|
|
|
894
915
|
function getItemTagIds(item) {
|
|
895
916
|
return Array.isArray(item?.product?.tags) ? normalizeIds(item.product.tags) : [];
|
|
896
917
|
}
|
|
897
|
-
function selectBestRuleForItem({ rules, item,
|
|
918
|
+
function selectBestRuleForItem({ rules, item, itemTotalCents, quantity, cartTotalCents, minOrderAmountCents, allowedTotalCommissionTypes }) {
|
|
898
919
|
const allowedTypes = allowedCommissionTypesSet(allowedTotalCommissionTypes);
|
|
899
920
|
const eligibleRules = rules.filter((rule) => {
|
|
900
921
|
if (!(rule?.totalCommission?.type ? allowedTypes.has(rule.totalCommission.type) : true)) return false;
|
|
901
|
-
const
|
|
902
|
-
if (
|
|
922
|
+
const resolvedMinCents = minOrderAmountCents != null && Number.isFinite(minOrderAmountCents) ? minOrderAmountCents : typeof rule?.minOrderAmount === "number" && Number.isFinite(rule.minOrderAmount) ? toCents(rule.minOrderAmount) : null;
|
|
923
|
+
if (resolvedMinCents != null) return cartTotalCents >= resolvedMinCents;
|
|
903
924
|
return true;
|
|
904
925
|
});
|
|
905
926
|
const productId = relationId$5(item.product);
|
|
@@ -922,7 +943,7 @@ function selectBestRuleForItem({ rules, item, itemTotal, quantity, cartTotal, mi
|
|
|
922
943
|
for (const rule of candidates) {
|
|
923
944
|
const reward = calculateItemRewardByRule({
|
|
924
945
|
rule,
|
|
925
|
-
|
|
946
|
+
itemTotalCents,
|
|
926
947
|
quantity,
|
|
927
948
|
allowedTotalCommissionTypes
|
|
928
949
|
});
|
|
@@ -948,6 +969,10 @@ function selectBestRuleForItem({ rules, item, itemTotal, quantity, cartTotal, mi
|
|
|
948
969
|
}
|
|
949
970
|
return best;
|
|
950
971
|
}
|
|
972
|
+
/**
|
|
973
|
+
* Returns the effective minimum order amount for a program in NORMAL CURRENCY.
|
|
974
|
+
* Returns null if there is no minimum.
|
|
975
|
+
*/
|
|
951
976
|
function getProgramMinimumOrderAmount({ program, allowedTotalCommissionTypes }) {
|
|
952
977
|
if (typeof program?.minOrderAmount === "number" && Number.isFinite(program.minOrderAmount)) return program.minOrderAmount;
|
|
953
978
|
const rules = Array.isArray(program?.commissionRules) ? program.commissionRules : [];
|
|
@@ -960,50 +985,66 @@ function getProgramMinimumOrderAmount({ program, allowedTotalCommissionTypes })
|
|
|
960
985
|
if (!minValues.length) return null;
|
|
961
986
|
return Math.min(...minValues);
|
|
962
987
|
}
|
|
988
|
+
/**
|
|
989
|
+
* Calculate total partner commission and customer discount for a cart.
|
|
990
|
+
*
|
|
991
|
+
* All monetary inputs are in NORMAL CURRENCY.
|
|
992
|
+
* Returns results in NORMAL CURRENCY (2 dp).
|
|
993
|
+
*/
|
|
963
994
|
function calculateCommissionAndDiscount({ cartItems, program, currencyCode = "AED", cartTotal = 0, allowedTotalCommissionTypes }) {
|
|
964
995
|
const rules = Array.isArray(program?.commissionRules) ? program.commissionRules : [];
|
|
965
996
|
if (!rules.length) return {
|
|
966
997
|
partnerCommission: 0,
|
|
967
998
|
customerDiscount: 0
|
|
968
999
|
};
|
|
969
|
-
|
|
970
|
-
|
|
1000
|
+
const cartTotalCents = toCents(cartTotal);
|
|
1001
|
+
const programMinOrderAmountCents = typeof program?.minOrderAmount === "number" && Number.isFinite(program.minOrderAmount) ? toCents(program.minOrderAmount) : null;
|
|
1002
|
+
let totalPartnerCents = 0;
|
|
1003
|
+
let totalCustomerCents = 0;
|
|
971
1004
|
for (const item of cartItems) {
|
|
972
1005
|
const product = typeof item.product === "object" ? item.product : {};
|
|
973
|
-
const
|
|
1006
|
+
const itemPriceCurrency = getCartItemUnitPrice({
|
|
974
1007
|
item,
|
|
975
1008
|
product,
|
|
976
1009
|
variant: typeof item.variant === "object" ? item.variant : {},
|
|
977
1010
|
currencyCode
|
|
978
1011
|
});
|
|
979
1012
|
const quantity = item.quantity ?? 1;
|
|
980
|
-
const
|
|
1013
|
+
const itemTotalCents = toCents(itemPriceCurrency) * quantity;
|
|
981
1014
|
const bestMatch = selectBestRuleForItem({
|
|
982
1015
|
rules,
|
|
983
1016
|
item: {
|
|
984
1017
|
...item,
|
|
985
1018
|
product
|
|
986
1019
|
},
|
|
987
|
-
|
|
1020
|
+
itemTotalCents,
|
|
988
1021
|
quantity,
|
|
989
|
-
|
|
990
|
-
|
|
1022
|
+
cartTotalCents,
|
|
1023
|
+
minOrderAmountCents: programMinOrderAmountCents,
|
|
991
1024
|
allowedTotalCommissionTypes
|
|
992
1025
|
});
|
|
993
1026
|
if (!bestMatch) continue;
|
|
994
|
-
|
|
995
|
-
|
|
1027
|
+
totalPartnerCents += bestMatch.reward.partner;
|
|
1028
|
+
totalCustomerCents += bestMatch.reward.customer;
|
|
996
1029
|
}
|
|
997
|
-
const
|
|
998
|
-
const
|
|
999
|
-
if (
|
|
1000
|
-
if (
|
|
1030
|
+
const maxPartnerCents = typeof program?.maxPartnerCommissionPerOrder === "number" && Number.isFinite(program.maxPartnerCommissionPerOrder) ? toCents(program.maxPartnerCommissionPerOrder) : null;
|
|
1031
|
+
const maxCustomerCents = typeof program?.maxCustomerDiscountPerOrder === "number" && Number.isFinite(program.maxCustomerDiscountPerOrder) ? toCents(program.maxCustomerDiscountPerOrder) : null;
|
|
1032
|
+
if (maxPartnerCents != null) totalPartnerCents = Math.min(totalPartnerCents, maxPartnerCents);
|
|
1033
|
+
if (maxCustomerCents != null) totalCustomerCents = Math.min(totalCustomerCents, maxCustomerCents);
|
|
1001
1034
|
return {
|
|
1002
|
-
partnerCommission:
|
|
1003
|
-
customerDiscount:
|
|
1035
|
+
partnerCommission: fromCents(totalPartnerCents),
|
|
1036
|
+
customerDiscount: fromCents(totalCustomerCents)
|
|
1004
1037
|
};
|
|
1005
1038
|
}
|
|
1006
1039
|
//#endregion
|
|
1040
|
+
//#region src/utilities/roundTo2.ts
|
|
1041
|
+
/**
|
|
1042
|
+
* Rounds a number to 2 decimal places (standard for monetary values).
|
|
1043
|
+
*/
|
|
1044
|
+
function roundTo2(value) {
|
|
1045
|
+
return Math.round(value * 100) / 100;
|
|
1046
|
+
}
|
|
1047
|
+
//#endregion
|
|
1007
1048
|
//#region src/endpoints/applyCoupon.ts
|
|
1008
1049
|
function relationId$4(value) {
|
|
1009
1050
|
if (value == null) return null;
|