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