@wtree/payload-ecommerce-coupon 3.77.4 → 3.77.6
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 +274 -684
- package/dist/client/hooks.d.ts +21 -13
- 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 +1123 -543
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1122 -541
- 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 +103 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/utilities/calculateValues.d.ts +2 -2
- 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 +11 -17
- 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 +3 -3
- package/dist/utilities/userRoles.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value:
|
|
2
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
2
|
//#region src/collections/createCouponsCollection.ts
|
|
4
3
|
const createCouponsCollection = (pluginConfig) => {
|
|
5
|
-
const { collections, access, defaultCurrency, adminGroups } = pluginConfig;
|
|
4
|
+
const { collections, access, defaultCurrency, adminGroups, integration } = pluginConfig;
|
|
5
|
+
const usersSlug = integration.collections.usersSlug;
|
|
6
6
|
return {
|
|
7
7
|
slug: collections.couponsSlug,
|
|
8
8
|
admin: {
|
|
@@ -30,6 +30,16 @@ const createCouponsCollection = (pluginConfig) => {
|
|
|
30
30
|
unique: true,
|
|
31
31
|
admin: { description: "The coupon code that customers will enter" }
|
|
32
32
|
},
|
|
33
|
+
{
|
|
34
|
+
name: "normalizedCode",
|
|
35
|
+
type: "text",
|
|
36
|
+
unique: true,
|
|
37
|
+
index: true,
|
|
38
|
+
admin: {
|
|
39
|
+
hidden: true,
|
|
40
|
+
description: "Uppercased, trimmed code used for fast case-insensitive lookups"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
33
43
|
{
|
|
34
44
|
name: "description",
|
|
35
45
|
type: "text",
|
|
@@ -105,21 +115,33 @@ const createCouponsCollection = (pluginConfig) => {
|
|
|
105
115
|
{
|
|
106
116
|
name: "createdBy",
|
|
107
117
|
type: "relationship",
|
|
108
|
-
relationTo:
|
|
118
|
+
relationTo: usersSlug,
|
|
109
119
|
admin: {
|
|
110
120
|
readOnly: true,
|
|
111
121
|
position: "sidebar"
|
|
112
122
|
}
|
|
113
123
|
}
|
|
114
124
|
],
|
|
115
|
-
hooks: {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
hooks: {
|
|
126
|
+
beforeValidate: [({ data }) => {
|
|
127
|
+
if (data && typeof data.code === "string") {
|
|
128
|
+
data.code = data.code.trim();
|
|
129
|
+
data.normalizedCode = data.code.toUpperCase();
|
|
130
|
+
}
|
|
131
|
+
return data;
|
|
132
|
+
}],
|
|
133
|
+
beforeChange: [({ operation, req, data }) => {
|
|
134
|
+
if (data && typeof data.code === "string") {
|
|
135
|
+
data.code = data.code.trim();
|
|
136
|
+
data.normalizedCode = data.code.toUpperCase();
|
|
137
|
+
}
|
|
138
|
+
if (operation === "create" && req.user && !data.createdBy) data.createdBy = req.user.id;
|
|
139
|
+
return data;
|
|
140
|
+
}]
|
|
141
|
+
},
|
|
119
142
|
timestamps: true
|
|
120
143
|
};
|
|
121
144
|
};
|
|
122
|
-
|
|
123
145
|
//#endregion
|
|
124
146
|
//#region src/utilities/userRoles.ts
|
|
125
147
|
function readByPath(input, path) {
|
|
@@ -168,17 +190,19 @@ const buildPartnerUserFilterWhere = ({ roleConfig }) => {
|
|
|
168
190
|
if (conditions.length === 1) return conditions[0];
|
|
169
191
|
return { or: conditions };
|
|
170
192
|
};
|
|
171
|
-
|
|
172
193
|
//#endregion
|
|
173
194
|
//#region src/collections/createReferralCodesCollection.ts
|
|
195
|
+
const normalizeCode$2 = (value) => typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
174
196
|
const createReferralCodesCollection = (pluginConfig) => {
|
|
175
|
-
const { collections, access, adminGroups, defaultCurrency, roleConfig } = pluginConfig;
|
|
197
|
+
const { collections, access, adminGroups, defaultCurrency, roleConfig, policies, integration } = pluginConfig;
|
|
198
|
+
const usersSlug = integration.collections.usersSlug;
|
|
176
199
|
return {
|
|
177
200
|
slug: collections.referralCodesSlug,
|
|
178
201
|
admin: {
|
|
179
202
|
useAsTitle: "code",
|
|
180
203
|
defaultColumns: [
|
|
181
204
|
"code",
|
|
205
|
+
"normalizedCode",
|
|
182
206
|
"partner",
|
|
183
207
|
"program",
|
|
184
208
|
"usageCount",
|
|
@@ -187,34 +211,59 @@ const createReferralCodesCollection = (pluginConfig) => {
|
|
|
187
211
|
group: adminGroups.referralsGroup
|
|
188
212
|
},
|
|
189
213
|
access: {
|
|
190
|
-
read: ({ req }) => {
|
|
214
|
+
read: async ({ req }) => {
|
|
191
215
|
const user = req?.user;
|
|
192
216
|
if (!user) return false;
|
|
193
217
|
if (isAdminUser({
|
|
194
218
|
user,
|
|
195
219
|
roleConfig
|
|
196
|
-
}) || access.isAdmin?.({ req })) return true;
|
|
220
|
+
}) || await Promise.resolve(access.isAdmin?.({ req }))) return true;
|
|
221
|
+
if (!await Promise.resolve(policies.canApplyReferral({
|
|
222
|
+
req,
|
|
223
|
+
user,
|
|
224
|
+
payload: req?.payload
|
|
225
|
+
}))) return false;
|
|
197
226
|
if (isPartnerUser({
|
|
198
227
|
user,
|
|
199
228
|
roleConfig
|
|
200
|
-
}) || access.isPartner?.({ req })) return { partner: { equals: user.id } };
|
|
229
|
+
}) || await Promise.resolve(access.isPartner?.({ req }))) return { partner: { equals: user.id } };
|
|
201
230
|
return access.canUseReferrals ? access.canUseReferrals({ req }) : false;
|
|
202
231
|
},
|
|
203
|
-
create: ({ req }) => {
|
|
232
|
+
create: async ({ req }) => {
|
|
204
233
|
const user = req?.user;
|
|
205
234
|
if (!user) return false;
|
|
235
|
+
if (!await Promise.resolve(policies.canApplyReferral({
|
|
236
|
+
req,
|
|
237
|
+
user,
|
|
238
|
+
payload: req?.payload
|
|
239
|
+
}))) return false;
|
|
206
240
|
if (isAdminUser({
|
|
207
241
|
user,
|
|
208
242
|
roleConfig
|
|
209
|
-
}) || access.isAdmin?.({ req })) return true;
|
|
243
|
+
}) || await Promise.resolve(access.isAdmin?.({ req }))) return true;
|
|
210
244
|
if (isPartnerUser({
|
|
211
245
|
user,
|
|
212
246
|
roleConfig
|
|
213
|
-
}) || access.isPartner?.({ req })) return true;
|
|
247
|
+
}) || await Promise.resolve(access.isPartner?.({ req }))) return true;
|
|
214
248
|
return access.isAdmin ? access.isAdmin({ req }) : false;
|
|
215
249
|
},
|
|
216
|
-
update:
|
|
217
|
-
|
|
250
|
+
update: async ({ req }) => {
|
|
251
|
+
const user = req?.user;
|
|
252
|
+
if (!user) return false;
|
|
253
|
+
if (isAdminUser({
|
|
254
|
+
user,
|
|
255
|
+
roleConfig
|
|
256
|
+
}) || await Promise.resolve(access.isAdmin?.({ req }))) return true;
|
|
257
|
+
return false;
|
|
258
|
+
},
|
|
259
|
+
delete: async ({ req }) => {
|
|
260
|
+
const user = req?.user;
|
|
261
|
+
if (!user) return false;
|
|
262
|
+
return isAdminUser({
|
|
263
|
+
user,
|
|
264
|
+
roleConfig
|
|
265
|
+
}) || await Promise.resolve(access.isAdmin?.({ req }));
|
|
266
|
+
}
|
|
218
267
|
},
|
|
219
268
|
fields: [
|
|
220
269
|
{
|
|
@@ -224,6 +273,17 @@ const createReferralCodesCollection = (pluginConfig) => {
|
|
|
224
273
|
unique: true,
|
|
225
274
|
admin: { description: "The referral code that customers will enter" }
|
|
226
275
|
},
|
|
276
|
+
{
|
|
277
|
+
name: "normalizedCode",
|
|
278
|
+
type: "text",
|
|
279
|
+
required: true,
|
|
280
|
+
unique: true,
|
|
281
|
+
index: true,
|
|
282
|
+
admin: {
|
|
283
|
+
readOnly: true,
|
|
284
|
+
description: "Uppercased normalized code for fast case-insensitive lookup"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
227
287
|
{
|
|
228
288
|
name: "program",
|
|
229
289
|
type: "relationship",
|
|
@@ -234,16 +294,16 @@ const createReferralCodesCollection = (pluginConfig) => {
|
|
|
234
294
|
{
|
|
235
295
|
name: "partner",
|
|
236
296
|
type: "relationship",
|
|
237
|
-
relationTo:
|
|
297
|
+
relationTo: usersSlug,
|
|
238
298
|
required: true,
|
|
239
|
-
filterOptions: ({ req, user }) => {
|
|
299
|
+
filterOptions: async ({ req, user }) => {
|
|
240
300
|
if (isAdminUser({
|
|
241
301
|
user: user || req?.user,
|
|
242
302
|
roleConfig
|
|
243
|
-
}) || access.isAdmin?.({ req })) return true;
|
|
303
|
+
}) || await Promise.resolve(access.isAdmin?.({ req }))) return true;
|
|
244
304
|
return buildPartnerUserFilterWhere({ roleConfig });
|
|
245
305
|
},
|
|
246
|
-
admin: { description:
|
|
306
|
+
admin: { description: `The partner who owns this referral code (relation: ${usersSlug})` }
|
|
247
307
|
},
|
|
248
308
|
{
|
|
249
309
|
name: "isActive",
|
|
@@ -315,40 +375,41 @@ const createReferralCodesCollection = (pluginConfig) => {
|
|
|
315
375
|
}
|
|
316
376
|
}
|
|
317
377
|
],
|
|
318
|
-
hooks: {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
378
|
+
hooks: {
|
|
379
|
+
beforeValidate: [({ data }) => {
|
|
380
|
+
if (!data) return data;
|
|
381
|
+
data.normalizedCode = normalizeCode$2(data.code);
|
|
382
|
+
return data;
|
|
383
|
+
}],
|
|
384
|
+
beforeChange: [({ operation, req, data }) => {
|
|
385
|
+
if (!data) return data;
|
|
386
|
+
if (operation === "create" && !data.code && data.partner) data.code = `REF-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`.toUpperCase();
|
|
387
|
+
data.normalizedCode = normalizeCode$2(data.code);
|
|
388
|
+
if (operation === "create" && req.user) {
|
|
389
|
+
const user = req.user;
|
|
390
|
+
if (isPartnerUser({
|
|
391
|
+
user,
|
|
392
|
+
roleConfig
|
|
393
|
+
}) && user.id != null) data.partner = user.id;
|
|
394
|
+
}
|
|
395
|
+
return data;
|
|
396
|
+
}]
|
|
397
|
+
},
|
|
329
398
|
timestamps: true
|
|
330
399
|
};
|
|
331
400
|
};
|
|
332
|
-
|
|
333
401
|
//#endregion
|
|
334
402
|
//#region src/collections/createReferralProgramsCollection.ts
|
|
335
403
|
function toNumber(value) {
|
|
336
404
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
337
405
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return partner != null ? partner : 0;
|
|
342
|
-
}
|
|
343
|
-
const partner = toNumber(partnerSplit);
|
|
344
|
-
if (partner == null) return 0;
|
|
345
|
-
if (partner < 0) return 100;
|
|
346
|
-
if (partner > 100) return 0;
|
|
347
|
-
return 100 - partner;
|
|
348
|
-
};
|
|
406
|
+
function toCents(value) {
|
|
407
|
+
return Math.round(value * 100);
|
|
408
|
+
}
|
|
349
409
|
const createReferralProgramsCollection = (pluginConfig) => {
|
|
350
|
-
const { collections, access, defaultCurrency, adminGroups, referralConfig } = pluginConfig;
|
|
410
|
+
const { collections, access, defaultCurrency, adminGroups, referralConfig, integration } = pluginConfig;
|
|
351
411
|
const allowedTotalCommissionTypes = referralConfig.allowedTotalCommissionTypes;
|
|
412
|
+
const relationSlugs = integration.collections;
|
|
352
413
|
return {
|
|
353
414
|
slug: collections.referralProgramsSlug,
|
|
354
415
|
admin: {
|
|
@@ -372,34 +433,71 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
372
433
|
const r = rule;
|
|
373
434
|
if (!r.totalCommission) throw new Error(`Commission rule ${index + 1}: Total Commission is required`);
|
|
374
435
|
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(", ")}`);
|
|
436
|
+
const type = r.totalCommission.type;
|
|
375
437
|
const totalValue = toNumber(r.totalCommission.value);
|
|
376
|
-
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`);
|
|
377
|
-
if (r.totalCommission.type === "percentage" && totalValue && totalValue > 100) throw new Error(`Commission rule ${index + 1}: Percentage Total Commission cannot exceed 100`);
|
|
378
438
|
const maxAmount = toNumber(r.totalCommission.maxAmount);
|
|
439
|
+
if (type === "percentage") {
|
|
440
|
+
if (totalValue == null || totalValue < 0) throw new Error(`Commission rule ${index + 1}: Total Commission value must be a non-negative number`);
|
|
441
|
+
if (totalValue > 100) throw new Error(`Commission rule ${index + 1}: Percentage Total Commission cannot exceed 100`);
|
|
442
|
+
}
|
|
379
443
|
if (maxAmount != null && maxAmount < 0) throw new Error(`Commission rule ${index + 1}: Max Amount must be a non-negative number`);
|
|
380
444
|
const appliesTo = r.appliesTo ?? "all";
|
|
381
445
|
if (appliesTo === "products" && (!r.products || r.products.length === 0)) throw new Error(`Commission rule ${index + 1}: At least one product is required`);
|
|
382
446
|
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`);
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
let
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
447
|
+
let partnerSplit;
|
|
448
|
+
let customerSplit;
|
|
449
|
+
let partnerPercent = null;
|
|
450
|
+
let customerPercent = null;
|
|
451
|
+
let partnerAmount = null;
|
|
452
|
+
let customerAmount = null;
|
|
453
|
+
if (type === "percentage") {
|
|
454
|
+
const partnerPctInput = toNumber(r.partnerPercent) ?? toNumber(r.partnerSplit);
|
|
455
|
+
if (partnerPctInput == null || partnerPctInput < 0 || partnerPctInput > 100) throw new Error(`Commission rule ${index + 1}: Partner Split must be between 0 and 100`);
|
|
456
|
+
const customerPctComputed = 100 - partnerPctInput;
|
|
457
|
+
if (customerPctComputed < 0 || customerPctComputed > 100) throw new Error(`Commission rule ${index + 1}: Customer percentage must be between 0 and 100`);
|
|
458
|
+
partnerPercent = partnerPctInput;
|
|
459
|
+
customerPercent = customerPctComputed;
|
|
460
|
+
partnerSplit = partnerPctInput;
|
|
461
|
+
customerSplit = customerPctComputed;
|
|
462
|
+
} else {
|
|
463
|
+
const partnerAmountInput = toNumber(r.partnerAmount);
|
|
464
|
+
const customerAmountInput = toNumber(r.customerAmount);
|
|
465
|
+
const legacyPartnerSplitInput = toNumber(r.partnerSplit);
|
|
466
|
+
const legacyCustomerSplitInput = toNumber(r.customerSplit);
|
|
467
|
+
const hasNewFixedInputs = partnerAmountInput != null || customerAmountInput != null;
|
|
468
|
+
const hasLegacyFixedInputs = legacyPartnerSplitInput != null || legacyCustomerSplitInput != null;
|
|
469
|
+
if (hasNewFixedInputs) {
|
|
470
|
+
if (partnerAmountInput == null || partnerAmountInput < 0) throw new Error(`Commission rule ${index + 1}: Partner fixed amount must be a non-negative number`);
|
|
471
|
+
if (customerAmountInput == null || customerAmountInput < 0) throw new Error(`Commission rule ${index + 1}: Customer fixed amount must be a non-negative number`);
|
|
472
|
+
partnerAmount = partnerAmountInput;
|
|
473
|
+
customerAmount = customerAmountInput;
|
|
474
|
+
partnerSplit = toCents(partnerAmountInput);
|
|
475
|
+
customerSplit = toCents(customerAmountInput);
|
|
476
|
+
} else if (hasLegacyFixedInputs) {
|
|
477
|
+
if (legacyPartnerSplitInput == null || legacyPartnerSplitInput < 0) throw new Error(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be non-negative numbers`);
|
|
478
|
+
const legacyHasTotalValue = toNumber(r.totalCommission?.value) != null;
|
|
479
|
+
const resolvedLegacyCustomerSplit = legacyCustomerSplitInput ?? (legacyHasTotalValue ? 100 - legacyPartnerSplitInput : null);
|
|
480
|
+
if (resolvedLegacyCustomerSplit == null || resolvedLegacyCustomerSplit < 0) throw new Error(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be non-negative numbers`);
|
|
481
|
+
partnerSplit = legacyPartnerSplitInput;
|
|
482
|
+
customerSplit = resolvedLegacyCustomerSplit;
|
|
483
|
+
partnerAmount = null;
|
|
484
|
+
customerAmount = null;
|
|
485
|
+
} else throw new Error(`Commission rule ${index + 1}: For fixed commissions, both partner and customer values must be provided`);
|
|
486
|
+
}
|
|
393
487
|
const minOrderAmount = toNumber(r.minOrderAmount);
|
|
394
488
|
if (minOrderAmount != null && minOrderAmount < 0) throw new Error(`Commission rule ${index + 1}: Minimum Order Amount must be a non-negative number`);
|
|
395
489
|
return {
|
|
396
490
|
...rule,
|
|
397
491
|
appliesTo: appliesTo === "categories" ? "segments" : appliesTo,
|
|
398
492
|
totalCommission: {
|
|
399
|
-
type
|
|
400
|
-
value: totalValue,
|
|
493
|
+
type,
|
|
494
|
+
value: type === "percentage" ? totalValue : null,
|
|
401
495
|
maxAmount: maxAmount ?? null
|
|
402
496
|
},
|
|
497
|
+
partnerPercent,
|
|
498
|
+
customerPercent,
|
|
499
|
+
partnerAmount,
|
|
500
|
+
customerAmount,
|
|
403
501
|
partnerSplit,
|
|
404
502
|
customerSplit,
|
|
405
503
|
minOrderAmount: minOrderAmount ?? null
|
|
@@ -456,7 +554,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
456
554
|
{
|
|
457
555
|
name: "products",
|
|
458
556
|
type: "relationship",
|
|
459
|
-
relationTo:
|
|
557
|
+
relationTo: relationSlugs.productsSlug,
|
|
460
558
|
hasMany: true,
|
|
461
559
|
admin: {
|
|
462
560
|
condition: (_, siblingData) => siblingData?.appliesTo === "products",
|
|
@@ -466,7 +564,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
466
564
|
{
|
|
467
565
|
name: "categories",
|
|
468
566
|
type: "relationship",
|
|
469
|
-
relationTo:
|
|
567
|
+
relationTo: relationSlugs.categoriesSlug,
|
|
470
568
|
hasMany: true,
|
|
471
569
|
admin: {
|
|
472
570
|
condition: (_, siblingData) => siblingData?.appliesTo === "segments",
|
|
@@ -476,7 +574,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
476
574
|
{
|
|
477
575
|
name: "tags",
|
|
478
576
|
type: "relationship",
|
|
479
|
-
relationTo:
|
|
577
|
+
relationTo: relationSlugs.tagsSlug,
|
|
480
578
|
hasMany: true,
|
|
481
579
|
admin: {
|
|
482
580
|
condition: (_, siblingData) => siblingData?.appliesTo === "segments",
|
|
@@ -486,7 +584,7 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
486
584
|
{
|
|
487
585
|
name: "totalCommission",
|
|
488
586
|
type: "group",
|
|
489
|
-
admin: { description: "Total commission pool
|
|
587
|
+
admin: { description: "Total commission pool configuration" },
|
|
490
588
|
fields: [
|
|
491
589
|
{
|
|
492
590
|
name: "type",
|
|
@@ -501,11 +599,12 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
501
599
|
{
|
|
502
600
|
name: "value",
|
|
503
601
|
type: "number",
|
|
602
|
+
min: 0,
|
|
603
|
+
max: 100,
|
|
504
604
|
admin: {
|
|
505
605
|
condition: ({ siblingData }) => siblingData?.type === "percentage",
|
|
506
|
-
description: "Total commission
|
|
507
|
-
}
|
|
508
|
-
min: 0
|
|
606
|
+
description: "Total commission percentage for this rule (0-100). Partner/Customer percentages split this 100-based bucket."
|
|
607
|
+
}
|
|
509
608
|
},
|
|
510
609
|
{
|
|
511
610
|
name: "maxAmount",
|
|
@@ -516,30 +615,74 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
516
615
|
]
|
|
517
616
|
},
|
|
518
617
|
{
|
|
519
|
-
name: "
|
|
618
|
+
name: "partnerPercent",
|
|
520
619
|
type: "number",
|
|
521
|
-
required: true,
|
|
522
620
|
min: 0,
|
|
523
|
-
|
|
621
|
+
max: 100,
|
|
622
|
+
admin: {
|
|
623
|
+
condition: ({ siblingData }) => siblingData?.totalCommission?.type === "percentage",
|
|
624
|
+
description: "Partner share in percent (0-100). Customer share is auto-calculated as 100 - Partner."
|
|
625
|
+
}
|
|
524
626
|
},
|
|
525
627
|
{
|
|
526
|
-
name: "
|
|
628
|
+
name: "customerPercent",
|
|
527
629
|
type: "number",
|
|
528
630
|
min: 0,
|
|
529
|
-
|
|
631
|
+
max: 100,
|
|
632
|
+
admin: {
|
|
633
|
+
readOnly: true,
|
|
634
|
+
condition: ({ siblingData }) => siblingData?.totalCommission?.type === "percentage",
|
|
635
|
+
description: "Auto-calculated customer share percentage."
|
|
636
|
+
},
|
|
637
|
+
hooks: { beforeValidate: [({ siblingData }) => {
|
|
638
|
+
if (!siblingData || siblingData.totalCommission?.type !== "percentage") return null;
|
|
639
|
+
const partner = toNumber(siblingData.partnerPercent) ?? toNumber(siblingData.partnerSplit) ?? 0;
|
|
640
|
+
if (partner < 0) return 100;
|
|
641
|
+
if (partner > 100) return 0;
|
|
642
|
+
return 100 - partner;
|
|
643
|
+
}] }
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: "partnerAmount",
|
|
647
|
+
type: "number",
|
|
648
|
+
min: 0,
|
|
649
|
+
admin: {
|
|
650
|
+
condition: ({ siblingData }) => siblingData?.totalCommission?.type === "fixed",
|
|
651
|
+
description: `Fixed partner commission amount per item in ${defaultCurrency}. Stored as cents internally.`
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
name: "customerAmount",
|
|
656
|
+
type: "number",
|
|
657
|
+
min: 0,
|
|
658
|
+
admin: {
|
|
659
|
+
condition: ({ siblingData }) => siblingData?.totalCommission?.type === "fixed",
|
|
660
|
+
description: `Fixed customer discount amount per item in ${defaultCurrency}. Stored as cents internally.`
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
name: "partnerSplit",
|
|
665
|
+
type: "number",
|
|
666
|
+
min: 0,
|
|
667
|
+
admin: {
|
|
668
|
+
hidden: true,
|
|
669
|
+
description: "Canonical storage field. Percentage mode: percent. Fixed mode: amount in cents."
|
|
670
|
+
}
|
|
530
671
|
},
|
|
531
672
|
{
|
|
532
673
|
name: "customerSplit",
|
|
533
674
|
type: "number",
|
|
534
675
|
min: 0,
|
|
535
676
|
admin: {
|
|
536
|
-
|
|
537
|
-
description: "
|
|
538
|
-
},
|
|
539
|
-
hooks: {
|
|
540
|
-
beforeValidate: [({ siblingData }) => deriveCustomerSplit(siblingData?.partnerSplit, siblingData?.totalCommission?.type)],
|
|
541
|
-
beforeChange: [({ siblingData }) => deriveCustomerSplit(siblingData?.partnerSplit, siblingData?.totalCommission?.type)]
|
|
677
|
+
hidden: true,
|
|
678
|
+
description: "Canonical storage field. Percentage mode: percent. Fixed mode: amount in cents."
|
|
542
679
|
}
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: "minOrderAmount",
|
|
683
|
+
type: "number",
|
|
684
|
+
min: 0,
|
|
685
|
+
admin: { description: `Minimum cart subtotal required for this rule in ${defaultCurrency}. Leave empty for no minimum.` }
|
|
543
686
|
}
|
|
544
687
|
]
|
|
545
688
|
}
|
|
@@ -547,7 +690,6 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
547
690
|
timestamps: true
|
|
548
691
|
};
|
|
549
692
|
};
|
|
550
|
-
|
|
551
693
|
//#endregion
|
|
552
694
|
//#region src/utilities/roundTo2.ts
|
|
553
695
|
/**
|
|
@@ -556,12 +698,8 @@ const createReferralProgramsCollection = (pluginConfig) => {
|
|
|
556
698
|
function roundTo2(value) {
|
|
557
699
|
return Math.round(value * 100) / 100;
|
|
558
700
|
}
|
|
559
|
-
|
|
560
|
-
//#endregion
|
|
561
|
-
//#region src/utilities/pricing.ts
|
|
562
|
-
const DEFAULT_PRICE_CURRENCY = "AED";
|
|
563
701
|
function normalizeCurrencyCode(currencyCode) {
|
|
564
|
-
if (!currencyCode) return
|
|
702
|
+
if (!currencyCode) return "AED";
|
|
565
703
|
return currencyCode.toUpperCase();
|
|
566
704
|
}
|
|
567
705
|
function readNumberField(entity, key) {
|
|
@@ -572,7 +710,7 @@ function readNumberField(entity, key) {
|
|
|
572
710
|
function getPriceFieldKey(currencyCode) {
|
|
573
711
|
return `priceIn${normalizeCurrencyCode(currencyCode)}`;
|
|
574
712
|
}
|
|
575
|
-
function readMoneyField(entity, currencyCode, defaultCurrencyCode =
|
|
713
|
+
function readMoneyField(entity, currencyCode, defaultCurrencyCode = "AED") {
|
|
576
714
|
if (!entity) return void 0;
|
|
577
715
|
const primaryField = getPriceFieldKey(currencyCode);
|
|
578
716
|
const primary = readNumberField(entity, primaryField);
|
|
@@ -584,16 +722,15 @@ function readMoneyField(entity, currencyCode, defaultCurrencyCode = DEFAULT_PRIC
|
|
|
584
722
|
}
|
|
585
723
|
return typeof entity.price === "number" ? entity.price : void 0;
|
|
586
724
|
}
|
|
587
|
-
function resolveMoneyField(entity, currencyCode, defaultCurrencyCode =
|
|
725
|
+
function resolveMoneyField(entity, currencyCode, defaultCurrencyCode = "AED") {
|
|
588
726
|
return readMoneyField(entity, currencyCode, defaultCurrencyCode) ?? 0;
|
|
589
727
|
}
|
|
590
|
-
function getCartItemUnitPrice({ item, product, variant, currencyCode, defaultCurrencyCode =
|
|
728
|
+
function getCartItemUnitPrice({ item, product, variant, currencyCode, defaultCurrencyCode = "AED" }) {
|
|
591
729
|
if (typeof item?.price === "number") return item.price;
|
|
592
730
|
if (typeof item?.unitPrice === "number") return item.unitPrice;
|
|
593
731
|
if (variant) return resolveMoneyField(variant, currencyCode, defaultCurrencyCode);
|
|
594
732
|
return resolveMoneyField(product, currencyCode, defaultCurrencyCode);
|
|
595
733
|
}
|
|
596
|
-
|
|
597
734
|
//#endregion
|
|
598
735
|
//#region src/utilities/calculateValues.ts
|
|
599
736
|
function calculateCouponDiscount({ coupon, cartTotal }) {
|
|
@@ -607,7 +744,7 @@ function calculateCouponDiscount({ coupon, cartTotal }) {
|
|
|
607
744
|
}
|
|
608
745
|
return roundTo2(discount);
|
|
609
746
|
}
|
|
610
|
-
function relationId(value) {
|
|
747
|
+
function relationId$5(value) {
|
|
611
748
|
if (value == null) return null;
|
|
612
749
|
if (typeof value === "string" || typeof value === "number") return value;
|
|
613
750
|
if (typeof value === "object" && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
|
|
@@ -616,7 +753,7 @@ function relationId(value) {
|
|
|
616
753
|
const allowedCommissionTypesSet = (allowed) => new Set((allowed && allowed.length ? allowed : ["fixed", "percentage"]).map((v) => v));
|
|
617
754
|
function normalizeIds(values) {
|
|
618
755
|
if (!Array.isArray(values)) return [];
|
|
619
|
-
return values.map(relationId).filter((v) => v != null);
|
|
756
|
+
return values.map(relationId$5).filter((v) => v != null);
|
|
620
757
|
}
|
|
621
758
|
function getRuleSplits(rule) {
|
|
622
759
|
const partnerRaw = typeof rule.partnerSplit === "number" ? rule.partnerSplit : typeof rule.referrerSplit === "number" ? rule.referrerSplit : null;
|
|
@@ -671,7 +808,7 @@ function calculateItemRewardByRule({ rule, itemTotal, quantity, allowedTotalComm
|
|
|
671
808
|
}
|
|
672
809
|
function getItemCategoryIds(item) {
|
|
673
810
|
const productCategories = Array.isArray(item?.product?.categories) ? normalizeIds(item.product.categories) : [];
|
|
674
|
-
const singleCategory = relationId(item?.category ?? item?.product?.category);
|
|
811
|
+
const singleCategory = relationId$5(item?.category ?? item?.product?.category);
|
|
675
812
|
return [...productCategories, ...singleCategory != null ? [singleCategory] : []];
|
|
676
813
|
}
|
|
677
814
|
function getItemTagIds(item) {
|
|
@@ -684,7 +821,7 @@ function selectBestRuleForItem({ rules, item, itemTotal, quantity, cartTotal, al
|
|
|
684
821
|
if (typeof rule?.minOrderAmount === "number" && Number.isFinite(rule.minOrderAmount)) return cartTotal >= rule.minOrderAmount;
|
|
685
822
|
return true;
|
|
686
823
|
});
|
|
687
|
-
const productId = relationId(item.product);
|
|
824
|
+
const productId = relationId$5(item.product);
|
|
688
825
|
const itemCategoryIds = new Set(getItemCategoryIds(item));
|
|
689
826
|
const itemTagIds = new Set(getItemTagIds(item));
|
|
690
827
|
const candidates = [
|
|
@@ -779,66 +916,118 @@ function calculateCommissionAndDiscount({ cartItems, program, currencyCode = "AE
|
|
|
779
916
|
customerDiscount: totalCustomerDiscount
|
|
780
917
|
};
|
|
781
918
|
}
|
|
782
|
-
|
|
783
919
|
//#endregion
|
|
784
920
|
//#region src/endpoints/applyCoupon.ts
|
|
785
|
-
|
|
786
|
-
const getRelationId = (value) => {
|
|
921
|
+
function relationId$4(value) {
|
|
787
922
|
if (value == null) return null;
|
|
788
923
|
if (typeof value === "string" || typeof value === "number") return value;
|
|
789
924
|
if (typeof value === "object" && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
|
|
790
925
|
return null;
|
|
791
|
-
}
|
|
926
|
+
}
|
|
927
|
+
function readField$3(doc, field) {
|
|
928
|
+
if (!doc || typeof doc !== "object") return void 0;
|
|
929
|
+
return doc[field];
|
|
930
|
+
}
|
|
931
|
+
function writeField$1(doc, field, value) {
|
|
932
|
+
doc[field] = value;
|
|
933
|
+
}
|
|
934
|
+
function normalizeCode$1(value) {
|
|
935
|
+
return typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
936
|
+
}
|
|
937
|
+
async function findByNormalizedCode$1({ payload, collection, normalizedCode }) {
|
|
938
|
+
const exactQuery = await payload.find({
|
|
939
|
+
collection,
|
|
940
|
+
where: { normalizedCode: { equals: normalizedCode } },
|
|
941
|
+
limit: 1
|
|
942
|
+
});
|
|
943
|
+
if (exactQuery?.docs?.[0]) return exactQuery.docs[0];
|
|
944
|
+
const lowerQuery = await payload.find({
|
|
945
|
+
collection,
|
|
946
|
+
where: { code: { equals: normalizedCode.toLowerCase() } },
|
|
947
|
+
limit: 1
|
|
948
|
+
});
|
|
949
|
+
if (lowerQuery?.docs?.[0]) return lowerQuery.docs[0];
|
|
950
|
+
const upperQuery = await payload.find({
|
|
951
|
+
collection,
|
|
952
|
+
where: { code: { equals: normalizedCode.toUpperCase() } },
|
|
953
|
+
limit: 1
|
|
954
|
+
});
|
|
955
|
+
if (upperQuery?.docs?.[0]) return upperQuery.docs[0];
|
|
956
|
+
return (await payload.find({
|
|
957
|
+
collection,
|
|
958
|
+
where: { code: { equals: normalizedCode } },
|
|
959
|
+
limit: 1
|
|
960
|
+
}))?.docs?.[0] ?? null;
|
|
961
|
+
}
|
|
792
962
|
const applyCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
793
|
-
globalDebugLogs.length = 0;
|
|
794
963
|
const { payload } = req;
|
|
795
|
-
const
|
|
796
|
-
const
|
|
797
|
-
|
|
964
|
+
const fields = pluginConfig.integration.fields;
|
|
965
|
+
const collections = pluginConfig.integration.collections;
|
|
966
|
+
const rawCode = req?.data?.code;
|
|
967
|
+
const cartID = req?.data?.cartID;
|
|
968
|
+
const customerEmail = req?.data?.customerEmail;
|
|
969
|
+
const normalizedCode = normalizeCode$1(rawCode);
|
|
970
|
+
if (!normalizedCode || !cartID) return Response.json({
|
|
798
971
|
success: false,
|
|
799
972
|
error: `${pluginConfig.enableReferrals ? "Referral code" : "Coupon code"} and cart ID are required`
|
|
800
973
|
}, { status: 400 });
|
|
974
|
+
const allowCoupon = await Promise.resolve(pluginConfig.policies.canApplyCoupon({
|
|
975
|
+
req,
|
|
976
|
+
user: req?.user,
|
|
977
|
+
payload
|
|
978
|
+
}));
|
|
979
|
+
const allowReferral = await Promise.resolve(pluginConfig.policies.canApplyReferral({
|
|
980
|
+
req,
|
|
981
|
+
user: req?.user,
|
|
982
|
+
payload
|
|
983
|
+
}));
|
|
984
|
+
if (!allowCoupon && !(pluginConfig.enableReferrals && allowReferral)) return Response.json({
|
|
985
|
+
success: false,
|
|
986
|
+
error: "Forbidden"
|
|
987
|
+
}, { status: 403 });
|
|
801
988
|
try {
|
|
802
|
-
const
|
|
803
|
-
collection:
|
|
989
|
+
const cart = await payload.findByID({
|
|
990
|
+
collection: collections.cartsSlug,
|
|
804
991
|
id: cartID,
|
|
805
992
|
depth: 2
|
|
806
993
|
});
|
|
807
|
-
if (!
|
|
994
|
+
if (!cart) return Response.json({
|
|
808
995
|
success: false,
|
|
809
996
|
error: "Cart not found"
|
|
810
997
|
}, { status: 404 });
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
}
|
|
819
|
-
if (pluginConfig.enableReferrals) {
|
|
998
|
+
const cartAppliedCoupon = relationId$4(readField$3(cart, fields.cartAppliedCouponField));
|
|
999
|
+
const cartAppliedReferral = relationId$4(readField$3(cart, fields.cartAppliedReferralCodeField));
|
|
1000
|
+
if (pluginConfig.referralConfig.singleCodePerCart && (cartAppliedCoupon || cartAppliedReferral)) return Response.json({
|
|
1001
|
+
success: false,
|
|
1002
|
+
error: "A code has already been applied to this cart. Only one code can be used per order."
|
|
1003
|
+
}, { status: 400 });
|
|
1004
|
+
if (pluginConfig.enableReferrals && allowReferral) {
|
|
820
1005
|
const referralResult = await handleReferralCode({
|
|
821
1006
|
payload,
|
|
822
|
-
|
|
1007
|
+
cart,
|
|
823
1008
|
cartID,
|
|
824
|
-
|
|
825
|
-
customerEmail,
|
|
1009
|
+
normalizedCode,
|
|
826
1010
|
pluginConfig
|
|
827
1011
|
});
|
|
828
|
-
if (!referralResult.ok && referralResult.status === 404 && pluginConfig.referralConfig.allowBothSystems) return await handleCouponCode({
|
|
1012
|
+
if (!referralResult.ok && referralResult.status === 404 && pluginConfig.referralConfig.allowBothSystems && allowCoupon) return await handleCouponCode({
|
|
829
1013
|
payload,
|
|
830
|
-
|
|
1014
|
+
cart,
|
|
831
1015
|
cartID,
|
|
832
|
-
|
|
1016
|
+
normalizedCode,
|
|
833
1017
|
customerEmail,
|
|
834
1018
|
pluginConfig
|
|
835
1019
|
});
|
|
836
1020
|
return referralResult;
|
|
837
|
-
}
|
|
1021
|
+
}
|
|
1022
|
+
if (!allowCoupon) return Response.json({
|
|
1023
|
+
success: false,
|
|
1024
|
+
error: "Forbidden"
|
|
1025
|
+
}, { status: 403 });
|
|
1026
|
+
return await handleCouponCode({
|
|
838
1027
|
payload,
|
|
839
|
-
|
|
1028
|
+
cart,
|
|
840
1029
|
cartID,
|
|
841
|
-
|
|
1030
|
+
normalizedCode,
|
|
842
1031
|
customerEmail,
|
|
843
1032
|
pluginConfig
|
|
844
1033
|
});
|
|
@@ -850,27 +1039,18 @@ const applyCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
|
850
1039
|
}, { status: 500 });
|
|
851
1040
|
}
|
|
852
1041
|
};
|
|
853
|
-
async function handleCouponCode({ payload,
|
|
854
|
-
|
|
1042
|
+
async function handleCouponCode({ payload, cart, cartID, normalizedCode, customerEmail, pluginConfig }) {
|
|
1043
|
+
const fields = pluginConfig.integration.fields;
|
|
1044
|
+
const resolvers = pluginConfig.integration.resolvers;
|
|
1045
|
+
const coupon = await findByNormalizedCode$1({
|
|
1046
|
+
payload,
|
|
855
1047
|
collection: pluginConfig.collections.couponsSlug,
|
|
856
|
-
|
|
857
|
-
limit: 1
|
|
1048
|
+
normalizedCode
|
|
858
1049
|
});
|
|
859
|
-
if (!
|
|
860
|
-
collection: pluginConfig.collections.couponsSlug,
|
|
861
|
-
where: { code: { equals: code.toLowerCase() } },
|
|
862
|
-
limit: 1
|
|
863
|
-
});
|
|
864
|
-
if (!couponQuery.docs.length) couponQuery = await payload.find({
|
|
865
|
-
collection: pluginConfig.collections.couponsSlug,
|
|
866
|
-
where: { code: { equals: code.toUpperCase() } },
|
|
867
|
-
limit: 1
|
|
868
|
-
});
|
|
869
|
-
if (!couponQuery.docs.length) return Response.json({
|
|
1050
|
+
if (!coupon) return Response.json({
|
|
870
1051
|
success: false,
|
|
871
1052
|
error: "Invalid coupon code"
|
|
872
1053
|
}, { status: 404 });
|
|
873
|
-
const coupon = couponQuery.docs[0];
|
|
874
1054
|
const now = /* @__PURE__ */ new Date();
|
|
875
1055
|
const activeFrom = coupon.activeFrom ? new Date(coupon.activeFrom) : null;
|
|
876
1056
|
const activeUntil = coupon.activeUntil ? new Date(coupon.activeUntil) : null;
|
|
@@ -892,13 +1072,12 @@ async function handleCouponCode({ payload, code, cartID, cart, customerEmail, pl
|
|
|
892
1072
|
success: false,
|
|
893
1073
|
error: "Customer email is required for this coupon."
|
|
894
1074
|
}, { status: 400 });
|
|
895
|
-
const { ordersSlug, orderCustomerEmailField, orderPaymentStatusField, orderPaidStatusValue } = pluginConfig.orderIntegration;
|
|
896
1075
|
if ((await payload.find({
|
|
897
|
-
collection: ordersSlug,
|
|
1076
|
+
collection: pluginConfig.orderIntegration.ordersSlug,
|
|
898
1077
|
where: { and: [
|
|
899
|
-
{
|
|
900
|
-
{ [orderCustomerEmailField]: { equals: email } },
|
|
901
|
-
{ [orderPaymentStatusField]: { equals: orderPaidStatusValue } }
|
|
1078
|
+
{ [fields.orderAppliedCouponField]: { equals: coupon.id } },
|
|
1079
|
+
{ [pluginConfig.orderIntegration.orderCustomerEmailField]: { equals: email } },
|
|
1080
|
+
{ [pluginConfig.orderIntegration.orderPaymentStatusField]: { equals: pluginConfig.orderIntegration.orderPaidStatusValue } }
|
|
902
1081
|
] },
|
|
903
1082
|
limit: 0
|
|
904
1083
|
})).totalDocs >= coupon.perCustomerLimit) return Response.json({
|
|
@@ -906,11 +1085,12 @@ async function handleCouponCode({ payload, code, cartID, cart, customerEmail, pl
|
|
|
906
1085
|
error: "You have reached the maximum uses for this coupon."
|
|
907
1086
|
}, { status: 400 });
|
|
908
1087
|
}
|
|
909
|
-
if (
|
|
1088
|
+
if (relationId$4(readField$3(cart, fields.cartAppliedCouponField)) === coupon.id) return Response.json({
|
|
910
1089
|
success: false,
|
|
911
1090
|
error: "Coupon already applied to this cart"
|
|
912
1091
|
}, { status: 400 });
|
|
913
|
-
const
|
|
1092
|
+
const cartSubtotal = Number(resolvers.getCartSubtotal(cart)) || 0;
|
|
1093
|
+
const cartTotal = Number(resolvers.getCartTotal(cart)) || cartSubtotal || 0;
|
|
914
1094
|
if (coupon.minOrderValue && cartTotal < coupon.minOrderValue) return Response.json({
|
|
915
1095
|
success: false,
|
|
916
1096
|
error: `Minimum order value of ${coupon.minOrderValue} ${pluginConfig.defaultCurrency} required`
|
|
@@ -923,15 +1103,15 @@ async function handleCouponCode({ payload, code, cartID, cart, customerEmail, pl
|
|
|
923
1103
|
coupon,
|
|
924
1104
|
cartTotal
|
|
925
1105
|
});
|
|
926
|
-
const
|
|
1106
|
+
const nextTotal = roundTo2(Math.max(0, cartTotal - discountAmount));
|
|
1107
|
+
const data = {};
|
|
1108
|
+
writeField$1(data, fields.cartAppliedCouponField, coupon.id);
|
|
1109
|
+
writeField$1(data, fields.cartDiscountAmountField, discountAmount);
|
|
1110
|
+
writeField$1(data, fields.cartTotalField, nextTotal);
|
|
927
1111
|
await payload.update({
|
|
928
|
-
collection:
|
|
1112
|
+
collection: pluginConfig.integration.collections.cartsSlug,
|
|
929
1113
|
id: cartID,
|
|
930
|
-
data
|
|
931
|
-
appliedCoupon: coupon.id,
|
|
932
|
-
discountAmount,
|
|
933
|
-
total
|
|
934
|
-
}
|
|
1114
|
+
data
|
|
935
1115
|
});
|
|
936
1116
|
return Response.json({
|
|
937
1117
|
success: true,
|
|
@@ -942,34 +1122,21 @@ async function handleCouponCode({ payload, code, cartID, cart, customerEmail, pl
|
|
|
942
1122
|
value: coupon.value
|
|
943
1123
|
},
|
|
944
1124
|
discount: discountAmount,
|
|
945
|
-
currency: pluginConfig.defaultCurrency
|
|
946
|
-
debug: globalDebugLogs
|
|
1125
|
+
currency: pluginConfig.defaultCurrency
|
|
947
1126
|
});
|
|
948
1127
|
}
|
|
949
|
-
async function handleReferralCode({ payload,
|
|
950
|
-
|
|
1128
|
+
async function handleReferralCode({ payload, cart, cartID, normalizedCode, pluginConfig }) {
|
|
1129
|
+
const fields = pluginConfig.integration.fields;
|
|
1130
|
+
const resolvers = pluginConfig.integration.resolvers;
|
|
1131
|
+
const referralCode = await findByNormalizedCode$1({
|
|
1132
|
+
payload,
|
|
951
1133
|
collection: pluginConfig.collections.referralCodesSlug,
|
|
952
|
-
|
|
953
|
-
limit: 1,
|
|
954
|
-
depth: 1
|
|
1134
|
+
normalizedCode
|
|
955
1135
|
});
|
|
956
|
-
if (!
|
|
957
|
-
collection: pluginConfig.collections.referralCodesSlug,
|
|
958
|
-
where: { code: { equals: code.toLowerCase() } },
|
|
959
|
-
limit: 1,
|
|
960
|
-
depth: 1
|
|
961
|
-
});
|
|
962
|
-
if (!referralQuery.docs.length) referralQuery = await payload.find({
|
|
963
|
-
collection: pluginConfig.collections.referralCodesSlug,
|
|
964
|
-
where: { code: { equals: code.toUpperCase() } },
|
|
965
|
-
limit: 1,
|
|
966
|
-
depth: 1
|
|
967
|
-
});
|
|
968
|
-
if (!referralQuery.docs.length) return Response.json({
|
|
1136
|
+
if (!referralCode) return Response.json({
|
|
969
1137
|
success: false,
|
|
970
1138
|
error: "Invalid referral code"
|
|
971
1139
|
}, { status: 404 });
|
|
972
|
-
const referralCode = referralQuery.docs[0];
|
|
973
1140
|
if (!referralCode.isActive) return Response.json({
|
|
974
1141
|
success: false,
|
|
975
1142
|
error: "Referral code is not active"
|
|
@@ -982,7 +1149,7 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
|
|
|
982
1149
|
success: false,
|
|
983
1150
|
error: "Referral code usage limit exceeded"
|
|
984
1151
|
}, { status: 400 });
|
|
985
|
-
const programId = typeof referralCode.program === "string" ? referralCode.program : referralCode.program?.id;
|
|
1152
|
+
const programId = typeof referralCode.program === "string" || typeof referralCode.program === "number" ? referralCode.program : referralCode.program?.id;
|
|
986
1153
|
const program = await payload.findByID({
|
|
987
1154
|
collection: pluginConfig.collections.referralProgramsSlug,
|
|
988
1155
|
id: programId
|
|
@@ -991,11 +1158,12 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
|
|
|
991
1158
|
success: false,
|
|
992
1159
|
error: "Referral program is not active"
|
|
993
1160
|
}, { status: 400 });
|
|
994
|
-
if (
|
|
1161
|
+
if (relationId$4(readField$3(cart, fields.cartAppliedReferralCodeField)) === referralCode.id) return Response.json({
|
|
995
1162
|
success: false,
|
|
996
1163
|
error: "Referral code already applied to this cart"
|
|
997
1164
|
}, { status: 400 });
|
|
998
|
-
const
|
|
1165
|
+
const cartItems = resolvers.getCartItems(cart);
|
|
1166
|
+
const cartTotal = Number(resolvers.getCartTotal(cart)) || Number(resolvers.getCartSubtotal(cart)) || 0;
|
|
999
1167
|
const minOrderAmount = getProgramMinimumOrderAmount({
|
|
1000
1168
|
program,
|
|
1001
1169
|
allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
|
|
@@ -1005,7 +1173,7 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
|
|
|
1005
1173
|
error: `Minimum order value of ${minOrderAmount} ${pluginConfig.defaultCurrency} required for this referral program`
|
|
1006
1174
|
}, { status: 400 });
|
|
1007
1175
|
const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
|
|
1008
|
-
cartItems
|
|
1176
|
+
cartItems,
|
|
1009
1177
|
program,
|
|
1010
1178
|
currencyCode: pluginConfig.defaultCurrency,
|
|
1011
1179
|
cartTotal,
|
|
@@ -1013,16 +1181,16 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
|
|
|
1013
1181
|
});
|
|
1014
1182
|
const roundedPartnerCommission = roundTo2(partnerCommission);
|
|
1015
1183
|
const roundedCustomerDiscount = roundTo2(customerDiscount);
|
|
1016
|
-
const
|
|
1184
|
+
const nextTotal = roundTo2(Math.max(0, cartTotal - roundedCustomerDiscount));
|
|
1185
|
+
const data = {};
|
|
1186
|
+
writeField$1(data, fields.cartAppliedReferralCodeField, referralCode.id);
|
|
1187
|
+
writeField$1(data, fields.cartPartnerCommissionField, roundedPartnerCommission);
|
|
1188
|
+
writeField$1(data, fields.cartCustomerDiscountField, roundedCustomerDiscount);
|
|
1189
|
+
writeField$1(data, fields.cartTotalField, nextTotal);
|
|
1017
1190
|
await payload.update({
|
|
1018
|
-
collection:
|
|
1191
|
+
collection: pluginConfig.integration.collections.cartsSlug,
|
|
1019
1192
|
id: cartID,
|
|
1020
|
-
data
|
|
1021
|
-
appliedReferralCode: referralCode.id,
|
|
1022
|
-
partnerCommission: roundedPartnerCommission,
|
|
1023
|
-
customerDiscount: roundedCustomerDiscount,
|
|
1024
|
-
total
|
|
1025
|
-
}
|
|
1193
|
+
data
|
|
1026
1194
|
});
|
|
1027
1195
|
return Response.json({
|
|
1028
1196
|
success: true,
|
|
@@ -1030,8 +1198,7 @@ async function handleReferralCode({ payload, code, cartID, cart, customerEmail:
|
|
|
1030
1198
|
referralCode: { code: referralCode.code },
|
|
1031
1199
|
partnerCommission: roundedPartnerCommission,
|
|
1032
1200
|
customerDiscount: roundedCustomerDiscount,
|
|
1033
|
-
currency: pluginConfig.defaultCurrency
|
|
1034
|
-
debug: globalDebugLogs
|
|
1201
|
+
currency: pluginConfig.defaultCurrency
|
|
1035
1202
|
});
|
|
1036
1203
|
}
|
|
1037
1204
|
const applyCouponEndpoint = ({ pluginConfig }) => ({
|
|
@@ -1039,70 +1206,115 @@ const applyCouponEndpoint = ({ pluginConfig }) => ({
|
|
|
1039
1206
|
method: "post",
|
|
1040
1207
|
handler: applyCouponHandler({ pluginConfig })
|
|
1041
1208
|
});
|
|
1042
|
-
|
|
1043
1209
|
//#endregion
|
|
1044
1210
|
//#region src/endpoints/partnerStats.ts
|
|
1211
|
+
function relationId$3(value) {
|
|
1212
|
+
if (value == null) return null;
|
|
1213
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
1214
|
+
if (typeof value === "object" && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
function readField$2(doc, field) {
|
|
1218
|
+
if (!doc || typeof doc !== "object") return void 0;
|
|
1219
|
+
return doc[field];
|
|
1220
|
+
}
|
|
1221
|
+
function asNumber$1(value) {
|
|
1222
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1223
|
+
}
|
|
1224
|
+
function asString(value) {
|
|
1225
|
+
return typeof value === "string" ? value : "";
|
|
1226
|
+
}
|
|
1227
|
+
function toStatsStatus(value) {
|
|
1228
|
+
if (value === "paid") return "paid";
|
|
1229
|
+
if (value === "cancelled") return "cancelled";
|
|
1230
|
+
return "pending";
|
|
1231
|
+
}
|
|
1045
1232
|
const partnerStatsHandler = ({ pluginConfig }) => async (req) => {
|
|
1046
1233
|
const { payload, user } = req;
|
|
1234
|
+
const fields = pluginConfig.integration.fields;
|
|
1235
|
+
const collections = pluginConfig.integration.collections;
|
|
1047
1236
|
if (!user) return Response.json({
|
|
1048
1237
|
success: false,
|
|
1049
1238
|
error: "Authentication required"
|
|
1050
1239
|
}, { status: 401 });
|
|
1051
1240
|
const typedUser = user;
|
|
1241
|
+
const userID = pluginConfig.integration.resolvers.getUserID({
|
|
1242
|
+
req,
|
|
1243
|
+
user
|
|
1244
|
+
});
|
|
1245
|
+
if (userID == null) return Response.json({
|
|
1246
|
+
success: false,
|
|
1247
|
+
error: "Unable to resolve user identity"
|
|
1248
|
+
}, { status: 403 });
|
|
1052
1249
|
const isPartner = isPartnerUser({
|
|
1053
1250
|
user: typedUser,
|
|
1054
1251
|
roleConfig: pluginConfig.roleConfig
|
|
1055
|
-
}) || pluginConfig.access.isPartner?.({ req });
|
|
1252
|
+
}) || await Promise.resolve(pluginConfig.access.isPartner?.({ req }));
|
|
1056
1253
|
const isAdmin = isAdminUser({
|
|
1057
1254
|
user: typedUser,
|
|
1058
1255
|
roleConfig: pluginConfig.roleConfig
|
|
1059
|
-
}) || pluginConfig.access.isAdmin?.({ req });
|
|
1060
|
-
if (!
|
|
1256
|
+
}) || await Promise.resolve(pluginConfig.access.isAdmin?.({ req }));
|
|
1257
|
+
if (!await Promise.resolve(pluginConfig.policies.canViewPartnerStats({
|
|
1258
|
+
req,
|
|
1259
|
+
user,
|
|
1260
|
+
payload,
|
|
1261
|
+
requestedPartnerID: userID
|
|
1262
|
+
})) && !isAdmin && !isPartner) return Response.json({
|
|
1061
1263
|
success: false,
|
|
1062
1264
|
error: "Partner access required"
|
|
1063
1265
|
}, { status: 403 });
|
|
1064
1266
|
try {
|
|
1065
|
-
const
|
|
1267
|
+
const referralCodesQuery = await payload.find({
|
|
1066
1268
|
collection: pluginConfig.collections.referralCodesSlug,
|
|
1067
|
-
where: { partner: { equals:
|
|
1269
|
+
where: { partner: { equals: userID } },
|
|
1068
1270
|
limit: 100
|
|
1069
|
-
})
|
|
1271
|
+
});
|
|
1272
|
+
const referralCodes = Array.isArray(referralCodesQuery?.docs) ? referralCodesQuery.docs : [];
|
|
1070
1273
|
let totalEarnings = 0;
|
|
1071
1274
|
let pendingEarnings = 0;
|
|
1072
1275
|
let paidEarnings = 0;
|
|
1073
1276
|
let totalReferrals = 0;
|
|
1074
1277
|
let successfulReferrals = 0;
|
|
1075
1278
|
const referralCodeData = referralCodes.map((code) => {
|
|
1076
|
-
totalEarnings += code
|
|
1077
|
-
pendingEarnings += code
|
|
1078
|
-
paidEarnings += code
|
|
1079
|
-
totalReferrals += code
|
|
1080
|
-
successfulReferrals += code
|
|
1279
|
+
totalEarnings += asNumber$1(code?.totalEarnings);
|
|
1280
|
+
pendingEarnings += asNumber$1(code?.pendingEarnings);
|
|
1281
|
+
paidEarnings += asNumber$1(code?.paidEarnings);
|
|
1282
|
+
totalReferrals += asNumber$1(code?.usageCount);
|
|
1283
|
+
successfulReferrals += asNumber$1(code?.successfulReferralsCount);
|
|
1081
1284
|
return {
|
|
1082
|
-
id: code
|
|
1083
|
-
code: code
|
|
1084
|
-
usageCount: code
|
|
1085
|
-
totalEarnings: code
|
|
1086
|
-
isActive: code
|
|
1285
|
+
id: String(code?.id ?? ""),
|
|
1286
|
+
code: asString(code?.code),
|
|
1287
|
+
usageCount: asNumber$1(code?.usageCount),
|
|
1288
|
+
totalEarnings: asNumber$1(code?.totalEarnings),
|
|
1289
|
+
isActive: Boolean(code?.isActive)
|
|
1087
1290
|
};
|
|
1088
1291
|
});
|
|
1089
1292
|
const conversionRate = totalReferrals > 0 ? successfulReferrals / totalReferrals * 100 : 0;
|
|
1090
1293
|
const recentReferrals = [];
|
|
1091
1294
|
try {
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1295
|
+
const referralCodeIDs = referralCodes.map((c) => relationId$3(c?.id)).filter((id) => id != null);
|
|
1296
|
+
if (referralCodeIDs.length > 0) {
|
|
1297
|
+
const ordersQuery = await payload.find({
|
|
1298
|
+
collection: collections.ordersSlug,
|
|
1299
|
+
where: { [fields.orderAppliedReferralCodeField]: { in: referralCodeIDs } },
|
|
1300
|
+
limit: 10,
|
|
1301
|
+
sort: `-${fields.orderCreatedAtField}`
|
|
1302
|
+
});
|
|
1303
|
+
for (const order of ordersQuery?.docs || []) {
|
|
1304
|
+
const orderReferralID = relationId$3(readField$2(order, fields.orderAppliedReferralCodeField));
|
|
1305
|
+
const matchedCode = referralCodes.find((c) => relationId$3(c?.id) === orderReferralID);
|
|
1306
|
+
const paymentStatus = readField$2(order, fields.orderPaymentStatusField);
|
|
1307
|
+
const createdAt = readField$2(order, fields.orderCreatedAtField);
|
|
1308
|
+
recentReferrals.push({
|
|
1309
|
+
id: String(order?.id ?? ""),
|
|
1310
|
+
code: asString(matchedCode?.code),
|
|
1311
|
+
orderValue: asNumber$1(readField$2(order, fields.cartTotalField) ?? order?.total),
|
|
1312
|
+
commission: asNumber$1(readField$2(order, fields.orderPartnerCommissionField)),
|
|
1313
|
+
date: asString(createdAt),
|
|
1314
|
+
status: toStatsStatus(paymentStatus)
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1106
1318
|
} catch {}
|
|
1107
1319
|
const monthlyEarnings = [];
|
|
1108
1320
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1120,18 +1332,19 @@ const partnerStatsHandler = ({ pluginConfig }) => async (req) => {
|
|
|
1120
1332
|
let program = null;
|
|
1121
1333
|
if (referralCodes.length > 0) {
|
|
1122
1334
|
const firstCode = referralCodes[0];
|
|
1123
|
-
|
|
1335
|
+
const programID = relationId$3(firstCode?.program);
|
|
1336
|
+
if (programID != null) try {
|
|
1124
1337
|
const programData = await payload.findByID({
|
|
1125
1338
|
collection: pluginConfig.collections.referralProgramsSlug,
|
|
1126
|
-
id:
|
|
1339
|
+
id: programID
|
|
1127
1340
|
});
|
|
1128
1341
|
if (programData) {
|
|
1129
1342
|
const typedProgram = programData;
|
|
1130
|
-
const firstRule = typedProgram
|
|
1131
|
-
const partnerSplit = firstRule?.partnerSplit
|
|
1132
|
-
const customerSplit = firstRule?.customerSplit
|
|
1343
|
+
const firstRule = typedProgram?.commissionRules?.[0];
|
|
1344
|
+
const partnerSplit = asNumber$1(firstRule?.partnerSplit) || asNumber$1(firstRule?.referrerSplit) || asNumber$1(firstRule?.split?.partnerPercentage);
|
|
1345
|
+
const customerSplit = asNumber$1(firstRule?.customerSplit) || asNumber$1(firstRule?.refereeSplit) || asNumber$1(firstRule?.split?.customerPercentage) || Math.max(0, 100 - partnerSplit);
|
|
1133
1346
|
program = {
|
|
1134
|
-
name: typedProgram
|
|
1347
|
+
name: asString(typedProgram?.name),
|
|
1135
1348
|
commissionRate: partnerSplit,
|
|
1136
1349
|
customerDiscount: customerSplit
|
|
1137
1350
|
};
|
|
@@ -1170,27 +1383,81 @@ const partnerStatsEndpoint = ({ pluginConfig }) => ({
|
|
|
1170
1383
|
method: "get",
|
|
1171
1384
|
handler: partnerStatsHandler({ pluginConfig })
|
|
1172
1385
|
});
|
|
1173
|
-
|
|
1174
1386
|
//#endregion
|
|
1175
1387
|
//#region src/endpoints/validateCoupon.ts
|
|
1388
|
+
function relationId$2(value) {
|
|
1389
|
+
if (value == null) return null;
|
|
1390
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
1391
|
+
if (typeof value === "object" && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
function normalizeCode(value) {
|
|
1395
|
+
return typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
1396
|
+
}
|
|
1397
|
+
async function findByNormalizedCode({ payload, collection, normalizedCode }) {
|
|
1398
|
+
const normalizedQuery = await payload.find({
|
|
1399
|
+
collection,
|
|
1400
|
+
where: { normalizedCode: { equals: normalizedCode } },
|
|
1401
|
+
limit: 1
|
|
1402
|
+
});
|
|
1403
|
+
if (normalizedQuery?.docs?.length) return normalizedQuery.docs[0];
|
|
1404
|
+
const lowerQuery = await payload.find({
|
|
1405
|
+
collection,
|
|
1406
|
+
where: { code: { equals: normalizedCode.toLowerCase() } },
|
|
1407
|
+
limit: 1
|
|
1408
|
+
});
|
|
1409
|
+
if (lowerQuery?.docs?.length) return lowerQuery.docs[0];
|
|
1410
|
+
const upperQuery = await payload.find({
|
|
1411
|
+
collection,
|
|
1412
|
+
where: { code: { equals: normalizedCode.toUpperCase() } },
|
|
1413
|
+
limit: 1
|
|
1414
|
+
});
|
|
1415
|
+
if (upperQuery?.docs?.length) return upperQuery.docs[0];
|
|
1416
|
+
return (await payload.find({
|
|
1417
|
+
collection,
|
|
1418
|
+
where: { code: { equals: normalizedCode } },
|
|
1419
|
+
limit: 1
|
|
1420
|
+
}))?.docs?.[0] ?? null;
|
|
1421
|
+
}
|
|
1176
1422
|
const validateCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
1177
1423
|
const { payload } = req;
|
|
1178
|
-
const
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1424
|
+
const rawCode = req?.data?.code;
|
|
1425
|
+
const cartValue = req?.data?.cartValue;
|
|
1426
|
+
const cartID = req?.data?.cartID;
|
|
1427
|
+
const customerEmail = req?.data?.customerEmail;
|
|
1428
|
+
const normalizedCode = normalizeCode(rawCode);
|
|
1429
|
+
if (!normalizedCode) return Response.json({
|
|
1181
1430
|
success: false,
|
|
1182
1431
|
error: "Code is required"
|
|
1183
1432
|
}, { status: 400 });
|
|
1184
1433
|
try {
|
|
1185
|
-
if (pluginConfig.enableReferrals)
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1434
|
+
if (pluginConfig.enableReferrals) {
|
|
1435
|
+
if (!await Promise.resolve(pluginConfig.policies.canApplyReferral({
|
|
1436
|
+
req,
|
|
1437
|
+
user: req?.user,
|
|
1438
|
+
payload
|
|
1439
|
+
}))) return Response.json({
|
|
1440
|
+
success: false,
|
|
1441
|
+
error: "Forbidden"
|
|
1442
|
+
}, { status: 403 });
|
|
1443
|
+
return await validateReferralCode({
|
|
1444
|
+
payload,
|
|
1445
|
+
normalizedCode,
|
|
1446
|
+
cartID,
|
|
1447
|
+
pluginConfig
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
if (!await Promise.resolve(pluginConfig.policies.canApplyCoupon({
|
|
1451
|
+
req,
|
|
1452
|
+
user: req?.user,
|
|
1453
|
+
payload
|
|
1454
|
+
}))) return Response.json({
|
|
1455
|
+
success: false,
|
|
1456
|
+
error: "Forbidden"
|
|
1457
|
+
}, { status: 403 });
|
|
1458
|
+
return await validateCouponCode$1({
|
|
1192
1459
|
payload,
|
|
1193
|
-
|
|
1460
|
+
normalizedCode,
|
|
1194
1461
|
cartValue,
|
|
1195
1462
|
customerEmail,
|
|
1196
1463
|
pluginConfig
|
|
@@ -1203,27 +1470,17 @@ const validateCouponHandler = ({ pluginConfig }) => async (req) => {
|
|
|
1203
1470
|
}, { status: 500 });
|
|
1204
1471
|
}
|
|
1205
1472
|
};
|
|
1206
|
-
async function validateCouponCode$1({ payload,
|
|
1207
|
-
|
|
1473
|
+
async function validateCouponCode$1({ payload, normalizedCode, cartValue, customerEmail, pluginConfig }) {
|
|
1474
|
+
const fields = pluginConfig.integration.fields;
|
|
1475
|
+
const couponData = await findByNormalizedCode({
|
|
1476
|
+
payload,
|
|
1208
1477
|
collection: pluginConfig.collections.couponsSlug,
|
|
1209
|
-
|
|
1210
|
-
limit: 1
|
|
1478
|
+
normalizedCode
|
|
1211
1479
|
});
|
|
1212
|
-
if (!
|
|
1213
|
-
collection: pluginConfig.collections.couponsSlug,
|
|
1214
|
-
where: { code: { equals: code.toLowerCase() } },
|
|
1215
|
-
limit: 1
|
|
1216
|
-
});
|
|
1217
|
-
if (!coupon.docs.length) coupon = await payload.find({
|
|
1218
|
-
collection: pluginConfig.collections.couponsSlug,
|
|
1219
|
-
where: { code: { equals: code.toUpperCase() } },
|
|
1220
|
-
limit: 1
|
|
1221
|
-
});
|
|
1222
|
-
if (!coupon.docs.length) return Response.json({
|
|
1480
|
+
if (!couponData) return Response.json({
|
|
1223
1481
|
success: false,
|
|
1224
1482
|
error: "Invalid coupon code"
|
|
1225
1483
|
}, { status: 404 });
|
|
1226
|
-
const couponData = coupon.docs[0];
|
|
1227
1484
|
const now = /* @__PURE__ */ new Date();
|
|
1228
1485
|
const activeFrom = couponData.activeFrom ? new Date(couponData.activeFrom) : null;
|
|
1229
1486
|
const activeUntil = couponData.activeUntil ? new Date(couponData.activeUntil) : null;
|
|
@@ -1241,13 +1498,12 @@ async function validateCouponCode$1({ payload, code, cartValue, customerEmail, p
|
|
|
1241
1498
|
}, { status: 400 });
|
|
1242
1499
|
if (couponData.perCustomerLimit != null && couponData.perCustomerLimit > 0 && typeof customerEmail === "string" && customerEmail.trim().length > 0) {
|
|
1243
1500
|
const email = customerEmail.trim();
|
|
1244
|
-
const { ordersSlug, orderCustomerEmailField, orderPaymentStatusField, orderPaidStatusValue } = pluginConfig.orderIntegration;
|
|
1245
1501
|
if ((await payload.find({
|
|
1246
|
-
collection: ordersSlug,
|
|
1502
|
+
collection: pluginConfig.orderIntegration.ordersSlug,
|
|
1247
1503
|
where: { and: [
|
|
1248
|
-
{
|
|
1249
|
-
{ [orderCustomerEmailField]: { equals: email } },
|
|
1250
|
-
{ [orderPaymentStatusField]: { equals: orderPaidStatusValue } }
|
|
1504
|
+
{ [fields.orderAppliedCouponField]: { equals: couponData.id } },
|
|
1505
|
+
{ [pluginConfig.orderIntegration.orderCustomerEmailField]: { equals: email } },
|
|
1506
|
+
{ [pluginConfig.orderIntegration.orderPaymentStatusField]: { equals: pluginConfig.orderIntegration.orderPaidStatusValue } }
|
|
1251
1507
|
] },
|
|
1252
1508
|
limit: 0
|
|
1253
1509
|
})).totalDocs >= couponData.perCustomerLimit) return Response.json({
|
|
@@ -1289,27 +1545,18 @@ async function validateCouponCode$1({ payload, code, cartValue, customerEmail, p
|
|
|
1289
1545
|
currency: pluginConfig.defaultCurrency
|
|
1290
1546
|
});
|
|
1291
1547
|
}
|
|
1292
|
-
async function validateReferralCode({ payload,
|
|
1293
|
-
|
|
1548
|
+
async function validateReferralCode({ payload, normalizedCode, cartID, pluginConfig }) {
|
|
1549
|
+
const collections = pluginConfig.integration.collections;
|
|
1550
|
+
const resolvers = pluginConfig.integration.resolvers;
|
|
1551
|
+
const referralData = await findByNormalizedCode({
|
|
1552
|
+
payload,
|
|
1294
1553
|
collection: pluginConfig.collections.referralCodesSlug,
|
|
1295
|
-
|
|
1296
|
-
limit: 1
|
|
1554
|
+
normalizedCode
|
|
1297
1555
|
});
|
|
1298
|
-
if (!
|
|
1299
|
-
collection: pluginConfig.collections.referralCodesSlug,
|
|
1300
|
-
where: { code: { equals: code.toLowerCase() } },
|
|
1301
|
-
limit: 1
|
|
1302
|
-
});
|
|
1303
|
-
if (!referral.docs.length) referral = await payload.find({
|
|
1304
|
-
collection: pluginConfig.collections.referralCodesSlug,
|
|
1305
|
-
where: { code: { equals: code.toUpperCase() } },
|
|
1306
|
-
limit: 1
|
|
1307
|
-
});
|
|
1308
|
-
if (!referral.docs.length) return Response.json({
|
|
1556
|
+
if (!referralData) return Response.json({
|
|
1309
1557
|
success: false,
|
|
1310
1558
|
error: "Referral code not found"
|
|
1311
1559
|
}, { status: 404 });
|
|
1312
|
-
const referralData = referral.docs[0];
|
|
1313
1560
|
if (!referralData.isActive) return Response.json({
|
|
1314
1561
|
success: false,
|
|
1315
1562
|
error: "Referral code is not active"
|
|
@@ -1322,7 +1569,11 @@ async function validateReferralCode({ payload, code, cartID, pluginConfig }) {
|
|
|
1322
1569
|
success: false,
|
|
1323
1570
|
error: "Referral code usage limit exceeded"
|
|
1324
1571
|
}, { status: 400 });
|
|
1325
|
-
const programId =
|
|
1572
|
+
const programId = relationId$2(referralData.program);
|
|
1573
|
+
if (programId == null) return Response.json({
|
|
1574
|
+
success: false,
|
|
1575
|
+
error: "Referral program not found"
|
|
1576
|
+
}, { status: 404 });
|
|
1326
1577
|
const program = await payload.findByID({
|
|
1327
1578
|
collection: pluginConfig.collections.referralProgramsSlug,
|
|
1328
1579
|
id: programId
|
|
@@ -1332,11 +1583,11 @@ async function validateReferralCode({ payload, code, cartID, pluginConfig }) {
|
|
|
1332
1583
|
error: "Referral program is not active"
|
|
1333
1584
|
}, { status: 400 });
|
|
1334
1585
|
const cart = cartID ? await payload.findByID({
|
|
1335
|
-
collection:
|
|
1586
|
+
collection: collections.cartsSlug,
|
|
1336
1587
|
id: cartID,
|
|
1337
1588
|
depth: 2
|
|
1338
1589
|
}) : null;
|
|
1339
|
-
const cartTotal = cart ? cart
|
|
1590
|
+
const cartTotal = cart ? Number(resolvers.getCartTotal(cart)) || Number(resolvers.getCartSubtotal(cart)) || 0 : 0;
|
|
1340
1591
|
const minOrderAmount = getProgramMinimumOrderAmount({
|
|
1341
1592
|
program,
|
|
1342
1593
|
allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
|
|
@@ -1346,7 +1597,7 @@ async function validateReferralCode({ payload, code, cartID, pluginConfig }) {
|
|
|
1346
1597
|
error: `Minimum order value of ${minOrderAmount} ${pluginConfig.defaultCurrency} required for this referral program`
|
|
1347
1598
|
}, { status: 400 });
|
|
1348
1599
|
const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
|
|
1349
|
-
cartItems: cart
|
|
1600
|
+
cartItems: cart ? resolvers.getCartItems(cart) : [],
|
|
1350
1601
|
program,
|
|
1351
1602
|
currencyCode: pluginConfig.defaultCurrency,
|
|
1352
1603
|
cartTotal,
|
|
@@ -1371,152 +1622,428 @@ const validateCouponEndpoint = ({ pluginConfig }) => ({
|
|
|
1371
1622
|
method: "post",
|
|
1372
1623
|
handler: validateCouponHandler({ pluginConfig })
|
|
1373
1624
|
});
|
|
1374
|
-
|
|
1375
1625
|
//#endregion
|
|
1376
1626
|
//#region src/hooks/recalculateCart.ts
|
|
1627
|
+
function relationId$1(value) {
|
|
1628
|
+
if (value == null) return null;
|
|
1629
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
1630
|
+
if (typeof value === "object" && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
function readField$1(doc, field) {
|
|
1634
|
+
if (!doc || typeof doc !== "object") return void 0;
|
|
1635
|
+
return doc[field];
|
|
1636
|
+
}
|
|
1637
|
+
function writeField(doc, field, value) {
|
|
1638
|
+
doc[field] = value;
|
|
1639
|
+
}
|
|
1640
|
+
function clearCouponFields(target, fields) {
|
|
1641
|
+
writeField(target, fields.cartAppliedCouponField, null);
|
|
1642
|
+
writeField(target, fields.cartDiscountAmountField, 0);
|
|
1643
|
+
}
|
|
1644
|
+
function clearReferralFields(target, fields) {
|
|
1645
|
+
writeField(target, fields.cartAppliedReferralCodeField, null);
|
|
1646
|
+
writeField(target, fields.cartPartnerCommissionField, 0);
|
|
1647
|
+
writeField(target, fields.cartCustomerDiscountField, 0);
|
|
1648
|
+
}
|
|
1377
1649
|
const recalculateCartHook = (pluginConfig) => async ({ data, req, originalDoc }) => {
|
|
1378
1650
|
if (!req.payload) return data;
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1651
|
+
const integration = pluginConfig.integration || {};
|
|
1652
|
+
const collections = integration.collections || {
|
|
1653
|
+
cartsSlug: "carts",
|
|
1654
|
+
ordersSlug: "orders",
|
|
1655
|
+
productsSlug: "products",
|
|
1656
|
+
usersSlug: "users",
|
|
1657
|
+
categoriesSlug: "categories",
|
|
1658
|
+
tagsSlug: "tags"
|
|
1386
1659
|
};
|
|
1387
|
-
const
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1660
|
+
const fields = integration.fields || {
|
|
1661
|
+
cartItemsField: "items",
|
|
1662
|
+
cartSubtotalField: "subtotal",
|
|
1663
|
+
cartTotalField: "total",
|
|
1664
|
+
cartAppliedCouponField: "appliedCoupon",
|
|
1665
|
+
cartAppliedReferralCodeField: "appliedReferralCode",
|
|
1666
|
+
cartDiscountAmountField: "discountAmount",
|
|
1667
|
+
cartCustomerDiscountField: "customerDiscount",
|
|
1668
|
+
cartPartnerCommissionField: "partnerCommission",
|
|
1669
|
+
orderAppliedCouponField: "appliedCoupon",
|
|
1670
|
+
orderAppliedReferralCodeField: "appliedReferralCode",
|
|
1671
|
+
orderDiscountAmountField: "discountAmount",
|
|
1672
|
+
orderCustomerDiscountField: "customerDiscount",
|
|
1673
|
+
orderPartnerCommissionField: "partnerCommission",
|
|
1674
|
+
orderCustomerEmailField: "customerEmail",
|
|
1675
|
+
orderPaymentStatusField: "paymentStatus",
|
|
1676
|
+
orderCreatedAtField: "createdAt",
|
|
1677
|
+
productPriceField: "price",
|
|
1678
|
+
productCurrencyCodeField: "currencyCode"
|
|
1679
|
+
};
|
|
1680
|
+
const resolvers = integration.resolvers || {
|
|
1681
|
+
getUserID: ({ user }) => {
|
|
1682
|
+
if (!user || typeof user !== "object") return null;
|
|
1683
|
+
const id = user.id;
|
|
1684
|
+
if (typeof id === "string" || typeof id === "number") return id;
|
|
1685
|
+
return null;
|
|
1686
|
+
},
|
|
1687
|
+
getCartItems: (cart) => {
|
|
1688
|
+
if (!cart || typeof cart !== "object") return [];
|
|
1689
|
+
const value = cart[fields.cartItemsField];
|
|
1690
|
+
return Array.isArray(value) ? value : [];
|
|
1691
|
+
},
|
|
1692
|
+
getCartSubtotal: (cart) => {
|
|
1693
|
+
if (!cart || typeof cart !== "object") return 0;
|
|
1694
|
+
const value = cart[fields.cartSubtotalField];
|
|
1695
|
+
return typeof value === "number" ? value : 0;
|
|
1696
|
+
},
|
|
1697
|
+
getCartTotal: (cart) => {
|
|
1698
|
+
if (!cart || typeof cart !== "object") return 0;
|
|
1699
|
+
const value = cart[fields.cartTotalField];
|
|
1700
|
+
return typeof value === "number" ? value : 0;
|
|
1701
|
+
},
|
|
1702
|
+
isOrderPaid: (_order) => false,
|
|
1703
|
+
getProductUnitPrice: ({ item, product, variant, currencyCode }) => {
|
|
1704
|
+
if (item && typeof item === "object") {
|
|
1705
|
+
const itemPrice = item.price;
|
|
1706
|
+
if (typeof itemPrice === "number") return itemPrice;
|
|
1707
|
+
const unitPrice = item.unitPrice;
|
|
1708
|
+
if (typeof unitPrice === "number") return unitPrice;
|
|
1709
|
+
}
|
|
1710
|
+
const readPrice = (entity, code) => {
|
|
1711
|
+
if (!entity || typeof entity !== "object") return void 0;
|
|
1712
|
+
const map = entity;
|
|
1713
|
+
if (code && typeof code === "string") {
|
|
1714
|
+
const value = map[`priceIn${code.toUpperCase()}`];
|
|
1715
|
+
if (typeof value === "number") return value;
|
|
1716
|
+
}
|
|
1717
|
+
const base = map.price;
|
|
1718
|
+
return typeof base === "number" ? base : void 0;
|
|
1398
1719
|
};
|
|
1720
|
+
return readPrice(variant, currencyCode) ?? readPrice(product, currencyCode) ?? 0;
|
|
1399
1721
|
}
|
|
1400
|
-
return data;
|
|
1401
|
-
}
|
|
1402
|
-
const getRelationID = (value) => {
|
|
1403
|
-
if (value === null || value === void 0) return void 0;
|
|
1404
|
-
if (typeof value === "object") return value.id;
|
|
1405
|
-
if (typeof value === "string" || typeof value === "number") return value;
|
|
1406
1722
|
};
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1409
|
-
const
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1723
|
+
const mutableData = data || {};
|
|
1724
|
+
const original = originalDoc || {};
|
|
1725
|
+
const effectiveItems = readField$1(mutableData, fields.cartItemsField) ?? readField$1(original, fields.cartItemsField) ?? [];
|
|
1726
|
+
const effectiveAppliedReferral = readField$1(mutableData, fields.cartAppliedReferralCodeField) !== void 0 ? readField$1(mutableData, fields.cartAppliedReferralCodeField) : readField$1(original, fields.cartAppliedReferralCodeField);
|
|
1727
|
+
const effectiveAppliedCoupon = readField$1(mutableData, fields.cartAppliedCouponField) !== void 0 ? readField$1(mutableData, fields.cartAppliedCouponField) : readField$1(original, fields.cartAppliedCouponField);
|
|
1728
|
+
if (!Array.isArray(effectiveItems) || effectiveItems.length === 0) {
|
|
1729
|
+
clearReferralFields(mutableData, fields);
|
|
1730
|
+
clearCouponFields(mutableData, fields);
|
|
1731
|
+
writeField(mutableData, fields.cartTotalField, 0);
|
|
1732
|
+
return mutableData;
|
|
1733
|
+
}
|
|
1734
|
+
const getRelationID = (value) => relationId$1(value);
|
|
1735
|
+
const productIds = effectiveItems.map((item) => getRelationID(item?.product)).filter((id) => id != null);
|
|
1736
|
+
let productsMap = /* @__PURE__ */ new Map();
|
|
1737
|
+
if (productIds.length > 0) {
|
|
1738
|
+
const productsQuery = await req.payload.find({
|
|
1739
|
+
collection: collections.productsSlug,
|
|
1740
|
+
where: { id: { in: productIds } },
|
|
1741
|
+
limit: productIds.length
|
|
1742
|
+
});
|
|
1743
|
+
productsMap = new Map((productsQuery?.docs || []).map((p) => [String(p.id), p]));
|
|
1744
|
+
}
|
|
1415
1745
|
let calculatedSubtotal = 0;
|
|
1416
1746
|
const enrichedItems = effectiveItems.map((item) => {
|
|
1417
|
-
const
|
|
1418
|
-
const product =
|
|
1419
|
-
const
|
|
1747
|
+
const pid = getRelationID(item?.product);
|
|
1748
|
+
const product = pid != null ? productsMap.get(String(pid)) || {} : {};
|
|
1749
|
+
const variant = typeof item?.variant === "object" ? item.variant : void 0;
|
|
1750
|
+
const unitPrice = Number(resolvers.getProductUnitPrice({
|
|
1420
1751
|
item,
|
|
1421
1752
|
product,
|
|
1422
|
-
variant
|
|
1753
|
+
variant,
|
|
1423
1754
|
currencyCode: pluginConfig.defaultCurrency
|
|
1424
|
-
});
|
|
1425
|
-
|
|
1755
|
+
}));
|
|
1756
|
+
const quantity = typeof item?.quantity === "number" && Number.isFinite(item.quantity) ? item.quantity : 1;
|
|
1757
|
+
const safeUnitPrice = Number.isFinite(unitPrice) ? unitPrice : 0;
|
|
1758
|
+
calculatedSubtotal += safeUnitPrice * quantity;
|
|
1426
1759
|
return {
|
|
1427
1760
|
...item,
|
|
1428
1761
|
product,
|
|
1429
|
-
price:
|
|
1762
|
+
price: safeUnitPrice,
|
|
1763
|
+
quantity
|
|
1430
1764
|
};
|
|
1431
1765
|
});
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
return data;
|
|
1439
|
-
}
|
|
1440
|
-
const referralQuery = await req.payload.find({
|
|
1766
|
+
writeField(mutableData, fields.cartSubtotalField, roundTo2(calculatedSubtotal));
|
|
1767
|
+
let customerDiscount = 0;
|
|
1768
|
+
let couponDiscount = 0;
|
|
1769
|
+
const appliedReferralID = relationId$1(effectiveAppliedReferral);
|
|
1770
|
+
if (pluginConfig.enableReferrals && appliedReferralID != null) {
|
|
1771
|
+
const referralCode = (await req.payload.find({
|
|
1441
1772
|
collection: pluginConfig.collections.referralCodesSlug,
|
|
1442
|
-
where: { id: { equals:
|
|
1773
|
+
where: { id: { equals: appliedReferralID } },
|
|
1443
1774
|
limit: 1,
|
|
1444
1775
|
depth: 1
|
|
1445
|
-
});
|
|
1446
|
-
if (
|
|
1447
|
-
|
|
1448
|
-
const programId =
|
|
1449
|
-
const program = typeof referralCode.program === "object" ? referralCode.program : programId ? await req.payload.findByID({
|
|
1776
|
+
}))?.docs?.[0];
|
|
1777
|
+
if (!referralCode || referralCode.isActive === false) clearReferralFields(mutableData, fields);
|
|
1778
|
+
else {
|
|
1779
|
+
const programId = relationId$1(referralCode.program);
|
|
1780
|
+
const program = typeof referralCode.program === "object" ? referralCode.program : programId != null ? await req.payload.findByID({
|
|
1450
1781
|
collection: pluginConfig.collections.referralProgramsSlug,
|
|
1451
1782
|
id: programId
|
|
1452
1783
|
}) : null;
|
|
1453
|
-
if (program)
|
|
1784
|
+
if (!program || program.isActive === false) clearReferralFields(mutableData, fields);
|
|
1785
|
+
else {
|
|
1454
1786
|
const minOrderAmount = getProgramMinimumOrderAmount({
|
|
1455
1787
|
program,
|
|
1456
1788
|
allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
|
|
1457
1789
|
});
|
|
1458
|
-
if (typeof minOrderAmount === "number" && calculatedSubtotal < minOrderAmount)
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1790
|
+
if (typeof minOrderAmount === "number" && calculatedSubtotal < minOrderAmount) clearReferralFields(mutableData, fields);
|
|
1791
|
+
else {
|
|
1792
|
+
const result = calculateCommissionAndDiscount({
|
|
1793
|
+
cartItems: enrichedItems,
|
|
1794
|
+
program,
|
|
1795
|
+
currencyCode: pluginConfig.defaultCurrency,
|
|
1796
|
+
cartTotal: calculatedSubtotal,
|
|
1797
|
+
allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
|
|
1798
|
+
});
|
|
1799
|
+
const roundedPartnerCommission = roundTo2(result.partnerCommission);
|
|
1800
|
+
const roundedCustomerDiscount = roundTo2(Math.max(0, result.customerDiscount));
|
|
1801
|
+
writeField(mutableData, fields.cartPartnerCommissionField, roundedPartnerCommission);
|
|
1802
|
+
writeField(mutableData, fields.cartCustomerDiscountField, roundedCustomerDiscount);
|
|
1803
|
+
customerDiscount = roundedCustomerDiscount;
|
|
1464
1804
|
}
|
|
1465
|
-
const { partnerCommission, customerDiscount } = calculateCommissionAndDiscount({
|
|
1466
|
-
cartItems: enrichedItems,
|
|
1467
|
-
program,
|
|
1468
|
-
currencyCode: pluginConfig.defaultCurrency,
|
|
1469
|
-
cartTotal: calculatedSubtotal,
|
|
1470
|
-
allowedTotalCommissionTypes: pluginConfig.referralConfig.allowedTotalCommissionTypes
|
|
1471
|
-
});
|
|
1472
|
-
const roundedCustomerDiscount = roundTo2(customerDiscount);
|
|
1473
|
-
data.partnerCommission = roundTo2(partnerCommission);
|
|
1474
|
-
data.customerDiscount = roundedCustomerDiscount;
|
|
1475
|
-
data.total = Math.max(0, calculatedSubtotal - roundedCustomerDiscount);
|
|
1476
|
-
} else {
|
|
1477
|
-
data.appliedReferralCode = null;
|
|
1478
|
-
data.partnerCommission = 0;
|
|
1479
|
-
data.customerDiscount = 0;
|
|
1480
|
-
data.total = calculatedSubtotal;
|
|
1481
1805
|
}
|
|
1482
1806
|
}
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
const
|
|
1807
|
+
} else if (readField$1(mutableData, fields.cartAppliedReferralCodeField) === null) clearReferralFields(mutableData, fields);
|
|
1808
|
+
const appliedCouponID = relationId$1(effectiveAppliedCoupon);
|
|
1809
|
+
const canUseCouponWithReferral = !pluginConfig.enableReferrals || pluginConfig.referralConfig.allowBothSystems || relationId$1(readField$1(mutableData, fields.cartAppliedReferralCodeField)) == null;
|
|
1810
|
+
if (appliedCouponID != null && canUseCouponWithReferral) {
|
|
1811
|
+
const coupon = (await req.payload.find({
|
|
1488
1812
|
collection: pluginConfig.collections.couponsSlug,
|
|
1489
1813
|
where: { id: { equals: appliedCouponID } },
|
|
1490
1814
|
limit: 1
|
|
1815
|
+
}))?.docs?.[0];
|
|
1816
|
+
if (!coupon) clearCouponFields(mutableData, fields);
|
|
1817
|
+
else {
|
|
1818
|
+
const now = /* @__PURE__ */ new Date();
|
|
1819
|
+
const activeFrom = coupon.activeFrom ? new Date(coupon.activeFrom) : null;
|
|
1820
|
+
const activeUntil = coupon.activeUntil ? new Date(coupon.activeUntil) : null;
|
|
1821
|
+
const isValidDate = (!activeFrom || now >= activeFrom) && (!activeUntil || now <= activeUntil);
|
|
1822
|
+
const underUsage = !coupon.usageLimit || Number(coupon.usageCount || 0) < Number(coupon.usageLimit || 0);
|
|
1823
|
+
if (!isValidDate || !underUsage) clearCouponFields(mutableData, fields);
|
|
1824
|
+
else {
|
|
1825
|
+
couponDiscount = roundTo2(calculateCouponDiscount({
|
|
1826
|
+
coupon,
|
|
1827
|
+
cartTotal: calculatedSubtotal
|
|
1828
|
+
}));
|
|
1829
|
+
writeField(mutableData, fields.cartDiscountAmountField, couponDiscount);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
} else if (readField$1(mutableData, fields.cartAppliedCouponField) === null) {
|
|
1833
|
+
clearCouponFields(mutableData, fields);
|
|
1834
|
+
writeField(mutableData, fields.cartCustomerDiscountField, 0);
|
|
1835
|
+
writeField(mutableData, fields.cartPartnerCommissionField, 0);
|
|
1836
|
+
writeField(mutableData, fields.cartTotalField, roundTo2(Number(resolvers.getCartSubtotal(mutableData)) || calculatedSubtotal));
|
|
1837
|
+
return mutableData;
|
|
1838
|
+
}
|
|
1839
|
+
const nextTotal = roundTo2(Math.max(0, calculatedSubtotal - customerDiscount - couponDiscount));
|
|
1840
|
+
writeField(mutableData, fields.cartTotalField, nextTotal);
|
|
1841
|
+
return mutableData;
|
|
1842
|
+
};
|
|
1843
|
+
//#endregion
|
|
1844
|
+
//#region src/utilities/recordCouponUsageForOrder.ts
|
|
1845
|
+
const USAGE_MARKER_FIELD = "couponUsageRecordedAt";
|
|
1846
|
+
function relationId(value) {
|
|
1847
|
+
if (value == null) return null;
|
|
1848
|
+
if (typeof value === "string" || typeof value === "number") return value;
|
|
1849
|
+
if (typeof value === "object" && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
|
|
1850
|
+
return null;
|
|
1851
|
+
}
|
|
1852
|
+
function asNumber(value) {
|
|
1853
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1854
|
+
}
|
|
1855
|
+
function readField(doc, field) {
|
|
1856
|
+
if (!doc || typeof doc !== "object") return void 0;
|
|
1857
|
+
return doc[field];
|
|
1858
|
+
}
|
|
1859
|
+
async function markOrderUsageRecorded({ payload, pluginConfig, orderID }) {
|
|
1860
|
+
const ordersSlug = pluginConfig.integration.collections.ordersSlug;
|
|
1861
|
+
const latestOrder = await payload.findByID({
|
|
1862
|
+
collection: ordersSlug,
|
|
1863
|
+
id: orderID,
|
|
1864
|
+
depth: 0
|
|
1865
|
+
});
|
|
1866
|
+
if (!latestOrder) return false;
|
|
1867
|
+
if (Boolean(readField(latestOrder, USAGE_MARKER_FIELD))) return false;
|
|
1868
|
+
await payload.update({
|
|
1869
|
+
collection: ordersSlug,
|
|
1870
|
+
id: orderID,
|
|
1871
|
+
data: { [USAGE_MARKER_FIELD]: (/* @__PURE__ */ new Date()).toISOString() }
|
|
1872
|
+
});
|
|
1873
|
+
return true;
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Record coupon and referral usage when an order is placed successfully.
|
|
1877
|
+
* This function is idempotent and integration-field aware.
|
|
1878
|
+
*
|
|
1879
|
+
* Behavior:
|
|
1880
|
+
* - Uses configured order field names from `pluginConfig.integration.fields`
|
|
1881
|
+
* - Uses configured paid-order resolver (`integration.resolvers.isOrderPaid`)
|
|
1882
|
+
* - Marks the order with `couponUsageRecordedAt` before mutating counters to avoid duplicate counting
|
|
1883
|
+
* - If marker already exists, returns `alreadyRecorded: true` and performs no increments
|
|
1884
|
+
*/
|
|
1885
|
+
async function recordCouponUsageForOrder(payload, order, pluginConfig) {
|
|
1886
|
+
const result = {
|
|
1887
|
+
recordedCoupon: false,
|
|
1888
|
+
recordedReferral: false,
|
|
1889
|
+
alreadyRecorded: false
|
|
1890
|
+
};
|
|
1891
|
+
const orderID = order.id;
|
|
1892
|
+
if (orderID == null) return result;
|
|
1893
|
+
if (!await Promise.resolve(pluginConfig.integration.resolvers.isOrderPaid(order))) return result;
|
|
1894
|
+
if (!await Promise.resolve(pluginConfig.policies.canRecordOrderUsage({
|
|
1895
|
+
req: {},
|
|
1896
|
+
user: void 0,
|
|
1897
|
+
payload,
|
|
1898
|
+
order
|
|
1899
|
+
}))) return result;
|
|
1900
|
+
if (!await markOrderUsageRecorded({
|
|
1901
|
+
payload,
|
|
1902
|
+
pluginConfig,
|
|
1903
|
+
orderID
|
|
1904
|
+
})) {
|
|
1905
|
+
result.alreadyRecorded = true;
|
|
1906
|
+
return result;
|
|
1907
|
+
}
|
|
1908
|
+
const fields = pluginConfig.integration.fields;
|
|
1909
|
+
const couponField = fields.orderAppliedCouponField;
|
|
1910
|
+
const referralField = fields.orderAppliedReferralCodeField;
|
|
1911
|
+
const partnerCommissionField = fields.orderPartnerCommissionField;
|
|
1912
|
+
const couponId = relationId(readField(order, couponField));
|
|
1913
|
+
const referralCodeId = relationId(readField(order, referralField));
|
|
1914
|
+
const commission = asNumber(readField(order, partnerCommissionField));
|
|
1915
|
+
if (couponId) {
|
|
1916
|
+
const coupon = await payload.findByID({
|
|
1917
|
+
collection: pluginConfig.collections.couponsSlug,
|
|
1918
|
+
id: couponId,
|
|
1919
|
+
depth: 0
|
|
1920
|
+
});
|
|
1921
|
+
if (coupon) {
|
|
1922
|
+
const currentUsage = asNumber(coupon.usageCount);
|
|
1923
|
+
await payload.update({
|
|
1924
|
+
collection: pluginConfig.collections.couponsSlug,
|
|
1925
|
+
id: couponId,
|
|
1926
|
+
data: { usageCount: currentUsage + 1 }
|
|
1927
|
+
});
|
|
1928
|
+
result.recordedCoupon = true;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
if (referralCodeId) {
|
|
1932
|
+
const referralCode = await payload.findByID({
|
|
1933
|
+
collection: pluginConfig.collections.referralCodesSlug,
|
|
1934
|
+
id: referralCodeId,
|
|
1935
|
+
depth: 0
|
|
1491
1936
|
});
|
|
1492
|
-
if (
|
|
1493
|
-
const
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1937
|
+
if (referralCode) {
|
|
1938
|
+
const rc = referralCode;
|
|
1939
|
+
const currentTotal = asNumber(rc.totalEarnings);
|
|
1940
|
+
const currentPending = asNumber(rc.pendingEarnings);
|
|
1941
|
+
const currentUsage = asNumber(rc.usageCount);
|
|
1942
|
+
const currentSuccessful = asNumber(rc.successfulReferralsCount);
|
|
1943
|
+
await payload.update({
|
|
1944
|
+
collection: pluginConfig.collections.referralCodesSlug,
|
|
1945
|
+
id: referralCodeId,
|
|
1946
|
+
data: {
|
|
1947
|
+
usageCount: currentUsage + 1,
|
|
1948
|
+
successfulReferralsCount: currentSuccessful + 1,
|
|
1949
|
+
totalEarnings: currentTotal + commission,
|
|
1950
|
+
pendingEarnings: currentPending + commission
|
|
1951
|
+
}
|
|
1497
1952
|
});
|
|
1498
|
-
|
|
1499
|
-
const currentDiscount = data.customerDiscount || 0;
|
|
1500
|
-
data.total = Math.max(0, calculatedSubtotal - currentDiscount - discountAmount);
|
|
1953
|
+
result.recordedReferral = true;
|
|
1501
1954
|
}
|
|
1502
1955
|
}
|
|
1503
|
-
return
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1956
|
+
return result;
|
|
1957
|
+
}
|
|
1506
1958
|
//#endregion
|
|
1507
1959
|
//#region src/utilities/sanitizePluginConfig.ts
|
|
1960
|
+
const toCleanStringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean) : [];
|
|
1961
|
+
const toBoolean = (value, fallback) => {
|
|
1962
|
+
if (typeof value === "boolean") return value;
|
|
1963
|
+
if (typeof value === "number") {
|
|
1964
|
+
if (value === 1) return true;
|
|
1965
|
+
if (value === 0) return false;
|
|
1966
|
+
}
|
|
1967
|
+
if (typeof value === "string") {
|
|
1968
|
+
const normalized = value.trim().toLowerCase();
|
|
1969
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on") return true;
|
|
1970
|
+
if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "off") return false;
|
|
1971
|
+
}
|
|
1972
|
+
return fallback;
|
|
1973
|
+
};
|
|
1508
1974
|
const sanitizePluginConfig = ({ pluginConfig }) => {
|
|
1509
1975
|
const roleConfig = {
|
|
1510
|
-
roleFieldPaths:
|
|
1511
|
-
adminRoleValues:
|
|
1512
|
-
partnerRoleValues:
|
|
1976
|
+
roleFieldPaths: toCleanStringArray(pluginConfig?.roleConfig?.roleFieldPaths).length > 0 ? toCleanStringArray(pluginConfig?.roleConfig?.roleFieldPaths) : ["role", "roles"],
|
|
1977
|
+
adminRoleValues: toCleanStringArray(pluginConfig?.roleConfig?.adminRoleValues).length > 0 ? toCleanStringArray(pluginConfig?.roleConfig?.adminRoleValues) : ["admin"],
|
|
1978
|
+
partnerRoleValues: toCleanStringArray(pluginConfig?.roleConfig?.partnerRoleValues).length > 0 ? toCleanStringArray(pluginConfig?.roleConfig?.partnerRoleValues) : ["partner"],
|
|
1513
1979
|
customRoleResolver: typeof pluginConfig?.roleConfig?.customRoleResolver === "function" ? pluginConfig.roleConfig.customRoleResolver : void 0
|
|
1514
1980
|
};
|
|
1515
1981
|
const normalizedAllowedTotalCommissionTypes = Array.isArray(pluginConfig?.referralConfig?.allowedTotalCommissionTypes) ? [...new Set(pluginConfig.referralConfig.allowedTotalCommissionTypes.filter((value) => value === "fixed" || value === "percentage"))] : [];
|
|
1982
|
+
const integrationCollections = {
|
|
1983
|
+
cartsSlug: typeof pluginConfig?.integration?.collections?.cartsSlug === "string" && pluginConfig.integration.collections.cartsSlug.trim().length > 0 ? pluginConfig.integration.collections.cartsSlug.trim() : "carts",
|
|
1984
|
+
ordersSlug: typeof pluginConfig?.integration?.collections?.ordersSlug === "string" && pluginConfig.integration.collections.ordersSlug.trim().length > 0 ? pluginConfig.integration.collections.ordersSlug.trim() : "orders",
|
|
1985
|
+
productsSlug: typeof pluginConfig?.integration?.collections?.productsSlug === "string" && pluginConfig.integration.collections.productsSlug.trim().length > 0 ? pluginConfig.integration.collections.productsSlug.trim() : "products",
|
|
1986
|
+
usersSlug: typeof pluginConfig?.integration?.collections?.usersSlug === "string" && pluginConfig.integration.collections.usersSlug.trim().length > 0 ? pluginConfig.integration.collections.usersSlug.trim() : "users",
|
|
1987
|
+
categoriesSlug: typeof pluginConfig?.integration?.collections?.categoriesSlug === "string" && pluginConfig.integration.collections.categoriesSlug.trim().length > 0 ? pluginConfig.integration.collections.categoriesSlug.trim() : "categories",
|
|
1988
|
+
tagsSlug: typeof pluginConfig?.integration?.collections?.tagsSlug === "string" && pluginConfig.integration.collections.tagsSlug.trim().length > 0 ? pluginConfig.integration.collections.tagsSlug.trim() : "tags"
|
|
1989
|
+
};
|
|
1990
|
+
const integrationFields = {
|
|
1991
|
+
cartItemsField: pluginConfig?.integration?.fields?.cartItemsField?.trim() || "items",
|
|
1992
|
+
cartSubtotalField: pluginConfig?.integration?.fields?.cartSubtotalField?.trim() || "subtotal",
|
|
1993
|
+
cartTotalField: pluginConfig?.integration?.fields?.cartTotalField?.trim() || "total",
|
|
1994
|
+
cartAppliedCouponField: pluginConfig?.integration?.fields?.cartAppliedCouponField?.trim() || "appliedCoupon",
|
|
1995
|
+
cartAppliedReferralCodeField: pluginConfig?.integration?.fields?.cartAppliedReferralCodeField?.trim() || "appliedReferralCode",
|
|
1996
|
+
cartDiscountAmountField: pluginConfig?.integration?.fields?.cartDiscountAmountField?.trim() || "discountAmount",
|
|
1997
|
+
cartCustomerDiscountField: pluginConfig?.integration?.fields?.cartCustomerDiscountField?.trim() || "customerDiscount",
|
|
1998
|
+
cartPartnerCommissionField: pluginConfig?.integration?.fields?.cartPartnerCommissionField?.trim() || "partnerCommission",
|
|
1999
|
+
orderAppliedCouponField: pluginConfig?.integration?.fields?.orderAppliedCouponField?.trim() || "appliedCoupon",
|
|
2000
|
+
orderAppliedReferralCodeField: pluginConfig?.integration?.fields?.orderAppliedReferralCodeField?.trim() || "appliedReferralCode",
|
|
2001
|
+
orderDiscountAmountField: pluginConfig?.integration?.fields?.orderDiscountAmountField?.trim() || "discountAmount",
|
|
2002
|
+
orderCustomerDiscountField: pluginConfig?.integration?.fields?.orderCustomerDiscountField?.trim() || "customerDiscount",
|
|
2003
|
+
orderPartnerCommissionField: pluginConfig?.integration?.fields?.orderPartnerCommissionField?.trim() || "partnerCommission",
|
|
2004
|
+
orderCustomerEmailField: pluginConfig?.integration?.fields?.orderCustomerEmailField?.trim() || pluginConfig?.orderIntegration?.orderCustomerEmailField?.trim() || "customerEmail",
|
|
2005
|
+
orderPaymentStatusField: pluginConfig?.integration?.fields?.orderPaymentStatusField?.trim() || pluginConfig?.orderIntegration?.orderPaymentStatusField?.trim() || "paymentStatus",
|
|
2006
|
+
orderCreatedAtField: pluginConfig?.integration?.fields?.orderCreatedAtField?.trim() || "createdAt",
|
|
2007
|
+
productPriceField: pluginConfig?.integration?.fields?.productPriceField?.trim() || "price",
|
|
2008
|
+
productCurrencyCodeField: pluginConfig?.integration?.fields?.productCurrencyCodeField?.trim() || "currencyCode"
|
|
2009
|
+
};
|
|
2010
|
+
const integrationResolvers = {
|
|
2011
|
+
getUserID: pluginConfig?.integration?.resolvers?.getUserID || (({ user }) => {
|
|
2012
|
+
if (!user || typeof user !== "object") return null;
|
|
2013
|
+
const id = user.id;
|
|
2014
|
+
if (typeof id === "string" || typeof id === "number") return id;
|
|
2015
|
+
return null;
|
|
2016
|
+
}),
|
|
2017
|
+
getCartItems: pluginConfig?.integration?.resolvers?.getCartItems || ((cart) => {
|
|
2018
|
+
if (!cart || typeof cart !== "object") return [];
|
|
2019
|
+
const value = cart[integrationFields.cartItemsField];
|
|
2020
|
+
return Array.isArray(value) ? value : [];
|
|
2021
|
+
}),
|
|
2022
|
+
getCartSubtotal: pluginConfig?.integration?.resolvers?.getCartSubtotal || ((cart) => {
|
|
2023
|
+
if (!cart || typeof cart !== "object") return 0;
|
|
2024
|
+
const value = cart[integrationFields.cartSubtotalField];
|
|
2025
|
+
return typeof value === "number" ? value : 0;
|
|
2026
|
+
}),
|
|
2027
|
+
getCartTotal: pluginConfig?.integration?.resolvers?.getCartTotal || ((cart) => {
|
|
2028
|
+
if (!cart || typeof cart !== "object") return 0;
|
|
2029
|
+
const value = cart[integrationFields.cartTotalField];
|
|
2030
|
+
return typeof value === "number" ? value : 0;
|
|
2031
|
+
}),
|
|
2032
|
+
isOrderPaid: pluginConfig?.integration?.resolvers?.isOrderPaid || ((order) => {
|
|
2033
|
+
if (!order || typeof order !== "object") return false;
|
|
2034
|
+
return order[integrationFields.orderPaymentStatusField] === (pluginConfig?.orderIntegration?.orderPaidStatusValue ?? "paid");
|
|
2035
|
+
}),
|
|
2036
|
+
getProductUnitPrice: pluginConfig?.integration?.resolvers?.getProductUnitPrice || ((args) => getCartItemUnitPrice({
|
|
2037
|
+
item: args.item ?? null,
|
|
2038
|
+
product: args.product ?? null,
|
|
2039
|
+
variant: args.variant ?? null,
|
|
2040
|
+
currencyCode: args.currencyCode || "USD"
|
|
2041
|
+
}))
|
|
2042
|
+
};
|
|
1516
2043
|
return {
|
|
1517
|
-
enabled:
|
|
1518
|
-
enableReferrals:
|
|
1519
|
-
allowStackWithOtherCoupons:
|
|
2044
|
+
enabled: toBoolean(pluginConfig?.enabled, true),
|
|
2045
|
+
enableReferrals: toBoolean(pluginConfig?.enableReferrals, false),
|
|
2046
|
+
allowStackWithOtherCoupons: toBoolean(pluginConfig?.allowStackWithOtherCoupons, false),
|
|
1520
2047
|
defaultCurrency: typeof pluginConfig?.defaultCurrency === "string" && pluginConfig.defaultCurrency.length > 0 && pluginConfig.defaultCurrency.length <= 3 ? pluginConfig.defaultCurrency : "USD",
|
|
1521
2048
|
collections: {
|
|
1522
2049
|
couponsSlug: typeof pluginConfig?.collections?.couponsSlug === "string" && pluginConfig.collections.couponsSlug.trim().length > 0 && pluginConfig.collections.couponsSlug.length <= 100 ? pluginConfig.collections.couponsSlug : "coupons",
|
|
@@ -1525,10 +2052,10 @@ const sanitizePluginConfig = ({ pluginConfig }) => {
|
|
|
1525
2052
|
referralPartnersSlug: typeof pluginConfig?.collections?.referralPartnersSlug === "string" && pluginConfig.collections.referralPartnersSlug.trim().length > 0 && pluginConfig.collections.referralPartnersSlug.length <= 100 ? pluginConfig.collections.referralPartnersSlug : "referral-partners"
|
|
1526
2053
|
},
|
|
1527
2054
|
endpoints: {
|
|
1528
|
-
applyCoupon: typeof pluginConfig?.endpoints?.applyCoupon === "string" && pluginConfig.endpoints.applyCoupon.trim().length > 0 ? pluginConfig.endpoints.applyCoupon : "/coupons/apply",
|
|
1529
|
-
validateCoupon: typeof pluginConfig?.endpoints?.validateCoupon === "string" && pluginConfig.endpoints.validateCoupon.trim().length > 0 ? pluginConfig.endpoints.validateCoupon : "/coupons/validate",
|
|
1530
|
-
partnerStats: typeof pluginConfig?.endpoints?.partnerStats === "string" && pluginConfig.endpoints.partnerStats.trim().length > 0 ? pluginConfig.endpoints.partnerStats : "/referrals/partner-stats",
|
|
1531
|
-
recordOrderUsage: typeof pluginConfig?.endpoints?.recordOrderUsage === "string" && pluginConfig.endpoints.recordOrderUsage.trim().length > 0 ? pluginConfig.endpoints.recordOrderUsage : "/coupons/record-order-usage"
|
|
2055
|
+
applyCoupon: typeof pluginConfig?.endpoints?.applyCoupon === "string" && pluginConfig.endpoints.applyCoupon.trim().length > 0 ? pluginConfig.endpoints.applyCoupon.trim() : "/coupons/apply",
|
|
2056
|
+
validateCoupon: typeof pluginConfig?.endpoints?.validateCoupon === "string" && pluginConfig.endpoints.validateCoupon.trim().length > 0 ? pluginConfig.endpoints.validateCoupon.trim() : "/coupons/validate",
|
|
2057
|
+
partnerStats: typeof pluginConfig?.endpoints?.partnerStats === "string" && pluginConfig.endpoints.partnerStats.trim().length > 0 ? pluginConfig.endpoints.partnerStats.trim() : "/referrals/partner-stats",
|
|
2058
|
+
recordOrderUsage: typeof pluginConfig?.endpoints?.recordOrderUsage === "string" && pluginConfig.endpoints.recordOrderUsage.trim().length > 0 ? pluginConfig.endpoints.recordOrderUsage.trim() : "/coupons/record-order-usage"
|
|
1532
2059
|
},
|
|
1533
2060
|
autoIntegrate: pluginConfig?.autoIntegrate !== false,
|
|
1534
2061
|
access: {
|
|
@@ -1543,6 +2070,23 @@ const sanitizePluginConfig = ({ pluginConfig }) => {
|
|
|
1543
2070
|
roleConfig
|
|
1544
2071
|
})
|
|
1545
2072
|
},
|
|
2073
|
+
policies: {
|
|
2074
|
+
canApplyCoupon: typeof pluginConfig?.policies?.canApplyCoupon === "function" ? pluginConfig.policies.canApplyCoupon : () => true,
|
|
2075
|
+
canApplyReferral: typeof pluginConfig?.policies?.canApplyReferral === "function" ? pluginConfig.policies.canApplyReferral : () => true,
|
|
2076
|
+
canViewPartnerStats: typeof pluginConfig?.policies?.canViewPartnerStats === "function" ? pluginConfig.policies.canViewPartnerStats : ({ req }) => isPartnerUser({
|
|
2077
|
+
user: req?.user,
|
|
2078
|
+
roleConfig
|
|
2079
|
+
}) || isAdminUser({
|
|
2080
|
+
user: req?.user,
|
|
2081
|
+
roleConfig
|
|
2082
|
+
}),
|
|
2083
|
+
canRecordOrderUsage: typeof pluginConfig?.policies?.canRecordOrderUsage === "function" ? pluginConfig.policies.canRecordOrderUsage : () => true
|
|
2084
|
+
},
|
|
2085
|
+
integration: {
|
|
2086
|
+
collections: integrationCollections,
|
|
2087
|
+
fields: integrationFields,
|
|
2088
|
+
resolvers: integrationResolvers
|
|
2089
|
+
},
|
|
1546
2090
|
referralConfig: {
|
|
1547
2091
|
allowBothSystems: pluginConfig?.referralConfig?.allowBothSystems ?? false,
|
|
1548
2092
|
singleCodePerCart: pluginConfig?.referralConfig?.singleCodePerCart ?? true,
|
|
@@ -1562,17 +2106,84 @@ const sanitizePluginConfig = ({ pluginConfig }) => {
|
|
|
1562
2106
|
showCommissionBreakdown: pluginConfig?.partnerDashboard?.showCommissionBreakdown ?? true
|
|
1563
2107
|
},
|
|
1564
2108
|
orderIntegration: {
|
|
1565
|
-
ordersSlug: typeof pluginConfig?.orderIntegration?.ordersSlug === "string" && pluginConfig.orderIntegration.ordersSlug.trim().length > 0 ? pluginConfig.orderIntegration.ordersSlug :
|
|
1566
|
-
orderCustomerEmailField:
|
|
1567
|
-
orderPaymentStatusField:
|
|
2109
|
+
ordersSlug: typeof pluginConfig?.orderIntegration?.ordersSlug === "string" && pluginConfig.orderIntegration.ordersSlug.trim().length > 0 ? pluginConfig.orderIntegration.ordersSlug : integrationCollections.ordersSlug,
|
|
2110
|
+
orderCustomerEmailField: integrationFields.orderCustomerEmailField,
|
|
2111
|
+
orderPaymentStatusField: integrationFields.orderPaymentStatusField,
|
|
1568
2112
|
orderPaidStatusValue: typeof pluginConfig?.orderIntegration?.orderPaidStatusValue === "string" ? pluginConfig.orderIntegration.orderPaidStatusValue : "paid"
|
|
1569
2113
|
},
|
|
1570
2114
|
roleConfig
|
|
1571
2115
|
};
|
|
1572
2116
|
};
|
|
1573
|
-
|
|
1574
2117
|
//#endregion
|
|
1575
2118
|
//#region src/plugin.ts
|
|
2119
|
+
const RECALCULATE_HOOK_KEY = "__payloadEcommerceCouponRecalculateHook__";
|
|
2120
|
+
const asArray = (value) => Array.isArray(value) ? value : [];
|
|
2121
|
+
const hasNamedField = (collection, fieldName) => asArray(collection.fields).some((f) => f?.name === fieldName);
|
|
2122
|
+
const addFieldsToCollection = (config, targetSlug, newFields) => {
|
|
2123
|
+
const collections = asArray(config.collections);
|
|
2124
|
+
const idx = collections.findIndex((c) => c.slug === targetSlug);
|
|
2125
|
+
if (idx === -1) return;
|
|
2126
|
+
const collection = collections[idx];
|
|
2127
|
+
collection.fields = asArray(collection.fields);
|
|
2128
|
+
for (const field of newFields) {
|
|
2129
|
+
const name = typeof field.name === "string" ? field.name : "";
|
|
2130
|
+
if (!name) continue;
|
|
2131
|
+
if (!hasNamedField(collection, name)) collection.fields.push(field);
|
|
2132
|
+
}
|
|
2133
|
+
collections[idx] = collection;
|
|
2134
|
+
config.collections = collections;
|
|
2135
|
+
};
|
|
2136
|
+
const markHook = (fn) => {
|
|
2137
|
+
fn[RECALCULATE_HOOK_KEY] = true;
|
|
2138
|
+
return fn;
|
|
2139
|
+
};
|
|
2140
|
+
const hasMarkedHook = (hook) => Boolean(hook && typeof hook === "function" && hook[RECALCULATE_HOOK_KEY]);
|
|
2141
|
+
const createRecordOrderUsageEndpoint = ({ pluginConfig }) => ({
|
|
2142
|
+
path: pluginConfig.endpoints.recordOrderUsage,
|
|
2143
|
+
method: "post",
|
|
2144
|
+
handler: async (req) => {
|
|
2145
|
+
try {
|
|
2146
|
+
const payload = req?.payload;
|
|
2147
|
+
const orderId = req?.data?.orderId ?? req?.json?.orderId;
|
|
2148
|
+
if (!payload) return Response.json({
|
|
2149
|
+
success: false,
|
|
2150
|
+
error: "Payload instance is required"
|
|
2151
|
+
}, { status: 500 });
|
|
2152
|
+
if (!orderId) return Response.json({
|
|
2153
|
+
success: false,
|
|
2154
|
+
error: "orderId is required"
|
|
2155
|
+
}, { status: 400 });
|
|
2156
|
+
if (!await Promise.resolve(pluginConfig.policies.canRecordOrderUsage({
|
|
2157
|
+
req,
|
|
2158
|
+
user: req?.user,
|
|
2159
|
+
payload,
|
|
2160
|
+
order: { id: orderId }
|
|
2161
|
+
}))) return Response.json({
|
|
2162
|
+
success: false,
|
|
2163
|
+
error: "Forbidden"
|
|
2164
|
+
}, { status: 403 });
|
|
2165
|
+
const order = await payload.findByID({
|
|
2166
|
+
collection: pluginConfig.integration.collections.ordersSlug,
|
|
2167
|
+
id: orderId
|
|
2168
|
+
});
|
|
2169
|
+
if (!order) return Response.json({
|
|
2170
|
+
success: false,
|
|
2171
|
+
error: "Order not found"
|
|
2172
|
+
}, { status: 404 });
|
|
2173
|
+
const result = await recordCouponUsageForOrder(payload, order, pluginConfig);
|
|
2174
|
+
return Response.json({
|
|
2175
|
+
success: true,
|
|
2176
|
+
result
|
|
2177
|
+
});
|
|
2178
|
+
} catch (error) {
|
|
2179
|
+
console.error("record-order-usage endpoint error:", error);
|
|
2180
|
+
return Response.json({
|
|
2181
|
+
success: false,
|
|
2182
|
+
error: "Internal server error"
|
|
2183
|
+
}, { status: 500 });
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
1576
2187
|
const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConfig) => {
|
|
1577
2188
|
const pluginConfig = sanitizePluginConfig({ pluginConfig: pluginOptions });
|
|
1578
2189
|
if (!pluginConfig.enabled) return incomingConfig || {};
|
|
@@ -1580,7 +2191,8 @@ const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConf
|
|
|
1580
2191
|
collections: [],
|
|
1581
2192
|
endpoints: []
|
|
1582
2193
|
};
|
|
1583
|
-
|
|
2194
|
+
incomingConfig.collections = asArray(incomingConfig.collections);
|
|
2195
|
+
incomingConfig.endpoints = asArray(incomingConfig.endpoints);
|
|
1584
2196
|
const collectionsToAdd = [];
|
|
1585
2197
|
if (pluginConfig.enableReferrals) {
|
|
1586
2198
|
let referralProgramsCollection = createReferralProgramsCollection(pluginConfig);
|
|
@@ -1598,64 +2210,59 @@ const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConf
|
|
|
1598
2210
|
if (pluginOptions.collections?.couponsCollectionOverride) couponsCollection = await pluginOptions.collections.couponsCollectionOverride({ defaultCollection: couponsCollection });
|
|
1599
2211
|
collectionsToAdd.push(couponsCollection);
|
|
1600
2212
|
}
|
|
1601
|
-
const existingSlugs = new Set(incomingConfig.collections.map((c) => c.slug));
|
|
1602
|
-
const
|
|
1603
|
-
incomingConfig.collections = [...incomingConfig.collections, ...
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
2213
|
+
const existingSlugs = new Set(asArray(incomingConfig.collections).map((c) => c.slug));
|
|
2214
|
+
const toAppend = collectionsToAdd.filter((c) => !existingSlugs.has(c.slug));
|
|
2215
|
+
incomingConfig.collections = [...asArray(incomingConfig.collections), ...toAppend];
|
|
2216
|
+
const endpointPaths = new Set(asArray(incomingConfig.endpoints).map((e) => `${e?.method || "get"}:${e?.path || ""}`));
|
|
2217
|
+
const maybePushEndpoint = (endpoint) => {
|
|
2218
|
+
const key = `${endpoint?.method || "get"}:${endpoint?.path || ""}`;
|
|
2219
|
+
if (!endpointPaths.has(key)) {
|
|
2220
|
+
endpointPaths.add(key);
|
|
2221
|
+
incomingConfig.endpoints.push(endpoint);
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
maybePushEndpoint(validateCouponEndpoint({ pluginConfig }));
|
|
2225
|
+
maybePushEndpoint(applyCouponEndpoint({ pluginConfig }));
|
|
2226
|
+
if (pluginOptions.endpoints?.recordOrderUsage) maybePushEndpoint(createRecordOrderUsageEndpoint({ pluginConfig }));
|
|
2227
|
+
if (pluginConfig.enableReferrals) maybePushEndpoint(partnerStatsEndpoint({ pluginConfig }));
|
|
1611
2228
|
if (pluginConfig.autoIntegrate) {
|
|
1612
|
-
|
|
1613
|
-
const
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
if (idx === -1) return;
|
|
1617
|
-
const collection = incomingConfig.collections[idx];
|
|
1618
|
-
collection.fields = collection.fields || [];
|
|
1619
|
-
const existingFieldNames = new Set(collection.fields.map((f) => f.name));
|
|
1620
|
-
for (const f of newFields) if (!existingFieldNames.has(f.name)) collection.fields.push(f);
|
|
1621
|
-
incomingConfig.collections[idx] = collection;
|
|
1622
|
-
};
|
|
2229
|
+
const allSlugs = new Set(asArray(incomingConfig.collections).map((c) => c.slug));
|
|
2230
|
+
const cartsSlug = pluginConfig.integration.collections.cartsSlug;
|
|
2231
|
+
const ordersSlug = pluginConfig.integration.collections.ordersSlug;
|
|
2232
|
+
const { cartAppliedReferralCodeField, cartPartnerCommissionField, cartCustomerDiscountField, cartAppliedCouponField, cartDiscountAmountField, orderAppliedReferralCodeField, orderPartnerCommissionField, orderCustomerDiscountField, orderAppliedCouponField, orderDiscountAmountField } = pluginConfig.integration.fields;
|
|
1623
2233
|
if (pluginConfig.enableReferrals && allSlugs.has(pluginConfig.collections.referralCodesSlug)) {
|
|
1624
2234
|
const cartReferralFields = [
|
|
1625
2235
|
{
|
|
1626
|
-
name:
|
|
2236
|
+
name: cartAppliedReferralCodeField,
|
|
1627
2237
|
type: "relationship",
|
|
1628
2238
|
relationTo: pluginConfig.collections.referralCodesSlug,
|
|
1629
2239
|
admin: { description: "Referral code applied to this cart" }
|
|
1630
2240
|
},
|
|
1631
2241
|
{
|
|
1632
|
-
name:
|
|
2242
|
+
name: cartPartnerCommissionField,
|
|
1633
2243
|
type: "number",
|
|
1634
2244
|
admin: { description: "Partner commission amount for this cart" }
|
|
1635
2245
|
},
|
|
1636
2246
|
{
|
|
1637
|
-
name:
|
|
2247
|
+
name: cartCustomerDiscountField,
|
|
1638
2248
|
type: "number",
|
|
1639
2249
|
admin: { description: "Customer discount amount for this cart" }
|
|
1640
2250
|
}
|
|
1641
2251
|
];
|
|
1642
|
-
if (pluginConfig.referralConfig.allowBothSystems && allSlugs.has(pluginConfig.collections.couponsSlug)) {
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
});
|
|
1654
|
-
}
|
|
1655
|
-
addFieldsToCollection("carts", cartReferralFields);
|
|
2252
|
+
if (pluginConfig.referralConfig.allowBothSystems && allSlugs.has(pluginConfig.collections.couponsSlug)) cartReferralFields.push({
|
|
2253
|
+
name: cartAppliedCouponField,
|
|
2254
|
+
type: "relationship",
|
|
2255
|
+
relationTo: pluginConfig.collections.couponsSlug,
|
|
2256
|
+
admin: { description: "Coupon applied to this cart" }
|
|
2257
|
+
}, {
|
|
2258
|
+
name: cartDiscountAmountField,
|
|
2259
|
+
type: "number",
|
|
2260
|
+
admin: { description: "Discount amount from coupon" }
|
|
2261
|
+
});
|
|
2262
|
+
addFieldsToCollection(incomingConfig, cartsSlug, cartReferralFields);
|
|
1656
2263
|
const orderReferralFields = [
|
|
1657
2264
|
{
|
|
1658
|
-
name:
|
|
2265
|
+
name: orderAppliedReferralCodeField,
|
|
1659
2266
|
type: "relationship",
|
|
1660
2267
|
relationTo: pluginConfig.collections.referralCodesSlug,
|
|
1661
2268
|
admin: {
|
|
@@ -1664,7 +2271,7 @@ const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConf
|
|
|
1664
2271
|
}
|
|
1665
2272
|
},
|
|
1666
2273
|
{
|
|
1667
|
-
name:
|
|
2274
|
+
name: orderPartnerCommissionField,
|
|
1668
2275
|
type: "number",
|
|
1669
2276
|
admin: {
|
|
1670
2277
|
description: "Partner commission amount for this order",
|
|
@@ -1672,7 +2279,7 @@ const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConf
|
|
|
1672
2279
|
}
|
|
1673
2280
|
},
|
|
1674
2281
|
{
|
|
1675
|
-
name:
|
|
2282
|
+
name: orderCustomerDiscountField,
|
|
1676
2283
|
type: "number",
|
|
1677
2284
|
admin: {
|
|
1678
2285
|
description: "Customer discount amount for this order",
|
|
@@ -1680,39 +2287,36 @@ const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConf
|
|
|
1680
2287
|
}
|
|
1681
2288
|
}
|
|
1682
2289
|
];
|
|
1683
|
-
if (pluginConfig.referralConfig.allowBothSystems && allSlugs.has(pluginConfig.collections.couponsSlug)) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
addFieldsToCollection("orders", orderReferralFields);
|
|
2290
|
+
if (pluginConfig.referralConfig.allowBothSystems && allSlugs.has(pluginConfig.collections.couponsSlug)) orderReferralFields.push({
|
|
2291
|
+
name: orderAppliedCouponField,
|
|
2292
|
+
type: "relationship",
|
|
2293
|
+
relationTo: pluginConfig.collections.couponsSlug,
|
|
2294
|
+
admin: {
|
|
2295
|
+
description: "Coupon applied to this order",
|
|
2296
|
+
readOnly: true
|
|
2297
|
+
}
|
|
2298
|
+
}, {
|
|
2299
|
+
name: orderDiscountAmountField,
|
|
2300
|
+
type: "number",
|
|
2301
|
+
admin: {
|
|
2302
|
+
description: "Discount amount from coupon",
|
|
2303
|
+
readOnly: true
|
|
2304
|
+
}
|
|
2305
|
+
});
|
|
2306
|
+
addFieldsToCollection(incomingConfig, ordersSlug, orderReferralFields);
|
|
1703
2307
|
} else if (!pluginConfig.enableReferrals && allSlugs.has(pluginConfig.collections.couponsSlug)) {
|
|
1704
|
-
|
|
1705
|
-
name:
|
|
2308
|
+
const cartCouponFields = [{
|
|
2309
|
+
name: cartAppliedCouponField,
|
|
1706
2310
|
type: "relationship",
|
|
1707
2311
|
relationTo: pluginConfig.collections.couponsSlug,
|
|
1708
2312
|
admin: { description: "Coupon applied to this cart" }
|
|
1709
2313
|
}, {
|
|
1710
|
-
name:
|
|
2314
|
+
name: cartDiscountAmountField,
|
|
1711
2315
|
type: "number",
|
|
1712
2316
|
admin: { description: "Discount amount from coupon" }
|
|
1713
|
-
}]
|
|
1714
|
-
|
|
1715
|
-
name:
|
|
2317
|
+
}];
|
|
2318
|
+
const orderCouponFields = [{
|
|
2319
|
+
name: orderAppliedCouponField,
|
|
1716
2320
|
type: "relationship",
|
|
1717
2321
|
relationTo: pluginConfig.collections.couponsSlug,
|
|
1718
2322
|
admin: {
|
|
@@ -1720,43 +2324,75 @@ const payloadEcommerceCouponPlugin = (pluginOptions = {}) => async (incomingConf
|
|
|
1720
2324
|
readOnly: true
|
|
1721
2325
|
}
|
|
1722
2326
|
}, {
|
|
1723
|
-
name:
|
|
2327
|
+
name: orderDiscountAmountField,
|
|
1724
2328
|
type: "number",
|
|
1725
2329
|
admin: {
|
|
1726
2330
|
description: "Discount amount from coupon",
|
|
1727
2331
|
readOnly: true
|
|
1728
2332
|
}
|
|
1729
|
-
}]
|
|
2333
|
+
}];
|
|
2334
|
+
addFieldsToCollection(incomingConfig, cartsSlug, cartCouponFields);
|
|
2335
|
+
addFieldsToCollection(incomingConfig, ordersSlug, orderCouponFields);
|
|
1730
2336
|
}
|
|
1731
2337
|
}
|
|
1732
|
-
const
|
|
2338
|
+
const cartsSlug = pluginConfig.integration.collections.cartsSlug;
|
|
2339
|
+
const cartIndex = asArray(incomingConfig.collections).findIndex((c) => c.slug === cartsSlug);
|
|
1733
2340
|
if (cartIndex > -1) {
|
|
1734
2341
|
const collection = incomingConfig.collections[cartIndex];
|
|
1735
|
-
collection.hooks
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
2342
|
+
const beforeChangeHooks = asArray(collection.hooks?.beforeChange);
|
|
2343
|
+
if (!beforeChangeHooks.some((h) => hasMarkedHook(h))) {
|
|
2344
|
+
const hook = markHook(recalculateCartHook(pluginConfig));
|
|
2345
|
+
collection.hooks = {
|
|
2346
|
+
...collection.hooks || {},
|
|
2347
|
+
beforeChange: [...beforeChangeHooks, hook]
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
1739
2350
|
incomingConfig.collections[cartIndex] = collection;
|
|
1740
2351
|
}
|
|
1741
2352
|
return incomingConfig;
|
|
1742
2353
|
};
|
|
1743
|
-
|
|
1744
2354
|
//#endregion
|
|
1745
2355
|
//#region src/client/hooks.ts
|
|
2356
|
+
const DEFAULT_ENDPOINTS = {
|
|
2357
|
+
applyCoupon: "/api/coupons/apply",
|
|
2358
|
+
validateCoupon: "/api/coupons/validate",
|
|
2359
|
+
partnerStats: "/api/referrals/partner-stats"
|
|
2360
|
+
};
|
|
2361
|
+
function normalizePath(path) {
|
|
2362
|
+
if (!path) return "";
|
|
2363
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
2364
|
+
}
|
|
2365
|
+
function withBaseURL(path, baseURL) {
|
|
2366
|
+
if (!baseURL) return path;
|
|
2367
|
+
return `${baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL}${normalizePath(path)}`;
|
|
2368
|
+
}
|
|
2369
|
+
function resolveEndpoints(input) {
|
|
2370
|
+
if (typeof input === "string") return {
|
|
2371
|
+
...DEFAULT_ENDPOINTS,
|
|
2372
|
+
partnerStats: input
|
|
2373
|
+
};
|
|
2374
|
+
const baseURL = input?.baseURL;
|
|
2375
|
+
return {
|
|
2376
|
+
applyCoupon: withBaseURL(input?.applyCoupon || DEFAULT_ENDPOINTS.applyCoupon, baseURL),
|
|
2377
|
+
validateCoupon: withBaseURL(input?.validateCoupon || DEFAULT_ENDPOINTS.validateCoupon, baseURL),
|
|
2378
|
+
partnerStats: withBaseURL(input?.partnerStats || DEFAULT_ENDPOINTS.partnerStats, baseURL)
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
1746
2381
|
/**
|
|
1747
|
-
* Apply a coupon code to a cart
|
|
1748
|
-
* @param options -
|
|
1749
|
-
* @
|
|
2382
|
+
* Apply a coupon/referral code to a cart
|
|
2383
|
+
* @param options - Code, cart ID, and optional customerEmail
|
|
2384
|
+
* @param endpointConfig - Optional endpoint override config
|
|
1750
2385
|
*/
|
|
1751
|
-
async function useCouponCode(options) {
|
|
2386
|
+
async function useCouponCode(options, endpointConfig) {
|
|
1752
2387
|
const { code, cartID, customerEmail } = options;
|
|
1753
2388
|
if (!code) return {
|
|
1754
2389
|
success: false,
|
|
1755
2390
|
message: "Coupon code is required",
|
|
1756
2391
|
error: "Code is missing"
|
|
1757
2392
|
};
|
|
2393
|
+
const endpoints = resolveEndpoints(endpointConfig);
|
|
1758
2394
|
try {
|
|
1759
|
-
const response = await fetch(
|
|
2395
|
+
const response = await fetch(endpoints.applyCoupon, {
|
|
1760
2396
|
method: "POST",
|
|
1761
2397
|
headers: { "Content-Type": "application/json" },
|
|
1762
2398
|
body: JSON.stringify({
|
|
@@ -1774,8 +2410,8 @@ async function useCouponCode(options) {
|
|
|
1774
2410
|
const couponData = data.coupon;
|
|
1775
2411
|
const referralData = data.referralCode;
|
|
1776
2412
|
return {
|
|
1777
|
-
success: data.success,
|
|
1778
|
-
message: data.message,
|
|
2413
|
+
success: Boolean(data.success),
|
|
2414
|
+
message: data.message || "Code applied",
|
|
1779
2415
|
discount: data.discount || data.customerDiscount,
|
|
1780
2416
|
partnerCommission: data.partnerCommission,
|
|
1781
2417
|
customerDiscount: data.customerDiscount,
|
|
@@ -1796,25 +2432,29 @@ async function useCouponCode(options) {
|
|
|
1796
2432
|
}
|
|
1797
2433
|
}
|
|
1798
2434
|
/**
|
|
1799
|
-
* Validate a coupon code without applying it
|
|
1800
|
-
* @param code -
|
|
1801
|
-
* @param cartValue - Optional cart value
|
|
1802
|
-
* @
|
|
2435
|
+
* Validate a coupon/referral code without applying it
|
|
2436
|
+
* @param code - Code to validate
|
|
2437
|
+
* @param cartValue - Optional cart value
|
|
2438
|
+
* @param cartID - Optional cart ID
|
|
2439
|
+
* @param customerEmail - Optional customer email (for per-customer limits)
|
|
2440
|
+
* @param endpointConfig - Optional endpoint override config
|
|
1803
2441
|
*/
|
|
1804
|
-
async function validateCouponCode(code, cartValue, cartID) {
|
|
2442
|
+
async function validateCouponCode(code, cartValue, cartID, customerEmail, endpointConfig) {
|
|
1805
2443
|
if (!code) return {
|
|
1806
2444
|
success: false,
|
|
1807
2445
|
message: "Code required",
|
|
1808
2446
|
error: "Code missing"
|
|
1809
2447
|
};
|
|
2448
|
+
const endpoints = resolveEndpoints(endpointConfig);
|
|
1810
2449
|
try {
|
|
1811
|
-
const response = await fetch(
|
|
2450
|
+
const response = await fetch(endpoints.validateCoupon, {
|
|
1812
2451
|
method: "POST",
|
|
1813
2452
|
headers: { "Content-Type": "application/json" },
|
|
1814
2453
|
body: JSON.stringify({
|
|
1815
2454
|
code,
|
|
1816
2455
|
cartValue,
|
|
1817
|
-
cartID
|
|
2456
|
+
cartID,
|
|
2457
|
+
customerEmail
|
|
1818
2458
|
})
|
|
1819
2459
|
});
|
|
1820
2460
|
const data = await response.json();
|
|
@@ -1826,8 +2466,8 @@ async function validateCouponCode(code, cartValue, cartID) {
|
|
|
1826
2466
|
const couponData = data.coupon;
|
|
1827
2467
|
const referralData = data.referralCode;
|
|
1828
2468
|
return {
|
|
1829
|
-
success: data.success,
|
|
1830
|
-
message: data.message,
|
|
2469
|
+
success: Boolean(data.success),
|
|
2470
|
+
message: data.message || "Code is valid",
|
|
1831
2471
|
coupon: couponData ? {
|
|
1832
2472
|
code: couponData.code || "",
|
|
1833
2473
|
type: couponData.type || "percentage",
|
|
@@ -1850,12 +2490,12 @@ async function validateCouponCode(code, cartValue, cartID) {
|
|
|
1850
2490
|
}
|
|
1851
2491
|
/**
|
|
1852
2492
|
* Fetch partner dashboard statistics
|
|
1853
|
-
* @param
|
|
1854
|
-
* @returns Response with partner stats, referral codes, and program info
|
|
2493
|
+
* @param endpointConfig - Optional endpoint override config
|
|
1855
2494
|
*/
|
|
1856
|
-
async function usePartnerStats(
|
|
2495
|
+
async function usePartnerStats(endpointConfig) {
|
|
2496
|
+
const endpoints = resolveEndpoints(endpointConfig);
|
|
1857
2497
|
try {
|
|
1858
|
-
const response = await fetch(
|
|
2498
|
+
const response = await fetch(endpoints.partnerStats, {
|
|
1859
2499
|
method: "GET",
|
|
1860
2500
|
headers: { "Content-Type": "application/json" },
|
|
1861
2501
|
credentials: "include"
|
|
@@ -1866,7 +2506,7 @@ async function usePartnerStats(apiEndpoint = "/api/referrals/partner-stats") {
|
|
|
1866
2506
|
error: data.error || "Failed to fetch partner stats"
|
|
1867
2507
|
};
|
|
1868
2508
|
return {
|
|
1869
|
-
success: data.success,
|
|
2509
|
+
success: Boolean(data.success),
|
|
1870
2510
|
data: data.data,
|
|
1871
2511
|
currency: data.currency
|
|
1872
2512
|
};
|
|
@@ -1877,7 +2517,6 @@ async function usePartnerStats(apiEndpoint = "/api/referrals/partner-stats") {
|
|
|
1877
2517
|
};
|
|
1878
2518
|
}
|
|
1879
2519
|
}
|
|
1880
|
-
|
|
1881
2520
|
//#endregion
|
|
1882
2521
|
//#region src/utilities/getCartTotalWithDiscounts.ts
|
|
1883
2522
|
/**
|
|
@@ -1893,66 +2532,6 @@ function getCartTotalWithDiscounts(cart) {
|
|
|
1893
2532
|
const customerDiscount = cart.customerDiscount ?? 0;
|
|
1894
2533
|
return roundTo2(Math.max(0, subtotal - discountAmount - customerDiscount));
|
|
1895
2534
|
}
|
|
1896
|
-
|
|
1897
|
-
//#endregion
|
|
1898
|
-
//#region src/utilities/recordCouponUsageForOrder.ts
|
|
1899
|
-
/**
|
|
1900
|
-
* Record coupon and referral usage when an order is placed successfully.
|
|
1901
|
-
* Call this once when the order is created/paid (e.g. from Orders collection afterChange hook).
|
|
1902
|
-
*
|
|
1903
|
-
* - Coupon: increments the coupon's usageCount.
|
|
1904
|
-
* - Referral: increments the referral code's usageCount and successfulReferralsCount,
|
|
1905
|
-
* and adds order.partnerCommission to totalEarnings and pendingEarnings (partner gets commission;
|
|
1906
|
-
* referee discount is already on the order).
|
|
1907
|
-
*/
|
|
1908
|
-
async function recordCouponUsageForOrder(payload, order, pluginConfig) {
|
|
1909
|
-
const result = {
|
|
1910
|
-
recordedCoupon: false,
|
|
1911
|
-
recordedReferral: false
|
|
1912
|
-
};
|
|
1913
|
-
const couponId = order.appliedCoupon == null ? null : typeof order.appliedCoupon === "string" ? order.appliedCoupon : order.appliedCoupon?.id;
|
|
1914
|
-
const referralCodeId = order.appliedReferralCode == null ? null : typeof order.appliedReferralCode === "string" ? order.appliedReferralCode : order.appliedReferralCode?.id;
|
|
1915
|
-
if (couponId) {
|
|
1916
|
-
const coupon = await payload.findByID({
|
|
1917
|
-
collection: pluginConfig.collections.couponsSlug,
|
|
1918
|
-
id: couponId
|
|
1919
|
-
});
|
|
1920
|
-
if (coupon) {
|
|
1921
|
-
await payload.update({
|
|
1922
|
-
collection: pluginConfig.collections.couponsSlug,
|
|
1923
|
-
id: couponId,
|
|
1924
|
-
data: { usageCount: (coupon.usageCount ?? 0) + 1 }
|
|
1925
|
-
});
|
|
1926
|
-
result.recordedCoupon = true;
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
if (referralCodeId) {
|
|
1930
|
-
const referralCode = await payload.findByID({
|
|
1931
|
-
collection: pluginConfig.collections.referralCodesSlug,
|
|
1932
|
-
id: referralCodeId
|
|
1933
|
-
});
|
|
1934
|
-
if (referralCode) {
|
|
1935
|
-
const commission = Number(order.partnerCommission) || 0;
|
|
1936
|
-
const currentTotal = Number(referralCode.totalEarnings) || 0;
|
|
1937
|
-
const currentPending = Number(referralCode.pendingEarnings) || 0;
|
|
1938
|
-
const currentUsageCount = Number(referralCode.usageCount) || 0;
|
|
1939
|
-
const currentSuccessful = Number(referralCode.successfulReferralsCount) || 0;
|
|
1940
|
-
await payload.update({
|
|
1941
|
-
collection: pluginConfig.collections.referralCodesSlug,
|
|
1942
|
-
id: referralCodeId,
|
|
1943
|
-
data: {
|
|
1944
|
-
usageCount: currentUsageCount + 1,
|
|
1945
|
-
successfulReferralsCount: currentSuccessful + 1,
|
|
1946
|
-
totalEarnings: currentTotal + commission,
|
|
1947
|
-
pendingEarnings: currentPending + commission
|
|
1948
|
-
}
|
|
1949
|
-
});
|
|
1950
|
-
result.recordedReferral = true;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
return result;
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
2535
|
//#endregion
|
|
1957
2536
|
exports.calculateCommissionAndDiscount = calculateCommissionAndDiscount;
|
|
1958
2537
|
exports.createCouponsCollection = createCouponsCollection;
|
|
@@ -1965,4 +2544,5 @@ exports.recordCouponUsageForOrder = recordCouponUsageForOrder;
|
|
|
1965
2544
|
exports.useCouponCode = useCouponCode;
|
|
1966
2545
|
exports.usePartnerStats = usePartnerStats;
|
|
1967
2546
|
exports.validateCouponCode = validateCouponCode;
|
|
2547
|
+
|
|
1968
2548
|
//# sourceMappingURL=index.js.map
|