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