@wtree/payload-ecommerce-coupon 3.78.7 → 3.78.8

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,4 +1,15 @@
1
1
  import { type CollectionConfig } from 'payload';
2
2
  import type { SanitizedCouponPluginOptions } from '../types';
3
+ /**
4
+ * Scaling policy:
5
+ * - Admin inputs normal currency values (e.g. 100 means $100).
6
+ * - Internally, maxPartnerCommissionPerOrder, maxCustomerDiscountPerOrder, and
7
+ * minOrderAmount are stored in x100 (integer cents) form to avoid floating-point
8
+ * drift on monetary cap fields.
9
+ * - beforeChange → multiply by 100 before persisting.
10
+ * - afterRead → divide by 100 before returning to admin / calculation code.
11
+ * - partnerAmount / customerAmount per-rule fixed amounts are NOT scaled here;
12
+ * they represent per-item fixed currency amounts and are stored as-is.
13
+ */
3
14
  export declare const createReferralProgramsCollection: (pluginConfig: SanitizedCouponPluginOptions) => CollectionConfig;
4
15
  //# sourceMappingURL=createReferralProgramsCollection.d.ts.map
@@ -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;AA+B5D,eAAO,MAAM,gCAAgC,GAC3C,cAAc,4BAA4B,KACzC,gBAgaF,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,gBAkcF,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"applyCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/applyCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAA;AACrF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAQ5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAqGD,eAAO,MAAM,kBAAkB,GAC5B,kBAAkB,IAAI,KAAG,cAgHzB,CAAA;AAgRH,eAAO,MAAM,mBAAmB,GAAI,kBAAkB,IAAI,KAAG,QAI3D,CAAA"}
1
+ {"version":3,"file":"applyCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/applyCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAA;AACrF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAQ5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AAqGD,eAAO,MAAM,kBAAkB,GAC5B,kBAAkB,IAAI,KAAG,cAgHzB,CAAA;AAqRH,eAAO,MAAM,mBAAmB,GAAI,kBAAkB,IAAI,KAAG,QAI3D,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"validateCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/validateCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAA;AAErF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAO5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AA4FD,eAAO,MAAM,qBAAqB,GAC/B,kBAAkB,IAAI,KAAG,cAiEzB,CAAA;AA+OH,eAAO,MAAM,sBAAsB,GAAI,kBAAkB,IAAI,KAAG,QAI9D,CAAA"}
1
+ {"version":3,"file":"validateCoupon.d.ts","sourceRoot":"","sources":["../../src/endpoints/validateCoupon.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAA;AAErF,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAO5D,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,4BAA4B,CAAA;CAC3C,CAAA;AA4FD,eAAO,MAAM,qBAAqB,GAC/B,kBAAkB,IAAI,KAAG,cAiEzB,CAAA;AAqPH,eAAO,MAAM,sBAAsB,GAAI,kBAAkB,IAAI,KAAG,QAI9D,CAAA"}
package/dist/index.js CHANGED
@@ -423,23 +423,21 @@ const createReferralCodesCollection = (pluginConfig) => {
423
423
  };
424
424
  };
425
425
  //#endregion
426
- //#region src/utilities/roundTo2.ts
427
- /**
428
- * Rounds a number to 2 decimal places (standard for monetary values).
429
- */
430
- function roundTo2(value) {
431
- return Math.round(value * 100) / 100;
432
- }
433
- //#endregion
434
426
  //#region src/collections/createReferralProgramsCollection.ts
435
427
  function toNumber(value) {
436
428
  return typeof value === "number" && Number.isFinite(value) ? value : null;
437
429
  }
438
- function normalizeLegacyX100Money(value) {
439
- if (value == null) return null;
440
- if (Number.isInteger(value) && Math.abs(value) >= 1e3) return roundTo2(value / 100);
441
- return value;
442
- }
430
+ /**
431
+ * Scaling policy:
432
+ * - Admin inputs normal currency values (e.g. 100 means $100).
433
+ * - Internally, maxPartnerCommissionPerOrder, maxCustomerDiscountPerOrder, and
434
+ * minOrderAmount are stored in x100 (integer cents) form to avoid floating-point
435
+ * drift on monetary cap fields.
436
+ * - beforeChange → multiply by 100 before persisting.
437
+ * - afterRead → divide by 100 before returning to admin / calculation code.
438
+ * - partnerAmount / customerAmount per-rule fixed amounts are NOT scaled here;
439
+ * they represent per-item fixed currency amounts and are stored as-is.
440
+ */
443
441
  const createReferralProgramsCollection = (pluginConfig) => {
444
442
  const { collections, access, adminGroups, referralConfig, integration } = pluginConfig;
445
443
  const allowedTotalCommissionTypes = referralConfig.allowedTotalCommissionTypes;
@@ -462,88 +460,102 @@ const createReferralProgramsCollection = (pluginConfig) => {
462
460
  update: access.isAdmin || (() => false),
463
461
  delete: access.isAdmin || (() => false)
464
462
  },
465
- hooks: { beforeChange: [({ data }) => {
466
- if (!data.commissionRules || !Array.isArray(data.commissionRules) || data.commissionRules.length === 0) throw new payload.APIError("At least one commission rule is required", 400);
467
- const maxPartnerCommissionPerOrder = normalizeLegacyX100Money(toNumber(data.maxPartnerCommissionPerOrder));
468
- if (maxPartnerCommissionPerOrder != null && maxPartnerCommissionPerOrder < 0) throw new payload.APIError("Maximum commission per order for partner must be a non-negative number", 400);
469
- const maxCustomerDiscountPerOrder = normalizeLegacyX100Money(toNumber(data.maxCustomerDiscountPerOrder));
470
- if (maxCustomerDiscountPerOrder != null && maxCustomerDiscountPerOrder < 0) throw new payload.APIError("Maximum discount for customer per order must be a non-negative number", 400);
471
- const minOrderAmount = normalizeLegacyX100Money(toNumber(data.minOrderAmount));
472
- if (minOrderAmount != null && minOrderAmount < 0) throw new payload.APIError("Minimum Order Amount must be a non-negative number", 400);
473
- data.maxPartnerCommissionPerOrder = maxPartnerCommissionPerOrder != null ? maxPartnerCommissionPerOrder : null;
474
- data.maxCustomerDiscountPerOrder = maxCustomerDiscountPerOrder != null ? maxCustomerDiscountPerOrder : null;
475
- data.minOrderAmount = minOrderAmount ?? null;
476
- data.commissionRules = data.commissionRules.map((rule, index) => {
477
- const r = rule;
478
- if (!r.totalCommission) throw new payload.APIError(`Commission rule ${index + 1}: Total Commission is required`, 400);
479
- 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);
480
- const type = r.totalCommission.type;
481
- const appliesTo = r.appliesTo ?? "all";
482
- if (appliesTo === "products" && (!r.products || r.products.length === 0)) throw new payload.APIError(`Commission rule ${index + 1}: At least one product is required`, 400);
483
- 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);
484
- let partnerSplit;
485
- let customerSplit;
486
- let partnerPercent = null;
487
- let customerPercent = null;
488
- let partnerAmount = null;
489
- let customerAmount = null;
490
- let splitWarning = null;
491
- if (type === "percentage") {
492
- const partnerPctInput = toNumber(r.partnerPercent) ?? toNumber(r.partnerSplit);
493
- const customerPctInput = toNumber(r.customerPercent) ?? toNumber(r.customerSplit);
494
- if (partnerPctInput == null || partnerPctInput < 0 || partnerPctInput > 100) throw new payload.APIError(`Commission rule ${index + 1}: Partner Split must be between 0 and 100`, 400);
495
- if (customerPctInput != null && (customerPctInput < 0 || customerPctInput > 100)) throw new payload.APIError(`Commission rule ${index + 1}: Customer percentage must be between 0 and 100`, 400);
496
- const customerPctComputed = customerPctInput != null ? customerPctInput : 100 - partnerPctInput;
497
- const percentTotal = partnerPctInput + customerPctComputed;
498
- if (percentTotal > 100) throw new payload.APIError(`Commission rule ${index + 1}: Partner percentage + Customer percentage cannot exceed 100`, 400);
499
- if (percentTotal > 50) splitWarning = `High total split configured: ${percentTotal}% (partner + customer).`;
500
- partnerPercent = partnerPctInput;
501
- customerPercent = customerPctComputed;
502
- partnerSplit = partnerPctInput;
503
- customerSplit = customerPctComputed;
504
- } else {
505
- const partnerAmountInput = normalizeLegacyX100Money(toNumber(r.partnerAmount));
506
- const customerAmountInput = normalizeLegacyX100Money(toNumber(r.customerAmount));
507
- const legacyPartnerSplitInput = toNumber(r.partnerSplit);
508
- const legacyCustomerSplitInput = toNumber(r.customerSplit);
509
- const hasNewFixedInputs = partnerAmountInput != null || customerAmountInput != null;
510
- const hasLegacyFixedInputs = legacyPartnerSplitInput != null || legacyCustomerSplitInput != null;
511
- if (hasNewFixedInputs) {
512
- if (partnerAmountInput == null || partnerAmountInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: Partner fixed amount must be a non-negative number`, 400);
513
- if (customerAmountInput == null || customerAmountInput < 0) throw new payload.APIError(`Commission rule ${index + 1}: Customer fixed amount must be a non-negative number`, 400);
514
- partnerAmount = partnerAmountInput;
515
- customerAmount = customerAmountInput;
516
- partnerSplit = partnerAmountInput;
517
- customerSplit = customerAmountInput;
518
- } else if (hasLegacyFixedInputs) {
519
- 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);
520
- const resolvedLegacyCustomerSplit = legacyCustomerSplitInput ?? 100 - legacyPartnerSplitInput;
521
- 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);
522
- partnerSplit = legacyPartnerSplitInput;
523
- customerSplit = resolvedLegacyCustomerSplit;
524
- partnerAmount = null;
525
- customerAmount = null;
526
- } else throw new payload.APIError(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be provided`, 400);
527
- }
528
- return {
529
- ...rule,
530
- appliesTo: appliesTo === "categories" ? "segments" : appliesTo,
531
- totalCommission: {
532
- type,
533
- ...typeof r.totalCommission.value === "number" ? { value: r.totalCommission.value } : {},
534
- ...typeof r.totalCommission.maxAmount === "number" ? { maxAmount: r.totalCommission.maxAmount } : {}
535
- },
536
- partnerPercent,
537
- customerPercent,
538
- partnerAmount,
539
- customerAmount,
540
- partnerSplit,
541
- customerSplit,
542
- splitWarning
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;
543
552
  };
544
- });
545
- return data;
546
- }] },
553
+ doc.maxPartnerCommissionPerOrder = unscale(doc.maxPartnerCommissionPerOrder);
554
+ doc.maxCustomerDiscountPerOrder = unscale(doc.maxCustomerDiscountPerOrder);
555
+ doc.minOrderAmount = unscale(doc.minOrderAmount);
556
+ return doc;
557
+ }]
558
+ },
547
559
  fields: [
548
560
  {
549
561
  name: "name",
@@ -561,19 +573,19 @@ const createReferralProgramsCollection = (pluginConfig) => {
561
573
  name: "maxPartnerCommissionPerOrder",
562
574
  type: "number",
563
575
  min: 0,
564
- admin: { description: "Maximum commission per order for partner (normal currency value, ). Leave empty for no cap." }
576
+ admin: { description: "Maximum commission per order for partner (enter normal currency value, e.g. 50 for $50). Leave empty for no cap." }
565
577
  },
566
578
  {
567
579
  name: "maxCustomerDiscountPerOrder",
568
580
  type: "number",
569
581
  min: 0,
570
- admin: { description: "Maximum customer discount per order (normal currency value, ). Leave empty for no cap." }
582
+ admin: { description: "Maximum customer discount per order (enter normal currency value, e.g. 25 for $25). Leave empty for no cap." }
571
583
  },
572
584
  {
573
585
  name: "minOrderAmount",
574
586
  type: "number",
575
587
  min: 0,
576
- admin: { description: "Minimum cart subtotal required for this program (normal currency value, ). Leave empty for no minimum." }
588
+ admin: { description: "Minimum cart subtotal required for this program (enter normal currency value, e.g. 100 for $100). Leave empty for no minimum." }
577
589
  },
578
590
  {
579
591
  name: "commissionRules",
@@ -654,7 +666,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
654
666
  max: 100,
655
667
  admin: {
656
668
  condition: (_, siblingData) => siblingData?.totalCommission?.type === "percentage",
657
- description: "Partner share in percent (0-100)"
669
+ description: "Partner share in percent (0100)"
658
670
  }
659
671
  },
660
672
  {
@@ -664,7 +676,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
664
676
  max: 100,
665
677
  admin: {
666
678
  condition: (_, siblingData) => siblingData?.totalCommission?.type === "percentage",
667
- description: "Customer share percentage. (0-100). Partner + Customer cannot exceed 100."
679
+ description: "Customer share percentage (0100). Partner + Customer cannot exceed 100."
668
680
  }
669
681
  },
670
682
  {
@@ -673,7 +685,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
673
685
  min: 0,
674
686
  admin: {
675
687
  condition: (_, siblingData) => siblingData?.totalCommission?.type === "fixed",
676
- description: "Fixed partner commission amount per item (normal currency value)."
688
+ description: "Fixed partner commission amount per item (normal currency value, e.g. 12.50 for $12.50)."
677
689
  }
678
690
  },
679
691
  {
@@ -682,7 +694,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
682
694
  min: 0,
683
695
  admin: {
684
696
  condition: (_, siblingData) => siblingData?.totalCommission?.type === "fixed",
685
- description: "Fixed customer discount amount per item (normal currency value)."
697
+ description: "Fixed customer discount amount per item (normal currency value, e.g. 5 for $5)."
686
698
  }
687
699
  },
688
700
  {
@@ -691,7 +703,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
691
703
  min: 0,
692
704
  admin: {
693
705
  hidden: true,
694
- description: "Canonical storage field. Percentage mode: percent. Fixed mode: amount."
706
+ description: "Canonical storage field. Percentage mode: percent value. Fixed mode: per-item amount."
695
707
  }
696
708
  },
697
709
  {
@@ -700,7 +712,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
700
712
  min: 0,
701
713
  admin: {
702
714
  hidden: true,
703
- description: "Canonical storage field. Percentage mode: percent. Fixed mode: amount."
715
+ description: "Canonical storage field. Percentage mode: percent value. Fixed mode: per-item amount."
704
716
  }
705
717
  }
706
718
  ]
@@ -709,6 +721,14 @@ const createReferralProgramsCollection = (pluginConfig) => {
709
721
  timestamps: true
710
722
  };
711
723
  };
