@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 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
- let discount = 0;
775
+ const cartCents = toCents(cartTotal);
776
+ let discountCents = 0;
769
777
  if (coupon.type === "percentage") {
770
- discount = roundTo2(cartTotal * coupon.value / 100);
771
- if (coupon.maxDiscountAmount != null && discount > coupon.maxDiscountAmount) discount = roundTo2(coupon.maxDiscountAmount);
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
- discount = roundTo2(coupon.value);
774
- if (discount > cartTotal) discount = roundTo2(cartTotal);
784
+ discountCents = toCents(coupon.value);
785
+ if (discountCents > cartCents) discountCents = cartCents;
775
786
  }
776
- return roundTo2(discount);
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
- function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalCommissionTypes }) {
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 resolvedMaxAmount = typeof rule.totalCommission.maxAmount === "number" && Number.isFinite(rule.totalCommission.maxAmount) ? rule.totalCommission.maxAmount : null;
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 partnerAmtPerUnit = typeof rule.partnerSplit === "number" ? rule.partnerSplit : null;
804
- const customerAmtPerUnit = typeof rule.customerSplit === "number" ? rule.customerSplit : null;
805
- if (partnerAmtPerUnit == null || customerAmtPerUnit == null) return null;
806
- let partner = partnerAmtPerUnit * quantity;
807
- let customer = customerAmtPerUnit * quantity;
808
- if (resolvedMaxAmount != null) {
809
- const maxPotForLine = resolvedMaxAmount * quantity;
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
- const partner = itemTotal * partnerPercentInput / 100;
831
- const customer = itemTotal * customerPercentInput / 100;
832
- if (resolvedMaxAmount != null) {
833
- const maxPotForLine = resolvedMaxAmount * quantity;
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
- return {
838
- partner: Math.floor(partner * ratio),
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: Math.floor(partner),
845
- customer: Math.floor(customer)
858
+ partner,
859
+ customer
846
860
  };
847
861
  }
848
- totalPot = itemTotal * commissionValue / 100;
849
- } else {
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(totalPot * splits.partnerSplit / 100),
859
- customer: Math.floor(totalPot * splits.customerSplit / 100)
870
+ partner: Math.floor(totalPotCents * splits.partnerSplit / 100),
871
+ customer: Math.floor(totalPotCents * splits.customerSplit / 100)
860
872
  };
861
873
  }
862
- if (resolvedMaxAmount != null) {
863
- const maxPotForLine = resolvedMaxAmount * quantity;
864
- if (totalPot > maxPotForLine) totalPot = maxPotForLine;
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 = itemTotal * rule.referrerReward.value / 100;
876
- else partner = rule.referrerReward.value * quantity;
877
- if (rule.referrerReward.maxReward != null && partner > rule.referrerReward.maxReward) partner = rule.referrerReward.maxReward;
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 = itemTotal * rule.refereeReward.value / 100;
880
- else customer = rule.refereeReward.value * quantity;
881
- if (rule.refereeReward.maxReward != null && customer > rule.refereeReward.maxReward) customer = rule.refereeReward.maxReward;
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, itemTotal, quantity, cartTotal, minOrderAmount, allowedTotalCommissionTypes }) {
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 resolvedMinOrderAmount = typeof minOrderAmount === "number" && Number.isFinite(minOrderAmount) ? minOrderAmount : typeof rule?.minOrderAmount === "number" && Number.isFinite(rule.minOrderAmount) ? rule.minOrderAmount : null;
902
- if (resolvedMinOrderAmount != null) return cartTotal >= resolvedMinOrderAmount;
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
- itemTotal,
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
- let totalPartnerCommission = 0;
970
- let totalCustomerDiscount = 0;
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 itemPrice = getCartItemUnitPrice({
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 itemTotal = itemPrice * quantity;
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
- itemTotal,
1020
+ itemTotalCents,
988
1021
  quantity,
989
- cartTotal,
990
- minOrderAmount: typeof program?.minOrderAmount === "number" && Number.isFinite(program.minOrderAmount) ? program.minOrderAmount : null,
1022
+ cartTotalCents,
1023
+ minOrderAmountCents: programMinOrderAmountCents,
991
1024
  allowedTotalCommissionTypes
992
1025
  });
993
1026
  if (!bestMatch) continue;
994
- totalPartnerCommission += bestMatch.reward.partner;
995
- totalCustomerDiscount += bestMatch.reward.customer;
1027
+ totalPartnerCents += bestMatch.reward.partner;
1028
+ totalCustomerCents += bestMatch.reward.customer;
996
1029
  }
997
- const maxPartnerCommissionPerOrder = typeof program?.maxPartnerCommissionPerOrder === "number" && Number.isFinite(program.maxPartnerCommissionPerOrder) ? program.maxPartnerCommissionPerOrder : null;
998
- const maxCustomerDiscountPerOrder = typeof program?.maxCustomerDiscountPerOrder === "number" && Number.isFinite(program.maxCustomerDiscountPerOrder) ? program.maxCustomerDiscountPerOrder : null;
999
- if (maxPartnerCommissionPerOrder != null) totalPartnerCommission = Math.min(totalPartnerCommission, maxPartnerCommissionPerOrder);
1000
- if (maxCustomerDiscountPerOrder != null) totalCustomerDiscount = Math.min(totalCustomerDiscount, maxCustomerDiscountPerOrder);
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: totalPartnerCommission,
1003
- customerDiscount: totalCustomerDiscount
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;