@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.
- package/README.md +22 -1
- package/dist/client/hooks.d.ts +1 -1
- package/dist/client/hooks.d.ts.map +1 -1
- package/dist/client/index.d.ts +6 -6
- package/dist/client/index.d.ts.map +1 -1
- package/dist/collections/createCouponsCollection.d.ts +2 -2
- package/dist/collections/createCouponsCollection.d.ts.map +1 -1
- package/dist/collections/createReferralCodesCollection.d.ts +2 -2
- package/dist/collections/createReferralCodesCollection.d.ts.map +1 -1
- package/dist/collections/createReferralProgramsCollection.d.ts +2 -2
- package/dist/collections/createReferralProgramsCollection.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/EarningsSummary.d.ts +2 -2
- package/dist/components/PartnerDashboard/EarningsSummary.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/RecentReferrals.d.ts +3 -3
- package/dist/components/PartnerDashboard/RecentReferrals.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/ReferralCodes.d.ts +3 -3
- package/dist/components/PartnerDashboard/ReferralCodes.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/ReferralPerformance.d.ts +2 -2
- package/dist/components/PartnerDashboard/ReferralPerformance.d.ts.map +1 -1
- package/dist/components/PartnerDashboard/index.d.ts +2 -2
- package/dist/components/PartnerDashboard/index.d.ts.map +1 -1
- package/dist/endpoints/applyCoupon.d.ts +2 -2
- package/dist/endpoints/applyCoupon.d.ts.map +1 -1
- package/dist/endpoints/partnerStats.d.ts +2 -2
- package/dist/endpoints/partnerStats.d.ts.map +1 -1
- package/dist/endpoints/validateCoupon.d.ts +2 -2
- package/dist/endpoints/validateCoupon.d.ts.map +1 -1
- package/dist/hooks/recalculateCart.d.ts +2 -2
- package/dist/hooks/recalculateCart.d.ts.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +228 -82
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +228 -83
- package/dist/index.mjs.map +1 -1
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/types.d.ts +23 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities/calculateValues.d.ts +7 -1
- package/dist/utilities/calculateValues.d.ts.map +1 -1
- package/dist/utilities/getCartTotalWithDiscounts.d.ts.map +1 -1
- package/dist/utilities/migrateReferralRulesV2.d.ts.map +1 -1
- package/dist/utilities/pricing.d.ts.map +1 -1
- package/dist/utilities/pushTypeScriptProperties.d.ts +1 -1
- package/dist/utilities/pushTypeScriptProperties.d.ts.map +1 -1
- package/dist/utilities/recordCouponUsageForOrder.d.ts +2 -2
- package/dist/utilities/recordCouponUsageForOrder.d.ts.map +1 -1
- package/dist/utilities/sanitizePluginConfig.d.ts +1 -1
- package/dist/utilities/sanitizePluginConfig.d.ts.map +1 -1
- package/dist/utilities/userRoles.d.ts +25 -0
- package/dist/utilities/userRoles.d.ts.map +1 -0
- 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 (
|
|
143
|
-
|
|
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 (
|
|
150
|
-
|
|
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: ({
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return
|
|
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 (
|
|
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 || !
|
|
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
|
|
313
|
-
const
|
|
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
|
|
416
|
-
},
|
|
417
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
457
|
-
description: "
|
|
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
|
-
|
|
590
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
927
|
-
|
|
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 : () =>
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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
|