@wtree/payload-ecommerce-coupon 3.78.8 → 3.78.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"createReferralProgramsCollection.d.ts","sourceRoot":"","sources":["../../src/collections/createReferralProgramsCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAEzD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAsB5D;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,gCAAgC,GAC3C,cAAc,4BAA4B,KACzC,gBAkcF,CAAA"}
1
+ {"version":3,"file":"createReferralProgramsCollection.d.ts","sourceRoot":"","sources":["../../src/collections/createReferralProgramsCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAEzD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAsB5D;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,gCAAgC,GAC3C,cAAc,4BAA4B,KACzC,gBA2aF,CAAA"}
package/dist/index.js CHANGED
@@ -460,102 +460,88 @@ const createReferralProgramsCollection = (pluginConfig) => {
460
460
  update: access.isAdmin || (() => false),
461
461
  delete: access.isAdmin || (() => false)
462
462
  },
463
- hooks: {
464
- beforeChange: [({ data }) => {
465
- if (!data.commissionRules || !Array.isArray(data.commissionRules) || data.commissionRules.length === 0) throw new payload.APIError("At least one commission rule is required", 400);
466
- const rawMaxPartner = toNumber(data.maxPartnerCommissionPerOrder);
467
- if (rawMaxPartner != null && rawMaxPartner < 0) throw new payload.APIError("Maximum commission per order for partner must be a non-negative number", 400);
468
- const rawMaxCustomer = toNumber(data.maxCustomerDiscountPerOrder);
469
- if (rawMaxCustomer != null && rawMaxCustomer < 0) throw new payload.APIError("Maximum discount for customer per order must be a non-negative number", 400);
470
- const rawMinOrder = toNumber(data.minOrderAmount);
471
- if (rawMinOrder != null && rawMinOrder < 0) throw new payload.APIError("Minimum Order Amount must be a non-negative number", 400);
472
- data.maxPartnerCommissionPerOrder = rawMaxPartner != null ? Math.round(rawMaxPartner * 100) : null;
473
- data.maxCustomerDiscountPerOrder = rawMaxCustomer != null ? Math.round(rawMaxCustomer * 100) : null;
474
- data.minOrderAmount = rawMinOrder != null ? Math.round(rawMinOrder * 100) : null;
475
- data.commissionRules = data.commissionRules.map((rule, index) => {
476
- const r = rule;
477
- if (!r.totalCommission) throw new payload.APIError(`Commission rule ${index + 1}: Total Commission is required`, 400);
478
- if (!r.totalCommission.type || !allowedTotalCommissionTypes.includes(r.totalCommission.type)) throw new payload.APIError(`Commission rule ${index + 1}: Total Commission type must be one of ${allowedTotalCommissionTypes.join(", ")}`, 400);
479
- const type = r.totalCommission.type;
480
- const appliesTo = r.appliesTo ?? "all";
481
- if (appliesTo === "products" && (!r.products || r.products.length === 0)) throw new payload.APIError(`Commission rule ${index + 1}: At least one product is required`, 400);
482
- if ((appliesTo === "segments" || appliesTo === "categories") && (!r.categories || r.categories.length === 0) && (!r.tags || r.tags.length === 0)) throw new payload.APIError(`Commission rule ${index + 1}: At least one category or tag is required`, 400);
483
- let partnerSplit;
484
- let customerSplit;
485
- let partnerPercent = null;
486
- let customerPercent = null;
487
- let partnerAmount = null;
488
- let customerAmount = null;
489
- let splitWarning = null;
490
- if (type === "percentage") {
491
- const partnerPctInput = toNumber(r.partnerPercent) ?? toNumber(r.partnerSplit);
492
- const customerPctInput = toNumber(r.customerPercent) ?? toNumber(r.customerSplit);
493
- if (partnerPctInput == null || partnerPctInput < 0 || partnerPctInput > 100) throw new payload.APIError(`Commission rule ${index + 1}: Partner Split must be between 0 and 100`, 400);
494
- if (customerPctInput != null && (customerPctInput < 0 || customerPctInput > 100)) throw new payload.APIError(`Commission rule ${index + 1}: Customer percentage must be between 0 and 100`, 400);
495
- const customerPctComputed = customerPctInput != null ? customerPctInput : 100 - partnerPctInput;
496
- const percentTotal = partnerPctInput + customerPctComputed;
497
- if (percentTotal > 100) throw new payload.APIError(`Commission rule ${index + 1}: Partner percentage + Customer percentage cannot exceed 100`, 400);
498
- if (percentTotal > 50) splitWarning = `High total split configured: ${percentTotal}% (partner + customer).`;
499
- partnerPercent = partnerPctInput;
500
- customerPercent = customerPctComputed;
501
- partnerSplit = partnerPctInput;
502
- customerSplit = customerPctComputed;
503
- } else {
504
- const partnerAmountInput = toNumber(r.partnerAmount);
505
- const customerAmountInput = toNumber(r.customerAmount);
506
- const legacyPartnerSplitInput = toNumber(r.partnerSplit);
507
- const legacyCustomerSplitInput = toNumber(r.customerSplit);
508
- const hasNewFixedInputs = partnerAmountInput != null || customerAmountInput != null;
509
- const hasLegacyFixedInputs = legacyPartnerSplitInput != null || legacyCustomerSplitInput != null;
510
- if (hasNewFixedInputs) {
511
- if (partnerAmountInput == null || partnerAmountInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: Partner fixed amount must be a non-negative number`, 400);
512
- if (customerAmountInput == null || customerAmountInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: Customer fixed amount must be a non-negative number`, 400);
513
- partnerAmount = partnerAmountInput;
514
- customerAmount = customerAmountInput;
515
- partnerSplit = partnerAmountInput;
516
- customerSplit = customerAmountInput;
517
- } else if (hasLegacyFixedInputs) {
518
- if (legacyPartnerSplitInput == null || legacyPartnerSplitInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be non-negative numbers`, 400);
519
- const resolvedLegacyCustomerSplit = legacyCustomerSplitInput ?? 100 - legacyPartnerSplitInput;
520
- if (resolvedLegacyCustomerSplit == null || resolvedLegacyCustomerSplit < 0) throw new payload.APIError(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be non-negative numbers`, 400);
521
- partnerSplit = legacyPartnerSplitInput;
522
- customerSplit = resolvedLegacyCustomerSplit;
523
- partnerAmount = null;
524
- customerAmount = null;
525
- } else throw new payload.APIError(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be provided`, 400);
526
- }
527
- return {
528
- ...rule,
529
- appliesTo: appliesTo === "categories" ? "segments" : appliesTo,
530
- totalCommission: {
531
- type,
532
- ...typeof r.totalCommission.value === "number" ? { value: r.totalCommission.value } : {},
533
- ...typeof r.totalCommission.maxAmount === "number" ? { maxAmount: r.totalCommission.maxAmount } : {}
534
- },
535
- partnerPercent,
536
- customerPercent,
537
- partnerAmount,
538
- customerAmount,
539
- partnerSplit,
540
- customerSplit,
541
- splitWarning
542
- };
543
- });
544
- return data;
545
- }],
546
- afterRead: [({ doc }) => {
547
- if (!doc) return doc;
548
- const unscale = (value) => {
549
- const n = toNumber(value);
550
- if (n == null) return null;
551
- return Math.round(n / 100 * 100) / 100;
463
+ hooks: { beforeChange: [({ data }) => {
464
+ if (!data.commissionRules || !Array.isArray(data.commissionRules) || data.commissionRules.length === 0) throw new payload.APIError("At least one commission rule is required", 400);
465
+ const rawMaxPartner = toNumber(data.maxPartnerCommissionPerOrder);
466
+ if (rawMaxPartner != null && rawMaxPartner < 0) throw new payload.APIError("Maximum commission per order for partner must be a non-negative number", 400);
467
+ const rawMaxCustomer = toNumber(data.maxCustomerDiscountPerOrder);
468
+ if (rawMaxCustomer != null && rawMaxCustomer < 0) throw new payload.APIError("Maximum discount for customer per order must be a non-negative number", 400);
469
+ const rawMinOrder = toNumber(data.minOrderAmount);
470
+ if (rawMinOrder != null && rawMinOrder < 0) throw new payload.APIError("Minimum Order Amount must be a non-negative number", 400);
471
+ data.maxPartnerCommissionPerOrder = rawMaxPartner ?? null;
472
+ data.maxCustomerDiscountPerOrder = rawMaxCustomer ?? null;
473
+ data.minOrderAmount = rawMinOrder ?? null;
474
+ data.commissionRules = data.commissionRules.map((rule, index) => {
475
+ const r = rule;
476
+ if (!r.totalCommission) throw new payload.APIError(`Commission rule ${index + 1}: Total Commission is required`, 400);
477
+ if (!r.totalCommission.type || !allowedTotalCommissionTypes.includes(r.totalCommission.type)) throw new payload.APIError(`Commission rule ${index + 1}: Total Commission type must be one of ${allowedTotalCommissionTypes.join(", ")}`, 400);
478
+ const type = r.totalCommission.type;
479
+ const appliesTo = r.appliesTo ?? "all";
480
+ if (appliesTo === "products" && (!r.products || r.products.length === 0)) throw new payload.APIError(`Commission rule ${index + 1}: At least one product is required`, 400);
481
+ if ((appliesTo === "segments" || appliesTo === "categories") && (!r.categories || r.categories.length === 0) && (!r.tags || r.tags.length === 0)) throw new payload.APIError(`Commission rule ${index + 1}: At least one category or tag is required`, 400);
482
+ let partnerSplit;
483
+ let customerSplit;
484
+ let partnerPercent = null;
485
+ let customerPercent = null;
486
+ let partnerAmount = null;
487
+ let customerAmount = null;
488
+ let splitWarning = null;
489
+ if (type === "percentage") {
490
+ const partnerPctInput = toNumber(r.partnerPercent) ?? toNumber(r.partnerSplit);
491
+ const customerPctInput = toNumber(r.customerPercent) ?? toNumber(r.customerSplit);
492
+ if (partnerPctInput == null || partnerPctInput < 0 || partnerPctInput > 100) throw new payload.APIError(`Commission rule ${index + 1}: Partner Split must be between 0 and 100`, 400);
493
+ if (customerPctInput != null && (customerPctInput < 0 || customerPctInput > 100)) throw new payload.APIError(`Commission rule ${index + 1}: Customer percentage must be between 0 and 100`, 400);
494
+ const customerPctComputed = customerPctInput != null ? customerPctInput : 100 - partnerPctInput;
495
+ const percentTotal = partnerPctInput + customerPctComputed;
496
+ if (percentTotal > 100) throw new payload.APIError(`Commission rule ${index + 1}: Partner percentage + Customer percentage cannot exceed 100`, 400);
497
+ if (percentTotal > 50) splitWarning = `High total split configured: ${percentTotal}% (partner + customer).`;
498
+ partnerPercent = partnerPctInput;
499
+ customerPercent = customerPctComputed;
500
+ partnerSplit = partnerPctInput;
501
+ customerSplit = customerPctComputed;
502
+ } else {
503
+ const partnerAmountInput = toNumber(r.partnerAmount);
504
+ const customerAmountInput = toNumber(r.customerAmount);
505
+ const legacyPartnerSplitInput = toNumber(r.partnerSplit);
506
+ const legacyCustomerSplitInput = toNumber(r.customerSplit);
507
+ const hasNewFixedInputs = partnerAmountInput != null || customerAmountInput != null;
508
+ const hasLegacyFixedInputs = legacyPartnerSplitInput != null || legacyCustomerSplitInput != null;
509
+ if (hasNewFixedInputs) {
510
+ if (partnerAmountInput == null || partnerAmountInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: Partner fixed amount must be a non-negative number`, 400);
511
+ if (customerAmountInput == null || customerAmountInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: Customer fixed amount must be a non-negative number`, 400);
512
+ partnerAmount = partnerAmountInput;
513
+ customerAmount = customerAmountInput;
514
+ partnerSplit = partnerAmountInput;
515
+ customerSplit = customerAmountInput;
516
+ } else if (hasLegacyFixedInputs) {
517
+ if (legacyPartnerSplitInput == null || legacyPartnerSplitInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be non-negative numbers`, 400);
518
+ const resolvedLegacyCustomerSplit = legacyCustomerSplitInput ?? 100 - legacyPartnerSplitInput;
519
+ if (resolvedLegacyCustomerSplit == null || resolvedLegacyCustomerSplit < 0) throw new payload.APIError(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be non-negative numbers`, 400);
520
+ partnerSplit = legacyPartnerSplitInput;
521
+ customerSplit = resolvedLegacyCustomerSplit;
522
+ partnerAmount = null;
523
+ customerAmount = null;
524
+ } else throw new payload.APIError(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be provided`, 400);
525
+ }
526
+ return {
527
+ ...rule,
528
+ appliesTo: appliesTo === "categories" ? "segments" : appliesTo,
529
+ totalCommission: {
530
+ type,
531
+ ...typeof r.totalCommission.value === "number" ? { value: r.totalCommission.value } : {},
532
+ ...typeof r.totalCommission.maxAmount === "number" ? { maxAmount: r.totalCommission.maxAmount } : {}
533
+ },
534
+ partnerPercent,
535
+ customerPercent,
536
+ partnerAmount,
537
+ customerAmount,
538
+ partnerSplit,
539
+ customerSplit,
540
+ splitWarning
552
541
  };
553
- doc.maxPartnerCommissionPerOrder = unscale(doc.maxPartnerCommissionPerOrder);
554
- doc.maxCustomerDiscountPerOrder = unscale(doc.maxCustomerDiscountPerOrder);
555
- doc.minOrderAmount = unscale(doc.minOrderAmount);
556
- return doc;
557
- }]
558
- },
542
+ });
543
+ return data;
544
+ }] },
559
545
  fields: [
560
546
  {
561
547
  name: "name",
@@ -721,14 +707,6 @@ const createReferralProgramsCollection = (pluginConfig) => {
721
707
  timestamps: true
722
708
  };
723
709
  };
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
710
  function normalizeCurrencyCode(currencyCode) {
733
711
  if (!currencyCode) return "AED";
734
712
  return currencyCode.toUpperCase();
@@ -764,16 +742,35 @@ function getCartItemUnitPrice({ item, product, variant, currencyCode, defaultCur
764
742
  }
765
743
  //#endregion
766
744
  //#region src/utilities/calculateValues.ts
745
+ /** Convert a normal-currency amount to integer cents. */
746
+ function toCents(amount) {
747
+ return Math.round(amount * 100);
748
+ }
749
+ /** Convert integer cents back to a normal-currency amount (2 dp max). */
750
+ function fromCents(cents) {
751
+ return Math.round(cents) / 100;
752
+ }
753
+ /**
754
+ * Calculate the discount amount for a coupon.
755
+ *
756
+ * @param coupon - Coupon document from DB (values in normal currency).
757
+ * @param cartTotal - Cart subtotal in normal currency.
758
+ * @returns Discount amount in normal currency (2 dp).
759
+ */
767
760
  function calculateCouponDiscount({ coupon, cartTotal }) {
768
- let discount = 0;
761
+ const cartCents = toCents(cartTotal);
762
+ let discountCents = 0;
769
763
  if (coupon.type === "percentage") {
770
- discount = roundTo2(cartTotal * coupon.value / 100);
771
- if (coupon.maxDiscountAmount != null && discount > coupon.maxDiscountAmount) discount = roundTo2(coupon.maxDiscountAmount);
764
+ discountCents = Math.floor(cartCents * coupon.value / 100);
765
+ if (coupon.maxDiscountAmount != null) {
766
+ const maxCents = toCents(coupon.maxDiscountAmount);
767
+ if (discountCents > maxCents) discountCents = maxCents;
768
+ }
772
769
  } else if (coupon.type === "fixed") {
773
- discount = roundTo2(coupon.value);
774
- if (discount > cartTotal) discount = roundTo2(cartTotal);
770
+ discountCents = toCents(coupon.value);
771
+ if (discountCents > cartCents) discountCents = cartCents;
775
772
  }
776
- return roundTo2(discount);
773
+ return fromCents(discountCents);
777
774
  }
778
775
  function relationId$5(value) {
779
776
  if (value == null) return null;
@@ -794,19 +791,25 @@ function getRuleSplits(rule) {
794
791
  customerSplit: typeof rule.customerSplit === "number" ? rule.customerSplit : typeof rule.refereeSplit === "number" ? rule.refereeSplit : 100 - partnerRaw
795
792
  };
796
793
  }
797
- function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalCommissionTypes }) {
794
+ /**
795
+ * Calculate partner and customer reward for a single line item.
796
+ *
797
+ * ALL inputs are expected in CENTS.
798
+ * Returns rewards in CENTS, or null if the rule is inapplicable.
799
+ */
800
+ function calculateItemRewardByRule({ rule, itemTotalCents, quantity, allowedTotalCommissionTypes }) {
798
801
  const allowedTypes = allowedCommissionTypesSet(allowedTotalCommissionTypes);
799
802
  if (rule.totalCommission) {
800
803
  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;
804
+ const resolvedMaxAmountCents = typeof rule.totalCommission.maxAmount === "number" && Number.isFinite(rule.totalCommission.maxAmount) ? toCents(rule.totalCommission.maxAmount) : null;
802
805
  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;
806
+ const partnerAmtPerUnitCents = typeof rule.partnerSplit === "number" ? toCents(rule.partnerSplit) : null;
807
+ const customerAmtPerUnitCents = typeof rule.customerSplit === "number" ? toCents(rule.customerSplit) : null;
808
+ if (partnerAmtPerUnitCents == null || customerAmtPerUnitCents == null) return null;
809
+ let partner = partnerAmtPerUnitCents * quantity;
810
+ let customer = customerAmtPerUnitCents * quantity;
811
+ if (resolvedMaxAmountCents != null) {
812
+ const maxPotForLine = resolvedMaxAmountCents * quantity;
810
813
  const totalPot = partner + customer;
811
814
  if (totalPot > maxPotForLine && totalPot > 0) {
812
815
  const ratio = maxPotForLine / totalPot;
@@ -819,7 +822,6 @@ function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalComm
819
822
  customer
820
823
  };
821
824
  }
822
- let totalPot = 0;
823
825
  if (rule.totalCommission.type === "percentage") {
824
826
  const commissionValue = typeof rule.totalCommission.value === "number" && Number.isFinite(rule.totalCommission.value) ? rule.totalCommission.value : null;
825
827
  if (commissionValue == null) {
@@ -827,58 +829,63 @@ function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalComm
827
829
  if (partnerPercentInput == null || partnerPercentInput < 0 || partnerPercentInput > 100) return null;
828
830
  const customerPercentInput = typeof rule.customerPercent === "number" ? rule.customerPercent : typeof rule.customerSplit === "number" ? rule.customerSplit : 100 - partnerPercentInput;
829
831
  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;
832
+ let partner = Math.floor(itemTotalCents * partnerPercentInput / 100);
833
+ let customer = Math.floor(itemTotalCents * customerPercentInput / 100);
834
+ if (resolvedMaxAmountCents != null) {
835
+ const maxPotForLine = resolvedMaxAmountCents * quantity;
834
836
  const totalForLine = partner + customer;
835
837
  if (totalForLine > maxPotForLine && totalForLine > 0) {
836
838
  const ratio = maxPotForLine / totalForLine;
837
- return {
838
- partner: Math.floor(partner * ratio),
839
- customer: Math.floor(customer * ratio)
840
- };
839
+ partner = Math.floor(partner * ratio);
840
+ customer = Math.floor(customer * ratio);
841
841
  }
842
842
  }
843
843
  return {
844
- partner: Math.floor(partner),
845
- customer: Math.floor(customer)
844
+ partner,
845
+ customer
846
846
  };
847
847
  }
848
- totalPot = itemTotal * commissionValue / 100;
849
- } else {
848
+ let totalPotCents = Math.floor(itemTotalCents * commissionValue / 100);
849
+ if (resolvedMaxAmountCents != null) {
850
+ const maxPotForLine = resolvedMaxAmountCents * quantity;
851
+ if (totalPotCents > maxPotForLine) totalPotCents = maxPotForLine;
852
+ }
850
853
  const splits = getRuleSplits(rule);
851
854
  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
855
  return {
858
- partner: Math.floor(totalPot * splits.partnerSplit / 100),
859
- customer: Math.floor(totalPot * splits.customerSplit / 100)
856
+ partner: Math.floor(totalPotCents * splits.partnerSplit / 100),
857
+ customer: Math.floor(totalPotCents * splits.customerSplit / 100)
860
858
  };
861
859
  }
862
- if (resolvedMaxAmount != null) {
863
- const maxPotForLine = resolvedMaxAmount * quantity;
864
- if (totalPot > maxPotForLine) totalPot = maxPotForLine;
860
+ {
861
+ const splits = getRuleSplits(rule);
862
+ if (!splits) return null;
863
+ let totalPotCents = toCents(rule.totalCommission.value) * quantity;
864
+ if (resolvedMaxAmountCents != null) {
865
+ const maxPotForLine = resolvedMaxAmountCents * quantity;
866
+ if (totalPotCents > maxPotForLine) totalPotCents = maxPotForLine;
867
+ }
868
+ return {
869
+ partner: Math.floor(totalPotCents * splits.partnerSplit / 100),
870
+ customer: Math.floor(totalPotCents * splits.customerSplit / 100)
871
+ };
865
872
  }
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
873
  }
873
874
  if (rule.referrerReward && rule.refereeReward) {
874
875
  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;
876
+ if (rule.referrerReward.type === "percentage") partner = Math.floor(itemTotalCents * rule.referrerReward.value / 100);
877
+ else partner = toCents(rule.referrerReward.value) * quantity;
878
+ if (rule.referrerReward.maxReward != null) {
879
+ const maxCents = toCents(rule.referrerReward.maxReward);
880
+ if (partner > maxCents) partner = maxCents;
881
+ }
878
882
  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;
883
+ if (rule.refereeReward.type === "percentage") customer = Math.floor(itemTotalCents * rule.refereeReward.value / 100);
884
+ else customer = toCents(rule.refereeReward.value) * quantity;
885
+ if (rule.refereeReward.maxReward != null) {
886
+ const maxCents = toCents(rule.refereeReward.maxReward);
887
+ if (customer > maxCents) customer = maxCents;
888
+ }
882
889
  return {
883
890
  partner,
884
891
  customer
@@ -894,12 +901,12 @@ function getItemCategoryIds(item) {
894
901
  function getItemTagIds(item) {
895
902
  return Array.isArray(item?.product?.tags) ? normalizeIds(item.product.tags) : [];
896
903
  }
897
- function selectBestRuleForItem({ rules, item, itemTotal, quantity, cartTotal, minOrderAmount, allowedTotalCommissionTypes }) {
904
+ function selectBestRuleForItem({ rules, item, itemTotalCents, quantity, cartTotalCents, minOrderAmountCents, allowedTotalCommissionTypes }) {
898
905
  const allowedTypes = allowedCommissionTypesSet(allowedTotalCommissionTypes);
899
906
  const eligibleRules = rules.filter((rule) => {
900
907
  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;
908
+ const resolvedMinCents = minOrderAmountCents != null && Number.isFinite(minOrderAmountCents) ? minOrderAmountCents : typeof rule?.minOrderAmount === "number" && Number.isFinite(rule.minOrderAmount) ? toCents(rule.minOrderAmount) : null;
909
+ if (resolvedMinCents != null) return cartTotalCents >= resolvedMinCents;
903
910
  return true;
904
911
  });
905
912
  const productId = relationId$5(item.product);
@@ -922,7 +929,7 @@ function selectBestRuleForItem({ rules, item, itemTotal, quantity, cartTotal, mi
922
929
  for (const rule of candidates) {
923
930
  const reward = calculateItemRewardByRule({
924
931
  rule,
925
- itemTotal,
932
+ itemTotalCents,
926
933
  quantity,
927
934
  allowedTotalCommissionTypes
928
935
  });
@@ -948,6 +955,10 @@ function selectBestRuleForItem({ rules, item, itemTotal, quantity, cartTotal, mi
948
955
  }
949
956
  return best;
950
957
  }
958
+ /**
959
+ * Returns the effective minimum order amount for a program in NORMAL CURRENCY.
960
+ * Returns null if there is no minimum.
961
+ */
951
962
  function getProgramMinimumOrderAmount({ program, allowedTotalCommissionTypes }) {
952
963
  if (typeof program?.minOrderAmount === "number" && Number.isFinite(program.minOrderAmount)) return program.minOrderAmount;
953
964
  const rules = Array.isArray(program?.commissionRules) ? program.commissionRules : [];
@@ -960,50 +971,66 @@ function getProgramMinimumOrderAmount({ program, allowedTotalCommissionTypes })
960
971
  if (!minValues.length) return null;
961
972
  return Math.min(...minValues);
962
973
  }
974
+ /**
975
+ * Calculate total partner commission and customer discount for a cart.
976
+ *
977
+ * All monetary inputs are in NORMAL CURRENCY.
978
+ * Returns results in NORMAL CURRENCY (2 dp).
979
+ */
963
980
  function calculateCommissionAndDiscount({ cartItems, program, currencyCode = "AED", cartTotal = 0, allowedTotalCommissionTypes }) {
964
981
  const rules = Array.isArray(program?.commissionRules) ? program.commissionRules : [];
965
982
  if (!rules.length) return {
966
983
  partnerCommission: 0,
967
984
  customerDiscount: 0
968
985
  };
969
- let totalPartnerCommission = 0;
970
- let totalCustomerDiscount = 0;
986
+ const cartTotalCents = toCents(cartTotal);
987
+ const programMinOrderAmountCents = typeof program?.minOrderAmount === "number" && Number.isFinite(program.minOrderAmount) ? toCents(program.minOrderAmount) : null;
988
+ let totalPartnerCents = 0;
989
+ let totalCustomerCents = 0;
971
990
  for (const item of cartItems) {
972
991
  const product = typeof item.product === "object" ? item.product : {};
973
- const itemPrice = getCartItemUnitPrice({
992
+ const itemPriceCurrency = getCartItemUnitPrice({
974
993
  item,
975
994
  product,
976
995
  variant: typeof item.variant === "object" ? item.variant : {},
977
996
  currencyCode
978
997
  });
979
998
  const quantity = item.quantity ?? 1;
980
- const itemTotal = itemPrice * quantity;
999
+ const itemTotalCents = toCents(itemPriceCurrency) * quantity;
981
1000
  const bestMatch = selectBestRuleForItem({
982
1001
  rules,
983
1002
  item: {
984
1003
  ...item,
985
1004
  product
986
1005
  },
987
- itemTotal,
1006
+ itemTotalCents,
988
1007
  quantity,
989
- cartTotal,
990
- minOrderAmount: typeof program?.minOrderAmount === "number" && Number.isFinite(program.minOrderAmount) ? program.minOrderAmount : null,
1008
+ cartTotalCents,
1009
+ minOrderAmountCents: programMinOrderAmountCents,
991
1010
  allowedTotalCommissionTypes
992
1011
  });
993
1012
  if (!bestMatch) continue;
994
- totalPartnerCommission += bestMatch.reward.partner;
995
- totalCustomerDiscount += bestMatch.reward.customer;
1013
+ totalPartnerCents += bestMatch.reward.partner;
1014
+ totalCustomerCents += bestMatch.reward.customer;
996
1015
  }
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);
1016
+ const maxPartnerCents = typeof program?.maxPartnerCommissionPerOrder === "number" && Number.isFinite(program.maxPartnerCommissionPerOrder) ? toCents(program.maxPartnerCommissionPerOrder) : null;
1017
+ const maxCustomerCents = typeof program?.maxCustomerDiscountPerOrder === "number" && Number.isFinite(program.maxCustomerDiscountPerOrder) ? toCents(program.maxCustomerDiscountPerOrder) : null;
1018
+ if (maxPartnerCents != null) totalPartnerCents = Math.min(totalPartnerCents, maxPartnerCents);
1019
+ if (maxCustomerCents != null) totalCustomerCents = Math.min(totalCustomerCents, maxCustomerCents);
1001
1020
  return {
1002
- partnerCommission: totalPartnerCommission,
1003
- customerDiscount: totalCustomerDiscount
1021
+ partnerCommission: fromCents(totalPartnerCents),
1022
+ customerDiscount: fromCents(totalCustomerCents)
1004
1023
  };
1005
1024
  }
1006
1025
  //#endregion
1026
+ //#region src/utilities/roundTo2.ts
1027
+ /**
1028
+ * Rounds a number to 2 decimal places (standard for monetary values).
1029
+ */
1030
+ function roundTo2(value) {
1031
+ return Math.round(value * 100) / 100;
1032
+ }
1033
+ //#endregion
1007
1034
  //#region src/endpoints/applyCoupon.ts
1008
1035
  function relationId$4(value) {
1009
1036
  if (value == null) return null;