724
+ //#endregion
725
+ //#region src/utilities/roundTo2.ts
726
+ /**
727
+ * Rounds a number to 2 decimal places (standard for monetary values).
728
+ */
729
+ function roundTo2(value) {
730
+ return Math.round(value * 100) / 100;
731
+ }
712
732
  function normalizeCurrencyCode(currencyCode) {
713
733
  if (!currencyCode) return "AED";
714
734
  return currencyCode.toUpperCase();
@@ -1176,17 +1196,17 @@ async function handleCouponCode({ payload: payload$7, cart, cartID, normalizedCo
1176
1196
  }, { status: 400 });
1177
1197
  const cartSubtotal = Number(resolvers.getCartSubtotal(cart)) || 0;
1178
1198
  const cartTotal = Number(resolvers.getCartTotal(cart)) || cartSubtotal || 0;
1179
- if (coupon.minOrderValue && cartTotal < coupon.minOrderValue) return Response.json({
1199
+ if (coupon.minOrderValue && cartSubtotal < coupon.minOrderValue) return Response.json({
1180
1200
  success: false,
1181
1201
  error: `Minimum order value of ${coupon.minOrderValue} ${pluginConfig.defaultCurrency} required`
1182
1202
  }, { status: 400 });
1183
- if (coupon.maxOrderValue && cartTotal > coupon.maxOrderValue) return Response.json({
1203
+ if (coupon.maxOrderValue && cartSubtotal > coupon.maxOrderValue) return Response.json({
1184
1204
  success: false,
1185
1205
  error: `Maximum order value of ${coupon.maxOrderValue} ${pluginConfig.defaultCurrency} exceeded`
1186
1206
  }, { status: 400 });
1187
1207
  const discountAmount = calculateCouponDiscount({
1188
1208
  coupon,
1189
- cartTotal
1209
+ cartTotal: cartSubtotal
1190
1210
  });
1191
1211
  const nextTotal = roundTo2(Math.max(0, cartTotal - discountAmount));
1192
1212
  const data = {};
@@ -1248,12 +1268,13 @@ async function handleReferralCode({ payload: payload$8, cart, cartID, normalized
1248
1268
  error: "Referral code already applied to this cart"
1249
1269
  }, { status: 400 });
1250
1270
  const cartItems = resolvers.getCartItems(cart);
1251
- const cartTotal = Number(resolvers.getCartTotal(cart)) || Number(resolvers.getCartSubtotal(cart)) || 0;
1271
+ const cartSubtotal = Number(resolvers.getCartSubtotal(cart)) || 0;
1272
+ const cartTotal = Number(resolvers.getCartTotal(cart)) || cartSubtotal || 0;
1252
1273
  const minOrderAmount = getProgramMinimumOrderAmount({
1253
1274
  program,
1254
1275
  allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
1255
1276
  });
1256
- if (typeof minOrderAmount === "number" && cartTotal < minOrderAmount) return Response.json({
1277
+ if (typeof minOrderAmount === "number" && cartSubtotal < minOrderAmount) return Response.json({
1257
1278
  success: false,
1258
1279
  error: `Minimum order value of ${minOrderAmount} ${pluginConfig.defaultCurrency} required for this referral program`
1259
1280
  }, { status: 400 });
@@ -1261,7 +1282,7 @@ async function handleReferralCode({ payload: payload$8, cart, cartID, normalized
1261
1282
  cartItems,
1262
1283
  program,
1263
1284
  currencyCode: pluginConfig.defaultCurrency,
1264
- cartTotal,
1285
+ cartTotal: cartSubtotal,
1265
1286
  allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
1266
1287
  });
1267
1288
  const roundedPartnerCommission = roundTo2(partnerCommission);
@@ -1690,7 +1711,7 @@ async function validateReferralCode({ payload: payload$4, normalizedCode, cartID
1690
1711
  id: cartID,
1691
1712
  depth: 2
1692
1713
  }) : null;
1693
- const cartTotal = cart ? Number(resolvers.getCartTotal(cart)) || Number(resolvers.getCartSubtotal(cart)) || 0 : 0;
1714
+ const cartTotal = cart ? Number(resolvers.getCartSubtotal(cart)) || Number(resolvers.getCartTotal(cart)) || 0 : 0;
1694
1715
  const minOrderAmount = getProgramMinimumOrderAmount({
1695
1716
  program,
1696
1717
  allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes