@wtree/payload-ecommerce-coupon 3.77.2 → 3.77.4

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.
Files changed (53) hide show
  1. package/README.md +22 -1
  2. package/dist/client/hooks.d.ts +1 -1
  3. package/dist/client/hooks.d.ts.map +1 -1
  4. package/dist/client/index.d.ts +6 -6
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/collections/createCouponsCollection.d.ts +2 -2
  7. package/dist/collections/createCouponsCollection.d.ts.map +1 -1
  8. package/dist/collections/createReferralCodesCollection.d.ts +2 -2
  9. package/dist/collections/createReferralCodesCollection.d.ts.map +1 -1
  10. package/dist/collections/createReferralProgramsCollection.d.ts +2 -2
  11. package/dist/collections/createReferralProgramsCollection.d.ts.map +1 -1
  12. package/dist/components/PartnerDashboard/EarningsSummary.d.ts +2 -2
  13. package/dist/components/PartnerDashboard/EarningsSummary.d.ts.map +1 -1
  14. package/dist/components/PartnerDashboard/RecentReferrals.d.ts +3 -3
  15. package/dist/components/PartnerDashboard/RecentReferrals.d.ts.map +1 -1
  16. package/dist/components/PartnerDashboard/ReferralCodes.d.ts +3 -3
  17. package/dist/components/PartnerDashboard/ReferralCodes.d.ts.map +1 -1
  18. package/dist/components/PartnerDashboard/ReferralPerformance.d.ts +2 -2
  19. package/dist/components/PartnerDashboard/ReferralPerformance.d.ts.map +1 -1
  20. package/dist/components/PartnerDashboard/index.d.ts +2 -2
  21. package/dist/components/PartnerDashboard/index.d.ts.map +1 -1
  22. package/dist/endpoints/applyCoupon.d.ts +2 -2
  23. package/dist/endpoints/applyCoupon.d.ts.map +1 -1
  24. package/dist/endpoints/partnerStats.d.ts +2 -2
  25. package/dist/endpoints/partnerStats.d.ts.map +1 -1
  26. package/dist/endpoints/validateCoupon.d.ts +2 -2
  27. package/dist/endpoints/validateCoupon.d.ts.map +1 -1
  28. package/dist/hooks/recalculateCart.d.ts +2 -2
  29. package/dist/hooks/recalculateCart.d.ts.map +1 -1
  30. package/dist/index.d.ts +10 -10
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +228 -82
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +228 -83
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/plugin.d.ts +2 -2
  37. package/dist/plugin.d.ts.map +1 -1
  38. package/dist/types.d.ts +23 -3
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/utilities/calculateValues.d.ts +7 -1
  41. package/dist/utilities/calculateValues.d.ts.map +1 -1
  42. package/dist/utilities/getCartTotalWithDiscounts.d.ts.map +1 -1
  43. package/dist/utilities/migrateReferralRulesV2.d.ts.map +1 -1
  44. package/dist/utilities/pricing.d.ts.map +1 -1
  45. package/dist/utilities/pushTypeScriptProperties.d.ts +1 -1
  46. package/dist/utilities/pushTypeScriptProperties.d.ts.map +1 -1
  47. package/dist/utilities/recordCouponUsageForOrder.d.ts +2 -2
  48. package/dist/utilities/recordCouponUsageForOrder.d.ts.map +1 -1
  49. package/dist/utilities/sanitizePluginConfig.d.ts +1 -1
  50. package/dist/utilities/sanitizePluginConfig.d.ts.map +1 -1
  51. package/dist/utilities/userRoles.d.ts +25 -0
  52. package/dist/utilities/userRoles.d.ts.map +1 -0
  53. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -118,10 +118,59 @@ const createCouponsCollection = (pluginConfig) => {
118
118
  };
119
119
  };
120
120
 
121
+ //#endregion
122
+ //#region src/utilities/userRoles.ts
123
+ function readByPath(input, path) {
124
+ if (!input || typeof input !== "object" || !path) return void 0;
125
+ return path.split(".").reduce((acc, key) => {
126
+ if (!acc || typeof acc !== "object") return void 0;
127
+ return acc[key];
128
+ }, input);
129
+ }
130
+ function toRoleArray(value) {
131
+ if (typeof value === "string" && value.trim().length > 0) return [value];
132
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
133
+ return [];
134
+ }
135
+ const normalizeRoleValue = (value) => value.trim().toLowerCase();
136
+ const resolveUserRoles = ({ user, roleConfig }) => {
137
+ if (!user) return [];
138
+ if (typeof roleConfig.customRoleResolver === "function") {
139
+ const custom = roleConfig.customRoleResolver(user);
140
+ return Array.isArray(custom) ? custom.map(normalizeRoleValue).filter(Boolean) : [];
141
+ }
142
+ const roles = roleConfig.roleFieldPaths.flatMap((path) => toRoleArray(readByPath(user, path)));
143
+ return [...new Set(roles.map(normalizeRoleValue).filter(Boolean))];
144
+ };
145
+ const userHasAnyRole = ({ user, roleConfig, targetRoles }) => {
146
+ const userRoles = new Set(resolveUserRoles({
147
+ user,
148
+ roleConfig
149
+ }));
150
+ for (const target of targetRoles) if (userRoles.has(normalizeRoleValue(target))) return true;
151
+ return false;
152
+ };
153
+ const isPartnerUser = ({ user, roleConfig }) => userHasAnyRole({
154
+ user,
155
+ roleConfig,
156
+ targetRoles: roleConfig.partnerRoleValues
157
+ });
158
+ const isAdminUser = ({ user, roleConfig }) => userHasAnyRole({
159
+ user,
160
+ roleConfig,
161
+ targetRoles: roleConfig.adminRoleValues
162
+ });
163
+ const buildPartnerUserFilterWhere = ({ roleConfig }) => {
164
+ if (!roleConfig.roleFieldPaths.length || !roleConfig.partnerRoleValues.length) return true;
165
+ const conditions = roleConfig.roleFieldPaths.map((fieldPath) => ({ [fieldPath]: { in: roleConfig.partnerRoleValues } }));
166
+ if (conditions.length === 1) return conditions[0];
167
+ return { or: conditions };
168
+ };
169
+
121
170
  //#endregion
122
171
  //#region src/collections/createReferralCodesCollection.ts
123
172
  const createReferralCodesCollection = (pluginConfig) => {
124
- const { collections, access, adminGroups, defaultCurrency } = pluginConfig;
173
+ const { collections, access, adminGroups, defaultCurrency, roleConfig } = pluginConfig;
125
174
  return {
126
175
  slug: collections.referralCodesSlug,
127
176
  admin: {
@@ -139,15 +188,27 @@ const createReferralCodesCollection = (pluginConfig) => {
139
188
  read: ({ req }) => {
140
189
  const user = req?.user;
141
190
  if (!user) return false;
142
- if (user.role === "admin" || Array.isArray(user.role) && user.role.includes("admin") || Array.isArray(user.roles) && user.roles.includes("admin")) return true;
143
- if (user.role === "partner" || Array.isArray(user.role) && user.role.includes("partner") || Array.isArray(user.roles) && user.roles.includes("partner")) return { partner: { equals: user.id } };
191
+ if (isAdminUser({
192
+ user,
193
+ roleConfig
194
+ }) || access.isAdmin?.({ req })) return true;
195
+ if (isPartnerUser({
196
+ user,
197
+ roleConfig
198
+ }) || access.isPartner?.({ req })) return { partner: { equals: user.id } };
144
199
  return access.canUseReferrals ? access.canUseReferrals({ req }) : false;
145
200
  },
146
201
  create: ({ req }) => {
147
202
  const user = req?.user;
148
203
  if (!user) return false;
149
- if (user.role === "admin" || Array.isArray(user.role) && user.role.includes("admin") || Array.isArray(user.roles) && user.roles.includes("admin")) return true;
150
- if (user.role === "partner" || Array.isArray(user.role) && user.role.includes("partner") || Array.isArray(user.roles) && user.roles.includes("partner")) return true;
204
+ if (isAdminUser({
205
+ user,
206
+ roleConfig
207
+ }) || access.isAdmin?.({ req })) return true;
208
+ if (isPartnerUser({
209
+ user,
210
+ roleConfig
211
+ }) || access.isPartner?.({ req })) return true;
151
212
  return access.isAdmin ? access.isAdmin({ req }) : false;
152
213
  },
153
214
  update: access.isAdmin || (() => false),
@@ -173,11 +234,12 @@ const createReferralCodesCollection = (pluginConfig) => {
173
234
  type: "relationship",
174
235
  relationTo: "users",
175
236
  required: true,
176
- filterOptions: ({ data }) => {
177
- const user = data?.user;
178
- if (!user) return false;
179
- if (user.role === "partner" || Array.isArray(user.role) && user.role.includes("partner") || Array.isArray(user.roles) && user.roles.includes("partner")) return true;
180
- return false;
237
+ filterOptions: ({ req, user }) => {
238
+ if (isAdminUser({
239
+ user: user || req?.user,
240
+ roleConfig
241
+ }) || access.isAdmin?.({ req })) return true;
242
+ return buildPartnerUserFilterWhere({ roleConfig });
181
243
  },
182
244
  admin: { description: "The partner who owns this referral code" }
183
245
  },
@@ -255,7 +317,10 @@ const createReferralCodesCollection = (pluginConfig) => {
255
317
  if (operation === "create" && !data.code && data.partner) data.code = `REF-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 8)}`.toUpperCase();
256
318
  if (operation === "create" && req.user) {
257
319
  const user = req.user;
258
- if (user.role === "partner" || Array.isArray(user.role) && user.role.includes("partner")) data.partner = user.id;
320
+ if (isPartnerUser({
321
+ user,
322
+ roleConfig
323
+ })) data.partner = user.id;
259
324
  }
260
325
  return data;
261
326
  }] },
@@ -268,7 +333,11 @@ const createReferralCodesCollection = (pluginConfig) => {
268
333
  function toNumber(value) {
269
334
  return typeof value === "number" && Number.isFinite(value) ? value : null;
270
335
  }
271
- const deriveCustomerSplit = (partnerSplit) => {
336
+ const deriveCustomerSplit = (partnerSplit, totalCommission) => {
337
+ if (totalCommission?.type === "fixed" && totalCommission.value == null) {
338
+ const partner = toNumber(partnerSplit);
339
+ return partner != null ? partner : 0;
340
+ }
272
341
  const partner = toNumber(partnerSplit);
273
342
  if (partner == null) return 0;
274
343
  if (partner < 0) return 100;
@@ -277,6 +346,7 @@ const deriveCustomerSplit = (partnerSplit) => {
277
346
  };
278
347
  const createReferralProgramsCollection = (pluginConfig) => {
279
348
  const { collections, access, defaultCurrency, adminGroups, referralConfig } = pluginConfig;
349
+ const allowedTotalCommissionTypes = referralConfig.allowedTotalCommissionTypes;
280
350
  return {
281
351
  slug: collections.referralProgramsSlug,
282
352
  admin: {
@@ -299,18 +369,27 @@ const createReferralProgramsCollection = (pluginConfig) => {
299
369
  data.commissionRules = data.commissionRules.map((rule, index) => {
300
370
  const r = rule;
301
371
  if (!r.totalCommission) throw new Error(`Commission rule ${index + 1}: Total Commission is required`);
302
- if (!r.totalCommission.type || !["fixed", "percentage"].includes(r.totalCommission.type)) throw new Error(`Commission rule ${index + 1}: Total Commission type must be fixed or percentage`);
372
+ if (!r.totalCommission.type || !allowedTotalCommissionTypes.includes(r.totalCommission.type)) throw new Error(`Commission rule ${index + 1}: Total Commission type must be one of ${allowedTotalCommissionTypes.join(", ")}`);
303
373
  const totalValue = toNumber(r.totalCommission.value);
304
- if (totalValue == null || totalValue < 0) throw new Error(`Commission rule ${index + 1}: Total Commission value must be a non-negative number`);
305
- if (r.totalCommission.type === "percentage" && totalValue > 100) throw new Error(`Commission rule ${index + 1}: Percentage Total Commission cannot exceed 100`);
374
+ if (r.totalCommission.type === "percentage" && (totalValue == null || totalValue < 0)) throw new Error(`Commission rule ${index + 1}: Total Commission value must be a non-negative number`);
375
+ if (r.totalCommission.type === "percentage" && totalValue && totalValue > 100) throw new Error(`Commission rule ${index + 1}: Percentage Total Commission cannot exceed 100`);
306
376
  const maxAmount = toNumber(r.totalCommission.maxAmount);
307
377
  if (maxAmount != null && maxAmount < 0) throw new Error(`Commission rule ${index + 1}: Max Amount must be a non-negative number`);
308
378
  const appliesTo = r.appliesTo ?? "all";
309
379
  if (appliesTo === "products" && (!r.products || r.products.length === 0)) throw new Error(`Commission rule ${index + 1}: At least one product is required`);
310
380
  if ((appliesTo === "segments" || appliesTo === "categories") && (!r.categories || r.categories.length === 0) && (!r.tags || r.tags.length === 0)) throw new Error(`Commission rule ${index + 1}: At least one category or tag is required`);
311
381
  const partnerSplit = toNumber(r.partnerSplit);
312
- if (partnerSplit == null || partnerSplit < 0 || partnerSplit > 100) throw new Error(`Commission rule ${index + 1}: Partner Split must be between 0 and 100`);
313
- const customerSplit = 100 - partnerSplit;
382
+ if (partnerSplit == null || partnerSplit < 0) throw new Error(`Commission rule ${index + 1}: Partner Split must be a non-negative number`);
383
+ const hasFixedValue = r.totalCommission.type === "fixed" && toNumber(r.totalCommission.value) != null;
384
+ if (!hasFixedValue && r.totalCommission.type !== "fixed" && partnerSplit > 100) throw new Error(`Commission rule ${index + 1}: Partner Split must be between 0 and 100`);
385
+ if (hasFixedValue && partnerSplit > 100) throw new Error(`Commission rule ${index + 1}: Partner Split must be between 0 and 100`);
386
+ let customerSplit = null;
387
+ if (r.totalCommission.type === "fixed" && !hasFixedValue) {
388
+ customerSplit = toNumber(r.customerSplit);
389
+ if (customerSplit == null || customerSplit < 0) throw new Error(`Commission rule ${index + 1}: For fixed commissions with no value, both partnerSplit and customerSplit must be non-negative numbers`);
390
+ } else customerSplit = 100 - partnerSplit;
391
+ const minOrderAmount = toNumber(r.minOrderAmount);
392
+ if (minOrderAmount != null && minOrderAmount < 0) throw new Error(`Commission rule ${index + 1}: Minimum Order Amount must be a non-negative number`);
314
393
  return {
315
394
  ...rule,
316
395
  appliesTo: appliesTo === "categories" ? "segments" : appliesTo,
@@ -320,7 +399,8 @@ const createReferralProgramsCollection = (pluginConfig) => {
320
399
  maxAmount: maxAmount ?? null
321
400
  },
322
401
  partnerSplit,
323
- customerSplit
402
+ customerSplit,
403
+ minOrderAmount: minOrderAmount ?? null
324
404
  };
325
405
  });
326
406
  return data;
@@ -410,21 +490,20 @@ const createReferralProgramsCollection = (pluginConfig) => {
410
490
  name: "type",
411
491
  type: "select",
412
492
  required: true,
413
- options: [{
414
- label: "Fixed Amount",
415
- value: "fixed"
416
- }, {
417
- label: "Percentage of Order",
418
- value: "percentage"
419
- }],
420
- defaultValue: "percentage"
493
+ options: allowedTotalCommissionTypes.map((value) => ({
494
+ label: value === "fixed" ? "Fixed Amount" : "Percentage of Order",
495
+ value
496
+ })),
497
+ defaultValue: allowedTotalCommissionTypes.includes("fixed") ? "fixed" : "percentage"
421
498
  },
422
499
  {
423
500
  name: "value",
424
501
  type: "number",
425
- required: true,
426
- min: 0,
427
- admin: { description: `Total commission value` }
502
+ admin: {
503
+ condition: ({ siblingData }) => siblingData?.type === "percentage",
504
+ description: "Total commission value (shown for percentage rules; ignored when using fixed split amounts)"
505
+ },
506
+ min: 0
428
507
  },
429
508
  {
430
509
  name: "maxAmount",
@@ -439,22 +518,25 @@ const createReferralProgramsCollection = (pluginConfig) => {
439
518
  type: "number",
440
519
  required: true,
441
520
  min: 0,
442
- max: 100,
443
- defaultValue: referralConfig.defaultPartnerSplit,
444
- admin: { description: "Percentage of total commission given to Partner (0-100)" }
521
+ admin: { description: "For percentage rules this is the percent that goes to the partner; when using fixed type it becomes the literal amount per item" }
522
+ },
523
+ {
524
+ name: "minOrderAmount",
525
+ type: "number",
526
+ min: 0,
527
+ admin: { description: `Minimum cart subtotal required for this rule in ${defaultCurrency}. Leave empty for no minimum.` }
445
528
  },
446
529
  {
447
530
  name: "customerSplit",
448
531
  type: "number",
449
532
  min: 0,
450
- max: 100,
451
- hooks: {
452
- beforeValidate: [({ siblingData }) => deriveCustomerSplit(siblingData?.partnerSplit)],
453
- beforeChange: [({ siblingData }) => deriveCustomerSplit(siblingData?.partnerSplit)]
454
- },
455
533
  admin: {
456
- readOnly: true,
457
- description: "Auto-calculated from Partner Split (saved automatically)"
534
+ condition: ({ siblingData }) => siblingData?.totalCommission?.type !== "fixed",
535
+ description: "When using percentage rules this is auto-calculated; for fixed-type rules you may enter a literal amount"
536
+ },
537
+ hooks: {
538
+ beforeValidate: [({ siblingData }) => deriveCustomerSplit(siblingData?.partnerSplit, siblingData?.totalCommission?.type)],
539
+ beforeChange: [({ siblingData }) => deriveCustomerSplit(siblingData?.partnerSplit, siblingData?.totalCommission?.type)]
458
540
  }
459
541
  }
460
542
  ]
@@ -529,6 +611,7 @@ function relationId(value) {
529
611
  if (typeof value === "object" && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
530
612
  return null;
531
613
  }
614
+ const allowedCommissionTypesSet = (allowed) => new Set((allowed && allowed.length ? allowed : ["fixed", "percentage"]).map((v) => v));
532
615
  function normalizeIds(values) {
533
616
  if (!Array.isArray(values)) return [];
534
617
  return values.map(relationId).filter((v) => v != null);
@@ -541,8 +624,19 @@ function getRuleSplits(rule) {
541
624
  customerSplit: typeof rule.customerSplit === "number" ? rule.customerSplit : typeof rule.refereeSplit === "number" ? rule.refereeSplit : 100 - partnerRaw
542
625
  };
543
626
  }
544
- function calculateItemRewardByRule({ rule, itemTotal, quantity }) {
627
+ function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalCommissionTypes }) {
628
+ const allowedTypes = allowedCommissionTypesSet(allowedTotalCommissionTypes);
545
629
  if (rule.totalCommission) {
630
+ if (!allowedTypes.has(rule.totalCommission.type)) return null;
631
+ if (rule.totalCommission.type === "fixed" && rule.totalCommission.value == null) {
632
+ const partnerAmt = typeof rule.partnerSplit === "number" ? rule.partnerSplit : null;
633
+ const customerAmt = typeof rule.customerSplit === "number" ? rule.customerSplit : null;
634
+ if (partnerAmt == null || customerAmt == null) return null;
635
+ return {
636
+ partner: partnerAmt * quantity,
637
+ customer: customerAmt * quantity
638
+ };
639
+ }
546
640
  const splits = getRuleSplits(rule);
547
641
  if (!splits) return null;
548
642
  let totalPot = 0;
@@ -581,21 +675,27 @@ function getItemCategoryIds(item) {
581
675
  function getItemTagIds(item) {
582
676
  return Array.isArray(item?.product?.tags) ? normalizeIds(item.product.tags) : [];
583
677
  }
584
- function selectBestRuleForItem({ rules, item, itemTotal, quantity }) {
678
+ function selectBestRuleForItem({ rules, item, itemTotal, quantity, cartTotal, allowedTotalCommissionTypes }) {
679
+ const allowedTypes = allowedCommissionTypesSet(allowedTotalCommissionTypes);
680
+ const eligibleRules = rules.filter((rule) => {
681
+ if (!(rule?.totalCommission?.type ? allowedTypes.has(rule.totalCommission.type) : true)) return false;
682
+ if (typeof rule?.minOrderAmount === "number" && Number.isFinite(rule.minOrderAmount)) return cartTotal >= rule.minOrderAmount;
683
+ return true;
684
+ });
585
685
  const productId = relationId(item.product);
586
686
  const itemCategoryIds = new Set(getItemCategoryIds(item));
587
687
  const itemTagIds = new Set(getItemTagIds(item));
588
688
  const candidates = [
589
- rules.filter((r) => r.appliesTo === "products" && normalizeIds(r.products).some((id) => productId != null && id === productId)),
590
- rules.filter((r) => {
689
+ eligibleRules.filter((r) => r.appliesTo === "products" && normalizeIds(r.products).some((id) => productId != null && id === productId)),
690
+ eligibleRules.filter((r) => {
591
691
  if (!(r.appliesTo === "segments" || r.appliesTo === "categories")) return false;
592
692
  return normalizeIds(r.categories).some((id) => itemCategoryIds.has(id));
593
693
  }),
594
- rules.filter((r) => {
694
+ eligibleRules.filter((r) => {
595
695
  if (r.appliesTo !== "segments") return false;
596
696
  return normalizeIds(r.tags).some((id) => itemTagIds.has(id));
597
697
  }),
598
- rules.filter((r) => r.appliesTo === "all")
698
+ eligibleRules.filter((r) => r.appliesTo === "all")
599
699
  ].find((level) => level.length > 0) ?? [];
600
700
  if (!candidates.length) return null;
601
701
  let best = null;
@@ -603,7 +703,8 @@ function selectBestRuleForItem({ rules, item, itemTotal, quantity }) {
603
703
  const reward = calculateItemRewardByRule({
604
704
  rule,
605
705
  itemTotal,
606
- quantity
706
+ quantity,
707
+ allowedTotalCommissionTypes
607
708
  });
608
709
  if (!reward) continue;
609
710
  if (!best) {
@@ -627,7 +728,18 @@ function selectBestRuleForItem({ rules, item, itemTotal, quantity }) {
627
728
  }
628
729
  return best;
629
730
  }
630
- function calculateCommissionAndDiscount({ cartItems, program, currencyCode = "AED" }) {
731
+ function getProgramMinimumOrderAmount({ program, allowedTotalCommissionTypes }) {
732
+ const rules = Array.isArray(program?.commissionRules) ? program.commissionRules : [];
733
+ if (!rules.length) return null;
734
+ const allowedTypes = allowedCommissionTypesSet(allowedTotalCommissionTypes);
735
+ const minValues = rules.filter((rule) => {
736
+ if (rule?.totalCommission?.type) return allowedTypes.has(rule.totalCommission.type);
737
+ return true;
738
+ }).map((rule) => rule?.minOrderAmount).filter((value) => typeof value === "number" && Number.isFinite(value));
739
+ if (!minValues.length) return null;
740
+ return Math.min(...minValues);
741
+ }
742
+ function calculateCommissionAndDiscount({ cartItems, program, currencyCode = "AED", cartTotal = 0, allowedTotalCommissionTypes }) {
631
743
  const rules = Array.isArray(program?.commissionRules) ? program.commissionRules : [];
632
744
  if (!rules.length) return {
633
745
  partnerCommission: 0,
@@ -652,7 +764,9 @@ function calculateCommissionAndDiscount({ cartItems, program, currencyCode = "AE
652
764
  product
653
765
  },
654
766
  itemTotal,
655
- quantity
767
+ quantity,
768
+ cartTotal,
769
+ allowedTotalCommissionTypes
656
770
  });
657
771
  if (!bestMatch) continue;
658
772
  totalPartnerCommission += bestMatch.reward.partner;
@@ -880,10 +994,20 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
880
994
  error: "Referral code already applied to this cart"
881
995
  }, { status: 400 });
882
996
  const cartTotal = cart.subtotal || cart.total || 0;
997
+ const minOrderAmount = getProgramMinimumOrderAmount({
998
+ program,
999
+ allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
1000
+ });
1001
+ if (typeof minOrderAmount === "number" && cartTotal < minOrderAmount) return Response.json({
1002
+ success: false,
1003
+ error: `Minimum order value of ${minOrderAmount} ${pluginConfig.defaultCurrency} required for this referral program`
1004
+ }, { status: 400 });
883
1005
  const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
884
1006
  cartItems: cart.items || [],
885
1007
  program,
886
- currencyCode: pluginConfig.defaultCurrency
1008
+ currencyCode: pluginConfig.defaultCurrency,
1009
+ cartTotal,
1010
+ allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
887
1011
  });
888
1012
  const roundedPartnerCommission = roundTo2(partnerCommission);
889
1013
  const roundedCustomerDiscount = roundTo2(customerDiscount);
@@ -923,8 +1047,14 @@ const partnerStatsHandler = ({ pluginConfig }) => async (req) => {
923
1047
  error: "Authentication required"
924
1048
  }, { status: 401 });
925
1049
  const typedUser = user;
926
- const isPartner = typedUser.role === "partner" || Array.isArray(typedUser.role) && typedUser.role?.includes("partner") || Array.isArray(typedUser.roles) && typedUser.roles?.includes("partner");
927
- const isAdmin = typedUser.role === "admin" || Array.isArray(typedUser.role) && typedUser.role?.includes("admin") || Array.isArray(typedUser.roles) && typedUser.roles?.includes("admin");
1050
+ const isPartner = isPartnerUser({
1051
+ user: typedUser,
1052
+ roleConfig: pluginConfig.roleConfig
1053
+ }) || pluginConfig.access.isPartner?.({ req });
1054
+ const isAdmin = isAdminUser({
1055
+ user: typedUser,
1056
+ roleConfig: pluginConfig.roleConfig
1057
+ }) || pluginConfig.access.isAdmin?.({ req });
928
1058
  if (!isPartner && !isAdmin) return Response.json({
929
1059
  success: false,
930
1060
  error: "Partner access required"
@@ -1204,12 +1334,22 @@ async function validateReferralCode({ payload, code, cartID, pluginConfig }) {
1204
1334
  id: cartID,
1205
1335
  depth: 2
1206
1336
  }) : null;
1337
+ const cartTotal = cart ? cart.subtotal || cart.total || 0 : 0;
1338
+ const minOrderAmount = getProgramMinimumOrderAmount({
1339
+ program,
1340
+ allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
1341
+ });
1342
+ if (typeof minOrderAmount === "number" && cartTotal < minOrderAmount) return Response.json({
1343
+ success: false,
1344
+ error: `Minimum order value of ${minOrderAmount} ${pluginConfig.defaultCurrency} required for this referral program`
1345
+ }, { status: 400 });
1207
1346
  const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
1208
1347
  cartItems: cart?.items || [],
1209
1348
  program,
1210
- currencyCode: pluginConfig.defaultCurrency
1349
+ currencyCode: pluginConfig.defaultCurrency,
1350
+ cartTotal,
1351
+ allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
1211
1352
  });
1212
- const cartTotal = cart ? cart.subtotal || cart.total || 0 : 0;
1213
1353
  const cappedCustomerDiscount = cartTotal > 0 ? Math.min(customerDiscount, cartTotal) : customerDiscount;
1214
1354
  const roundedPartnerCommission = roundTo2(partnerCommission);
1215
1355
  const roundedCustomerDiscount = roundTo2(cappedCustomerDiscount);
@@ -1235,12 +1375,6 @@ const validateCouponEndpoint = ({ pluginConfig }) => ({
1235
1375
  const recalculateCartHook = (pluginConfig) => async ({ data, req, originalDoc }) => {
1236
1376
  if (!req.payload) return data;
1237
1377
  const effectiveItems = data.items || originalDoc?.items || [];
1238
- console.log("[RecalculateCart] Hook triggered", {
1239
- hasDataItems: !!data.items,
1240
- dataItemsCount: data.items?.length,
1241
- originalItemsCount: originalDoc?.items?.length,
1242
- effectiveItemsCount: effectiveItems.length
1243
- });
1244
1378
  if (!effectiveItems.length) return {
1245
1379
  ...data,
1246
1380
  partnerCommission: 0,
@@ -1287,12 +1421,6 @@ const recalculateCartHook = (pluginConfig) => async ({ data, req, originalDoc })
1287
1421
  currencyCode: pluginConfig.defaultCurrency
1288
1422
  });
1289
1423
  calculatedSubtotal += itemPrice * (item.quantity ?? 1);
1290
- console.log("[RecalculateCart] Item processed", {
1291
- productId,
1292
- quantity: item.quantity,
1293
- priceUsed: itemPrice,
1294
- currentSubtotal: calculatedSubtotal
1295
- });
1296
1424
  return {
1297
1425
  ...item,
1298
1426
  product,
@@ -1321,16 +1449,30 @@ const recalculateCartHook = (pluginConfig) => async ({ data, req, originalDoc })
1321
1449
  id: programId
1322
1450
  }) : null;
1323
1451
  if (program) {
1452
+ const minOrderAmount = getProgramMinimumOrderAmount({
1453
+ program,
1454
+ allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
1455
+ });
1456
+ if (typeof minOrderAmount === "number" && calculatedSubtotal < minOrderAmount) {
1457
+ data.appliedReferralCode = null;
1458
+ data.partnerCommission = 0;
1459
+ data.customerDiscount = 0;
1460
+ data.total = calculatedSubtotal;
1461
+ return data;
1462
+ }
1324
1463
  const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
1325
1464
  cartItems: enrichedItems,
1326
1465
  program,
1327
- currencyCode: pluginConfig.defaultCurrency
1466
+ currencyCode: pluginConfig.defaultCurrency,
1467
+ cartTotal: calculatedSubtotal,
1468
+ allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
1328
1469
  });
1329
1470
  const roundedCustomerDiscount = roundTo2(customerDiscount);
1330
1471
  data.partnerCommission = roundTo2(partnerCommission);
1331
1472
  data.customerDiscount = roundedCustomerDiscount;
1332
1473
  data.total = Math.max(0, calculatedSubtotal - roundedCustomerDiscount);
1333
1474
  } else {
1475
+ data.appliedReferralCode = null;
1334
1476
  data.partnerCommission = 0;
1335
1477
  data.customerDiscount = 0;
1336
1478
  data.total = calculatedSubtotal;
@@ -1351,12 +1493,6 @@ const recalculateCartHook = (pluginConfig) => async ({ data, req, originalDoc })
1351
1493
  coupon,
1352
1494
  cartTotal: calculatedSubtotal
1353
1495
  });
1354
- console.log("[RecalculateCart] Coupon Logic", {
1355
- appliedCoupon,
1356
- couponId: coupon.id,
1357
- cartTotal: calculatedSubtotal,
1358
- discountAmount
1359
- });
1360
1496
  data.discountAmount = discountAmount;
1361
1497
  const currentDiscount = data.customerDiscount || 0;
1362
1498
  data.total = Math.max(0, calculatedSubtotal - currentDiscount - discountAmount);
@@ -1368,6 +1504,13 @@ const recalculateCartHook = (pluginConfig) => async ({ data, req, originalDoc })
1368
1504
  //#endregion
1369
1505
  //#region src/utilities/sanitizePluginConfig.ts
1370
1506
  const sanitizePluginConfig = ({ pluginConfig }) => {
1507
+ const roleConfig = {
1508
+ roleFieldPaths: Array.isArray(pluginConfig?.roleConfig?.roleFieldPaths) && pluginConfig.roleConfig.roleFieldPaths.length > 0 ? pluginConfig.roleConfig.roleFieldPaths.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean) : ["role", "roles"],
1509
+ adminRoleValues: Array.isArray(pluginConfig?.roleConfig?.adminRoleValues) && pluginConfig.roleConfig.adminRoleValues.length > 0 ? pluginConfig.roleConfig.adminRoleValues.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean) : ["admin"],
1510
+ partnerRoleValues: Array.isArray(pluginConfig?.roleConfig?.partnerRoleValues) && pluginConfig.roleConfig.partnerRoleValues.length > 0 ? pluginConfig.roleConfig.partnerRoleValues.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean) : ["partner"],
1511
+ customRoleResolver: typeof pluginConfig?.roleConfig?.customRoleResolver === "function" ? pluginConfig.roleConfig.customRoleResolver : void 0
1512
+ };
1513
+ const normalizedAllowedTotalCommissionTypes = Array.isArray(pluginConfig?.referralConfig?.allowedTotalCommissionTypes) ? [...new Set(pluginConfig.referralConfig.allowedTotalCommissionTypes.filter((value) => value === "fixed" || value === "percentage"))] : [];
1371
1514
  return {
1372
1515
  enabled: !(pluginConfig?.enabled === false || typeof pluginConfig?.enabled === "string" && pluginConfig.enabled === "false"),
1373
1516
  enableReferrals: !!pluginConfig?.enableReferrals && (typeof pluginConfig?.enableReferrals !== "string" || pluginConfig.enableReferrals !== "false"),
@@ -1389,20 +1532,21 @@ const sanitizePluginConfig = ({ pluginConfig }) => {
1389
1532
  access: {
1390
1533
  canUseCoupons: typeof pluginConfig?.access?.canUseCoupons === "function" ? pluginConfig.access.canUseCoupons : () => true,
1391
1534
  canUseReferrals: typeof pluginConfig?.access?.canUseReferrals === "function" ? pluginConfig.access.canUseReferrals : () => false,
1392
- isAdmin: typeof pluginConfig?.access?.isAdmin === "function" ? pluginConfig.access.isAdmin : () => false,
1393
- isPartner: typeof pluginConfig?.access?.isPartner === "function" ? pluginConfig.access.isPartner : ({ req }) => {
1394
- const user = req?.user;
1395
- if (!user) return false;
1396
- if (user.role === "partner") return true;
1397
- if (Array.isArray(user.roles) && user.roles.includes("partner")) return true;
1398
- return false;
1399
- }
1535
+ isAdmin: typeof pluginConfig?.access?.isAdmin === "function" ? pluginConfig.access.isAdmin : ({ req }) => isAdminUser({
1536
+ user: req?.user,
1537
+ roleConfig
1538
+ }),
1539
+ isPartner: typeof pluginConfig?.access?.isPartner === "function" ? pluginConfig.access.isPartner : ({ req }) => isPartnerUser({
1540
+ user: req?.user,
1541
+ roleConfig
1542
+ })
1400
1543
  },
1401
1544
  referralConfig: {
1402
1545
  allowBothSystems: pluginConfig?.referralConfig?.allowBothSystems ?? false,
1403
1546
  singleCodePerCart: pluginConfig?.referralConfig?.singleCodePerCart ?? true,
1404
1547
  defaultPartnerSplit: pluginConfig?.referralConfig?.defaultPartnerSplit ?? 70,
1405
- defaultCustomerSplit: pluginConfig?.referralConfig?.defaultCustomerSplit ?? 30
1548
+ defaultCustomerSplit: pluginConfig?.referralConfig?.defaultCustomerSplit ?? 30,
1549
+ allowedTotalCommissionTypes: normalizedAllowedTotalCommissionTypes.length > 0 ? normalizedAllowedTotalCommissionTypes : ["fixed", "percentage"]
1406
1550
  },
1407
1551
  adminGroups: {
1408
1552
  couponsGroup: pluginConfig?.adminGroups?.couponsGroup ?? "Coupons",
@@ -1420,7 +1564,8 @@ const sanitizePluginConfig = ({ pluginConfig }) => {
1420
1564
  orderCustomerEmailField: typeof pluginConfig?.orderIntegration?.orderCustomerEmailField === "string" && pluginConfig.orderIntegration.orderCustomerEmailField.trim().length > 0 ? pluginConfig.orderIntegration.orderCustomerEmailField : "customerEmail",
1421
1565
  orderPaymentStatusField: typeof pluginConfig?.orderIntegration?.orderPaymentStatusField === "string" && pluginConfig.orderIntegration.orderPaymentStatusField.trim().length > 0 ? pluginConfig.orderIntegration.orderPaymentStatusField : "paymentStatus",
1422
1566
  orderPaidStatusValue: typeof pluginConfig?.orderIntegration?.orderPaidStatusValue === "string" ? pluginConfig.orderIntegration.orderPaidStatusValue : "paid"
1423
- }
1567
+ },
1568
+ roleConfig
1424
1569
  };
1425
1570
  };
1426
1571
 
@@ -1807,5 +1952,5 @@ async function recordCouponUsageForOrder(payload, order, pluginConfig) {
1807
1952
  }
1808
1953
 
1809
1954
  //#endregion
1810
- export { calculateCommissionAndDiscount, createCouponsCollection, createReferralCodesCollection, createReferralProgramsCollection, getCartTotalWithDiscounts, payloadEcommerceCouponPlugin as payloadEcommerceCoupon, recordCouponUsageForOrder, useCouponCode, usePartnerStats, validateCouponCode };
1955
+ export { calculateCommissionAndDiscount, createCouponsCollection, createReferralCodesCollection, createReferralProgramsCollection, getCartTotalWithDiscounts, getProgramMinimumOrderAmount, payloadEcommerceCouponPlugin as payloadEcommerceCoupon, recordCouponUsageForOrder, useCouponCode, usePartnerStats, validateCouponCode };
1811
1956
  //# sourceMappingURL=index.mjs.map