@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
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
510
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
-
|
|
761
|
+
const cartCents = toCents(cartTotal);
|
|
762
|
+
let discountCents = 0;
|
|
769
763
|
if (coupon.type === "percentage") {
|
|
770
|
-
|
|
771
|
-
if (coupon.maxDiscountAmount != null
|
|
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
|
-
|
|
774
|
-
if (
|
|
770
|
+
discountCents = toCents(coupon.value);
|
|
771
|
+
if (discountCents > cartCents) discountCents = cartCents;
|
|
775
772
|
}
|
|
776
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
|
804
|
-
const
|
|
805
|
-
if (
|
|
806
|
-
let partner =
|
|
807
|
-
let customer =
|
|
808
|
-
if (
|
|
809
|
-
const maxPotForLine =
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
if (
|
|
833
|
-
const maxPotForLine =
|
|
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
|
-
|
|
838
|
-
|
|
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
|
|
845
|
-
customer
|
|
844
|
+
partner,
|
|
845
|
+
customer
|
|
846
846
|
};
|
|
847
847
|
}
|
|
848
|
-
|
|
849
|
-
|
|
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(
|
|
859
|
-
customer: Math.floor(
|
|
856
|
+
partner: Math.floor(totalPotCents * splits.partnerSplit / 100),
|
|
857
|
+
customer: Math.floor(totalPotCents * splits.customerSplit / 100)
|
|
860
858
|
};
|
|
861
859
|
}
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
if (
|
|
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 =
|
|
876
|
-
else partner = rule.referrerReward.value * quantity;
|
|
877
|
-
if (rule.referrerReward.maxReward != null
|
|
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 =
|
|
880
|
-
else customer = rule.refereeReward.value * quantity;
|
|
881
|
-
if (rule.refereeReward.maxReward != null
|
|
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,
|
|
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
|
|
902
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
970
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1006
|
+
itemTotalCents,
|
|
988
1007
|
quantity,
|
|
989
|
-
|
|
990
|
-
|
|
1008
|
+
cartTotalCents,
|
|
1009
|
+
minOrderAmountCents: programMinOrderAmountCents,
|
|
991
1010
|
allowedTotalCommissionTypes
|
|
992
1011
|
});
|
|
993
1012
|
if (!bestMatch) continue;
|
|
994
|
-
|
|
995
|
-
|
|
1013
|
+
totalPartnerCents += bestMatch.reward.partner;
|
|
1014
|
+
totalCustomerCents += bestMatch.reward.customer;
|
|
996
1015
|
}
|
|
997
|
-
const
|
|
998
|
-
const
|
|
999
|
-
if (
|
|
1000
|
-
if (
|
|
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:
|
|
1003
|
-
customerDiscount:
|
|
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;
|