mumz-strapi-plugin-coupon 1.0.7
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 +311 -0
- package/dist/bootstrap.d.ts +5 -0
- package/dist/bootstrap.js +6 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +6 -0
- package/dist/content-types/coupon/index.d.ts +90 -0
- package/dist/content-types/coupon/index.js +9 -0
- package/dist/content-types/coupon/schema.d.ts +88 -0
- package/dist/content-types/coupon/schema.js +105 -0
- package/dist/content-types/index.d.ts +155 -0
- package/dist/content-types/index.js +11 -0
- package/dist/content-types/redemption/index.d.ts +64 -0
- package/dist/content-types/redemption/index.js +9 -0
- package/dist/content-types/redemption/schema.d.ts +62 -0
- package/dist/content-types/redemption/schema.js +74 -0
- package/dist/controllers/coupon.d.ts +41 -0
- package/dist/controllers/coupon.js +154 -0
- package/dist/controllers/index.d.ts +5 -0
- package/dist/controllers/index.js +9 -0
- package/dist/destroy.d.ts +5 -0
- package/dist/destroy.js +6 -0
- package/dist/index.d.ts +201 -0
- package/dist/index.js +24 -0
- package/dist/middlewares/index.d.ts +4 -0
- package/dist/middlewares/index.js +9 -0
- package/dist/middlewares/rate-limit.d.ts +6 -0
- package/dist/middlewares/rate-limit.js +42 -0
- package/dist/register.d.ts +5 -0
- package/dist/register.js +6 -0
- package/dist/routes/content-api/index.d.ts +23 -0
- package/dist/routes/content-api/index.js +76 -0
- package/dist/routes/index.d.ts +25 -0
- package/dist/routes/index.js +9 -0
- package/dist/services/coupon.d.ts +61 -0
- package/dist/services/coupon.js +399 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.js +9 -0
- package/dist/utils/validators.d.ts +14 -0
- package/dist/utils/validators.js +41 -0
- package/package.json +71 -0
- package/strapi-server.js +1 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ERROR_MESSAGES = exports.ERROR_CODES = void 0;
|
|
4
|
+
const validators_1 = require("../utils/validators");
|
|
5
|
+
// Error codes as per specification
|
|
6
|
+
exports.ERROR_CODES = {
|
|
7
|
+
COUPON_NOT_FOUND: 'COUPON_NOT_FOUND',
|
|
8
|
+
COUPON_EXPIRED: 'COUPON_EXPIRED',
|
|
9
|
+
COUPON_ALREADY_USED: 'COUPON_ALREADY_USED',
|
|
10
|
+
USAGE_LIMIT_REACHED: 'USAGE_LIMIT_REACHED',
|
|
11
|
+
INVALID_REQUEST: 'INVALID_REQUEST',
|
|
12
|
+
};
|
|
13
|
+
exports.ERROR_MESSAGES = {
|
|
14
|
+
COUPON_NOT_FOUND: 'Coupon code does not exist.',
|
|
15
|
+
COUPON_EXPIRED: 'This coupon has expired.',
|
|
16
|
+
COUPON_ALREADY_USED: 'This coupon has been already used.',
|
|
17
|
+
USAGE_LIMIT_REACHED: 'Coupon usage limit has been reached.',
|
|
18
|
+
INVALID_REQUEST: 'Invalid request parameters.',
|
|
19
|
+
};
|
|
20
|
+
const couponService = ({ strapi }) => ({
|
|
21
|
+
/**
|
|
22
|
+
* Validate a coupon for a specific service and user
|
|
23
|
+
*/
|
|
24
|
+
async validate(params) {
|
|
25
|
+
const { couponCode, phoneNumber, orderAmount = 0 } = params;
|
|
26
|
+
try {
|
|
27
|
+
// Validate inputs
|
|
28
|
+
if (!(0, validators_1.validateCouponCode)(couponCode)) {
|
|
29
|
+
return {
|
|
30
|
+
isValid: false,
|
|
31
|
+
errorCode: exports.ERROR_CODES.INVALID_REQUEST,
|
|
32
|
+
message: 'Invalid coupon code format',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (!(0, validators_1.validatePhoneNumber)(phoneNumber)) {
|
|
36
|
+
return {
|
|
37
|
+
isValid: false,
|
|
38
|
+
errorCode: exports.ERROR_CODES.INVALID_REQUEST,
|
|
39
|
+
message: 'Invalid phone number format',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (orderAmount < 0) {
|
|
43
|
+
return {
|
|
44
|
+
isValid: false,
|
|
45
|
+
errorCode: exports.ERROR_CODES.INVALID_REQUEST,
|
|
46
|
+
message: 'Order amount cannot be negative',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Find the coupon by code
|
|
50
|
+
const coupons = await strapi.entityService.findMany('plugin::coupon.coupon', {
|
|
51
|
+
filters: {
|
|
52
|
+
code: couponCode,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const coupon = Array.isArray(coupons) && coupons.length > 0 ? coupons[0] : null;
|
|
56
|
+
// Check if coupon exists
|
|
57
|
+
if (!coupon) {
|
|
58
|
+
return {
|
|
59
|
+
isValid: false,
|
|
60
|
+
errorCode: exports.ERROR_CODES.COUPON_NOT_FOUND,
|
|
61
|
+
message: exports.ERROR_MESSAGES.COUPON_NOT_FOUND,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Check if coupon is active
|
|
65
|
+
if (!coupon.isActive) {
|
|
66
|
+
return {
|
|
67
|
+
isValid: false,
|
|
68
|
+
errorCode: exports.ERROR_CODES.COUPON_EXPIRED,
|
|
69
|
+
message: exports.ERROR_MESSAGES.COUPON_EXPIRED,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Check validity period
|
|
73
|
+
const now = new Date();
|
|
74
|
+
const validFrom = new Date(coupon.validFrom);
|
|
75
|
+
const validTo = new Date(coupon.validTo);
|
|
76
|
+
if (now < validFrom || now > validTo) {
|
|
77
|
+
return {
|
|
78
|
+
isValid: false,
|
|
79
|
+
errorCode: exports.ERROR_CODES.COUPON_EXPIRED,
|
|
80
|
+
message: exports.ERROR_MESSAGES.COUPON_EXPIRED,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// Check usage limit
|
|
84
|
+
if (coupon.maxUsage && coupon.currentUsage >= coupon.maxUsage) {
|
|
85
|
+
return {
|
|
86
|
+
isValid: false,
|
|
87
|
+
errorCode: exports.ERROR_CODES.USAGE_LIMIT_REACHED,
|
|
88
|
+
message: exports.ERROR_MESSAGES.USAGE_LIMIT_REACHED,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Check if user has already used this coupon
|
|
92
|
+
const existingRedemptions = await strapi.entityService.findMany('plugin::coupon.redemption', {
|
|
93
|
+
filters: {
|
|
94
|
+
coupon: coupon.id,
|
|
95
|
+
phoneNumber,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
if (Array.isArray(existingRedemptions) && existingRedemptions.length > 0) {
|
|
99
|
+
return {
|
|
100
|
+
isValid: false,
|
|
101
|
+
errorCode: exports.ERROR_CODES.COUPON_ALREADY_USED,
|
|
102
|
+
message: exports.ERROR_MESSAGES.COUPON_ALREADY_USED,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Calculate discount
|
|
106
|
+
let discountAmount = 0;
|
|
107
|
+
let finalAmount = orderAmount;
|
|
108
|
+
if (coupon.discountType === 'percentage') {
|
|
109
|
+
discountAmount = (orderAmount * coupon.discountValue) / 100;
|
|
110
|
+
finalAmount = orderAmount - discountAmount;
|
|
111
|
+
}
|
|
112
|
+
else if (coupon.discountType === 'flat') {
|
|
113
|
+
discountAmount = coupon.discountValue;
|
|
114
|
+
finalAmount = Math.max(0, orderAmount - discountAmount);
|
|
115
|
+
}
|
|
116
|
+
// Return valid response
|
|
117
|
+
return {
|
|
118
|
+
isValid: true,
|
|
119
|
+
discountType: coupon.discountType,
|
|
120
|
+
discountValue: coupon.discountValue,
|
|
121
|
+
discountAmount,
|
|
122
|
+
finalAmount,
|
|
123
|
+
message: 'Coupon applied successfully.',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
strapi.log.error('Error validating coupon:', error);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* Redeem a coupon after successful payment
|
|
133
|
+
* Uses database transaction to prevent race conditions
|
|
134
|
+
*/
|
|
135
|
+
async redeem(params) {
|
|
136
|
+
const { couponCode, phoneNumber, orderId, orderAmount = 0 } = params;
|
|
137
|
+
try {
|
|
138
|
+
// Validate inputs
|
|
139
|
+
if (!(0, validators_1.validateOrderId)(orderId)) {
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
errorCode: exports.ERROR_CODES.INVALID_REQUEST,
|
|
143
|
+
message: 'Invalid order ID format',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Find and validate the coupon
|
|
147
|
+
const coupons = await strapi.db.query('plugin::coupon.coupon').findMany({
|
|
148
|
+
where: { code: couponCode },
|
|
149
|
+
});
|
|
150
|
+
const coupon = Array.isArray(coupons) && coupons.length > 0 ? coupons[0] : null;
|
|
151
|
+
if (!coupon) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
errorCode: exports.ERROR_CODES.COUPON_NOT_FOUND,
|
|
155
|
+
message: exports.ERROR_MESSAGES.COUPON_NOT_FOUND,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// Validate phone number
|
|
159
|
+
if (!(0, validators_1.validatePhoneNumber)(phoneNumber)) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
errorCode: exports.ERROR_CODES.INVALID_REQUEST,
|
|
163
|
+
message: 'Invalid phone number format',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// Perform all validations
|
|
167
|
+
if (!coupon.isActive) {
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
errorCode: exports.ERROR_CODES.COUPON_EXPIRED,
|
|
171
|
+
message: exports.ERROR_MESSAGES.COUPON_EXPIRED,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const now = new Date();
|
|
175
|
+
const validFrom = new Date(coupon.validFrom);
|
|
176
|
+
const validTo = new Date(coupon.validTo);
|
|
177
|
+
if (now < validFrom || now > validTo) {
|
|
178
|
+
return {
|
|
179
|
+
success: false,
|
|
180
|
+
errorCode: exports.ERROR_CODES.COUPON_EXPIRED,
|
|
181
|
+
message: exports.ERROR_MESSAGES.COUPON_EXPIRED,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
// Check for existing redemption
|
|
185
|
+
const existingRedemptions = await strapi.db.query('plugin::coupon.redemption').findMany({
|
|
186
|
+
where: {
|
|
187
|
+
coupon: coupon.id,
|
|
188
|
+
phoneNumber,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
if (Array.isArray(existingRedemptions) && existingRedemptions.length > 0) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
errorCode: exports.ERROR_CODES.COUPON_ALREADY_USED,
|
|
195
|
+
message: exports.ERROR_MESSAGES.COUPON_ALREADY_USED,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// Check usage limit before attempting redemption
|
|
199
|
+
if (coupon.maxUsage && coupon.currentUsage >= coupon.maxUsage) {
|
|
200
|
+
return {
|
|
201
|
+
success: false,
|
|
202
|
+
errorCode: exports.ERROR_CODES.USAGE_LIMIT_REACHED,
|
|
203
|
+
message: exports.ERROR_MESSAGES.USAGE_LIMIT_REACHED,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Calculate discount
|
|
207
|
+
let discountAmount = 0;
|
|
208
|
+
let finalAmount = orderAmount;
|
|
209
|
+
if (coupon.discountType === 'percentage') {
|
|
210
|
+
discountAmount = (orderAmount * coupon.discountValue) / 100;
|
|
211
|
+
finalAmount = orderAmount - discountAmount;
|
|
212
|
+
}
|
|
213
|
+
else if (coupon.discountType === 'flat') {
|
|
214
|
+
discountAmount = coupon.discountValue;
|
|
215
|
+
finalAmount = Math.max(0, orderAmount - discountAmount);
|
|
216
|
+
}
|
|
217
|
+
// Use transaction for atomic redemption
|
|
218
|
+
let redemptionId;
|
|
219
|
+
try {
|
|
220
|
+
// Atomic increment with optimistic locking
|
|
221
|
+
const updateResult = await strapi.db.query('plugin::coupon.coupon').update({
|
|
222
|
+
where: {
|
|
223
|
+
id: coupon.id,
|
|
224
|
+
currentUsage: coupon.currentUsage, // Optimistic lock
|
|
225
|
+
},
|
|
226
|
+
data: {
|
|
227
|
+
currentUsage: coupon.currentUsage + 1,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
// If update failed due to concurrent modification, check usage limit
|
|
231
|
+
if (!updateResult || (coupon.maxUsage && coupon.currentUsage + 1 > coupon.maxUsage)) {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
errorCode: exports.ERROR_CODES.USAGE_LIMIT_REACHED,
|
|
235
|
+
message: exports.ERROR_MESSAGES.USAGE_LIMIT_REACHED,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
// Create redemption record
|
|
239
|
+
const redemption = await strapi.db.query('plugin::coupon.redemption').create({
|
|
240
|
+
data: {
|
|
241
|
+
coupon: coupon.id,
|
|
242
|
+
phoneNumber,
|
|
243
|
+
orderId,
|
|
244
|
+
discountType: coupon.discountType,
|
|
245
|
+
discountValue: coupon.discountValue,
|
|
246
|
+
redemptionDate: new Date(),
|
|
247
|
+
metadata: {
|
|
248
|
+
orderAmount,
|
|
249
|
+
finalAmount,
|
|
250
|
+
discountAmount,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
redemptionId = `redeem_${redemption.id}`;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
strapi.log.error('Transaction error during redemption:', error);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
success: true,
|
|
262
|
+
message: 'Coupon redeemed successfully.',
|
|
263
|
+
redemptionId,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
strapi.log.error('Error redeeming coupon:', error);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
/**
|
|
272
|
+
* Create a new coupon (Admin only)
|
|
273
|
+
*/
|
|
274
|
+
async create(params) {
|
|
275
|
+
try {
|
|
276
|
+
const { code, discountType, discountValue, maxUsage, validFrom, validTo, description, userRestrictions, } = params;
|
|
277
|
+
// Validate coupon code
|
|
278
|
+
if (!(0, validators_1.validateCouponCode)(code)) {
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
errorCode: 'INVALID_COUPON_CODE',
|
|
282
|
+
message: 'Coupon code must be 3-50 uppercase alphanumeric characters',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// Validate discount value
|
|
286
|
+
const discountValidation = (0, validators_1.validateDiscountValue)(discountType, discountValue);
|
|
287
|
+
if (!discountValidation.valid) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
errorCode: 'INVALID_DISCOUNT',
|
|
291
|
+
message: discountValidation.error || 'Invalid discount value',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
// Validate date range
|
|
295
|
+
const dateValidation = (0, validators_1.validateDateRange)(validFrom, validTo);
|
|
296
|
+
if (!dateValidation.valid) {
|
|
297
|
+
return {
|
|
298
|
+
success: false,
|
|
299
|
+
errorCode: 'INVALID_DATE_RANGE',
|
|
300
|
+
message: dateValidation.error || 'Invalid date range',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// Validate maxUsage
|
|
304
|
+
if (maxUsage !== undefined && (typeof maxUsage !== 'number' || maxUsage < 1)) {
|
|
305
|
+
return {
|
|
306
|
+
success: false,
|
|
307
|
+
errorCode: 'INVALID_MAX_USAGE',
|
|
308
|
+
message: 'maxUsage must be a positive number',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Check if coupon code already exists
|
|
312
|
+
const existingCoupons = await strapi.entityService.findMany('plugin::coupon.coupon', {
|
|
313
|
+
filters: {
|
|
314
|
+
code,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
if (Array.isArray(existingCoupons) && existingCoupons.length > 0) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
errorCode: 'COUPON_ALREADY_EXISTS',
|
|
321
|
+
message: 'A coupon with this code already exists.',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
// Create the coupon
|
|
325
|
+
const coupon = await strapi.entityService.create('plugin::coupon.coupon', {
|
|
326
|
+
data: {
|
|
327
|
+
code,
|
|
328
|
+
discountType,
|
|
329
|
+
discountValue,
|
|
330
|
+
maxUsage: maxUsage || null,
|
|
331
|
+
currentUsage: 0,
|
|
332
|
+
validFrom,
|
|
333
|
+
validTo,
|
|
334
|
+
isActive: true,
|
|
335
|
+
description: description || null,
|
|
336
|
+
userRestrictions: userRestrictions || null,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
success: true,
|
|
341
|
+
couponId: `coupon_${coupon.id}`,
|
|
342
|
+
message: 'Coupon created successfully.',
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
strapi.log.error('Error creating coupon:', error);
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
/**
|
|
351
|
+
* Get all coupons
|
|
352
|
+
*/
|
|
353
|
+
async findAll(query = {}) {
|
|
354
|
+
try {
|
|
355
|
+
return await strapi.entityService.findMany('plugin::coupon.coupon', query);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
strapi.log.error('Error finding coupons:', error);
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* Get a single coupon
|
|
364
|
+
*/
|
|
365
|
+
async findOne(id) {
|
|
366
|
+
try {
|
|
367
|
+
return await strapi.entityService.findOne('plugin::coupon.coupon', id);
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
strapi.log.error('Error finding coupon:', error);
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
/**
|
|
375
|
+
* Update a coupon
|
|
376
|
+
*/
|
|
377
|
+
async update(id, data) {
|
|
378
|
+
try {
|
|
379
|
+
return await strapi.entityService.update('plugin::coupon.coupon', id, { data });
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
strapi.log.error('Error updating coupon:', error);
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
/**
|
|
387
|
+
* Delete a coupon
|
|
388
|
+
*/
|
|
389
|
+
async delete(id) {
|
|
390
|
+
try {
|
|
391
|
+
return await strapi.entityService.delete('plugin::coupon.coupon', id);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
strapi.log.error('Error deleting coupon:', error);
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
exports.default = couponService;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const coupon_1 = __importDefault(require("./coupon"));
|
|
7
|
+
exports.default = {
|
|
8
|
+
coupon: coupon_1.default,
|
|
9
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const PHONE_REGEX: RegExp;
|
|
2
|
+
export declare const ORDER_ID_REGEX: RegExp;
|
|
3
|
+
export declare const COUPON_CODE_REGEX: RegExp;
|
|
4
|
+
export declare function validatePhoneNumber(phone: string): boolean;
|
|
5
|
+
export declare function validateOrderId(orderId: string): boolean;
|
|
6
|
+
export declare function validateCouponCode(code: string): boolean;
|
|
7
|
+
export declare function validateDiscountValue(discountType: string, discountValue: number): {
|
|
8
|
+
valid: boolean;
|
|
9
|
+
error?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function validateDateRange(validFrom: string, validTo: string): {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Input validation utilities
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.COUPON_CODE_REGEX = exports.ORDER_ID_REGEX = exports.PHONE_REGEX = void 0;
|
|
5
|
+
exports.validatePhoneNumber = validatePhoneNumber;
|
|
6
|
+
exports.validateOrderId = validateOrderId;
|
|
7
|
+
exports.validateCouponCode = validateCouponCode;
|
|
8
|
+
exports.validateDiscountValue = validateDiscountValue;
|
|
9
|
+
exports.validateDateRange = validateDateRange;
|
|
10
|
+
exports.PHONE_REGEX = /^\+?[1-9]\d{1,14}$/; // E.164 format
|
|
11
|
+
exports.ORDER_ID_REGEX = /^[a-zA-Z0-9_-]{1,100}$/;
|
|
12
|
+
exports.COUPON_CODE_REGEX = /^[A-Z0-9]{3,50}$/;
|
|
13
|
+
function validatePhoneNumber(phone) {
|
|
14
|
+
return typeof phone === 'string' && phone.length >= 10 && phone.length <= 15 && exports.PHONE_REGEX.test(phone);
|
|
15
|
+
}
|
|
16
|
+
function validateOrderId(orderId) {
|
|
17
|
+
return typeof orderId === 'string' && orderId.length >= 1 && orderId.length <= 100 && exports.ORDER_ID_REGEX.test(orderId);
|
|
18
|
+
}
|
|
19
|
+
function validateCouponCode(code) {
|
|
20
|
+
return typeof code === 'string' && code.length >= 3 && code.length <= 50 && exports.COUPON_CODE_REGEX.test(code);
|
|
21
|
+
}
|
|
22
|
+
function validateDiscountValue(discountType, discountValue) {
|
|
23
|
+
if (typeof discountValue !== 'number' || discountValue <= 0) {
|
|
24
|
+
return { valid: false, error: 'Discount value must be greater than 0' };
|
|
25
|
+
}
|
|
26
|
+
if (discountType === 'percentage' && discountValue > 100) {
|
|
27
|
+
return { valid: false, error: 'Percentage discount cannot exceed 100%' };
|
|
28
|
+
}
|
|
29
|
+
return { valid: true };
|
|
30
|
+
}
|
|
31
|
+
function validateDateRange(validFrom, validTo) {
|
|
32
|
+
const validFromDate = new Date(validFrom);
|
|
33
|
+
const validToDate = new Date(validTo);
|
|
34
|
+
if (isNaN(validFromDate.getTime()) || isNaN(validToDate.getTime())) {
|
|
35
|
+
return { valid: false, error: 'Invalid date format' };
|
|
36
|
+
}
|
|
37
|
+
if (validFromDate >= validToDate) {
|
|
38
|
+
return { valid: false, error: 'validFrom must be before validTo' };
|
|
39
|
+
}
|
|
40
|
+
return { valid: true };
|
|
41
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mumz-strapi-plugin-coupon",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "Strapi plugin for centralized coupon management across multiple services",
|
|
5
|
+
"strapi": {
|
|
6
|
+
"name": "coupon",
|
|
7
|
+
"displayName": "Coupon Management",
|
|
8
|
+
"description": "Centralized coupon management system for validation, redemption, and tracking",
|
|
9
|
+
"kind": "plugin"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"strapi",
|
|
13
|
+
"plugin",
|
|
14
|
+
"coupon",
|
|
15
|
+
"discount",
|
|
16
|
+
"promotion"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "Mumzworld Tech"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/mumzworld-tech/strapi-plugin-coupon.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/mumzworld-tech/strapi-plugin-coupon/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/mumzworld-tech/strapi-plugin-coupon#readme",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"watch": "tsc --watch",
|
|
36
|
+
"clean": "rimraf dist",
|
|
37
|
+
"prepare": "npm run build",
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
39
|
+
"type-check": "tsc --noEmit"
|
|
40
|
+
},
|
|
41
|
+
"main": "./strapi-server.js",
|
|
42
|
+
"exports": {
|
|
43
|
+
"./strapi-server": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"require": "./dist/index.js",
|
|
46
|
+
"default": "./dist/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./package.json": "./package.json"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"strapi-admin.js",
|
|
53
|
+
"strapi-server.js"
|
|
54
|
+
],
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"typescript": "^5.0.0",
|
|
57
|
+
"rimraf": "^5.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@strapi/strapi": "^5.0.0",
|
|
61
|
+
"@strapi/typescript-utils": "^5.0.0",
|
|
62
|
+
"@types/node": "^20.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@strapi/strapi": "^5.0.0"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.0.0",
|
|
69
|
+
"npm": ">=6.0.0"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/strapi-server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist').default;
|