@cloudcommerce/app-discounts 0.0.54 → 0.0.57
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/.turbo/turbo-build.log +2 -2
- package/lib/discounts.d.ts +1 -1
- package/lib/discounts.js +2 -3
- package/lib/discounts.js.map +1 -1
- package/lib-cjs/apply-discount.cjs +330 -0
- package/lib-cjs/helpers.cjs +167 -0
- package/package.json +5 -4
- package/src/discounts.ts +2 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
[33m@cloudcommerce/app-discounts:build[0m: cache hit, replaying output [
|
|
1
|
+
[33m@cloudcommerce/app-discounts:build[0m: cache hit, replaying output [2m19577ff5408a53e1[0m
|
|
2
2
|
[33m@cloudcommerce/app-discounts:build: [0m
|
|
3
|
-
[33m@cloudcommerce/app-discounts:build: [0m> @cloudcommerce/app-discounts@0.0.
|
|
3
|
+
[33m@cloudcommerce/app-discounts:build: [0m> @cloudcommerce/app-discounts@0.0.56 build /home/leo/code/ecomplus/cloud-commerce/packages/apps/discounts
|
|
4
4
|
[33m@cloudcommerce/app-discounts:build: [0m> sh ../../../scripts/build-lib.sh
|
|
5
5
|
[33m@cloudcommerce/app-discounts:build: [0m
|
package/lib/discounts.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { AppModuleBody } from '@cloudcommerce/types';
|
|
2
|
-
export declare const applyDiscount: (modBody: AppModuleBody) => Promise<
|
|
2
|
+
export declare const applyDiscount: (modBody: AppModuleBody) => Promise<any>;
|
package/lib/discounts.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as handleApplyDiscount from '../lib-cjs/apply-discount.cjs';
|
|
2
2
|
|
|
3
3
|
export const applyDiscount = async (modBody) => {
|
|
4
|
-
|
|
5
|
-
return {};
|
|
4
|
+
return handleApplyDiscount(modBody);
|
|
6
5
|
};
|
|
7
6
|
// # sourceMappingURL=discounts.js.map
|
package/lib/discounts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discounts.js","sourceRoot":"","sources":["../src/discounts.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"discounts.js","sourceRoot":"","sources":["../src/discounts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,mBAAmB,MAAM,+BAA+B,CAAC;AAErE,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,OAAsB,EAAE,EAAE;IAC5D,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC,CAAC"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
const ecomUtils = require('@ecomplus/utils');
|
|
2
|
+
const api = require('@cloudcommerce/api');
|
|
3
|
+
const getEnv = require('@cloudcommerce/firebase/lib/env');
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
validateDateRange,
|
|
7
|
+
validateCustomerId,
|
|
8
|
+
checkOpenPromotion,
|
|
9
|
+
getValidDiscountRules,
|
|
10
|
+
matchDiscountRule,
|
|
11
|
+
checkCampaignProducts,
|
|
12
|
+
} = require('./helpers.cjs');
|
|
13
|
+
|
|
14
|
+
module.exports = async ({ params, application }) => {
|
|
15
|
+
// app configured options
|
|
16
|
+
const config = {
|
|
17
|
+
...application.data,
|
|
18
|
+
...application.hidden_data,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// setup response object
|
|
22
|
+
// https://apx-mods.e-com.plus/api/v1/apply_discount/response_schema.json?store_id=100
|
|
23
|
+
const response = {};
|
|
24
|
+
const respondSuccess = () => {
|
|
25
|
+
if (response.available_extra_discount && !response.available_extra_discount.value) {
|
|
26
|
+
delete response.available_extra_discount;
|
|
27
|
+
}
|
|
28
|
+
if (
|
|
29
|
+
response.discount_rule
|
|
30
|
+
&& (!response.discount_rule.extra_discount || !response.discount_rule.extra_discount.value)
|
|
31
|
+
) {
|
|
32
|
+
delete response.discount_rule;
|
|
33
|
+
}
|
|
34
|
+
return response;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const addDiscount = (discount, flag, label) => {
|
|
38
|
+
let value;
|
|
39
|
+
const maxDiscount = params.amount[discount.apply_at || 'total'];
|
|
40
|
+
if (maxDiscount) {
|
|
41
|
+
// update amount discount and total
|
|
42
|
+
if (discount.type === 'percentage') {
|
|
43
|
+
value = maxDiscount * (discount.value / 100);
|
|
44
|
+
} else {
|
|
45
|
+
value = discount.value;
|
|
46
|
+
}
|
|
47
|
+
if (value > maxDiscount) {
|
|
48
|
+
value = maxDiscount;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (value) {
|
|
53
|
+
if (response.discount_rule) {
|
|
54
|
+
// accumulate discount
|
|
55
|
+
const extraDiscount = response.discount_rule.extra_discount;
|
|
56
|
+
extraDiscount.value += value;
|
|
57
|
+
if (extraDiscount.flags.length < 20) {
|
|
58
|
+
extraDiscount.flags.push(flag);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
response.discount_rule = {
|
|
62
|
+
label: label || flag,
|
|
63
|
+
extra_discount: {
|
|
64
|
+
value,
|
|
65
|
+
flags: [flag],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (params.items && params.items.length) {
|
|
75
|
+
// try product kit discounts first
|
|
76
|
+
if (Array.isArray(config.product_kit_discounts)) {
|
|
77
|
+
config.product_kit_discounts = config.product_kit_discounts.map((kitDiscount) => {
|
|
78
|
+
if (!kitDiscount.product_ids) {
|
|
79
|
+
// kit with any items
|
|
80
|
+
kitDiscount.product_ids = [];
|
|
81
|
+
}
|
|
82
|
+
return kitDiscount;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const kitDiscounts = getValidDiscountRules(config.product_kit_discounts, params, params.items)
|
|
86
|
+
.sort((a, b) => {
|
|
87
|
+
if (a.min_quantity > b.min_quantity) {
|
|
88
|
+
return -1;
|
|
89
|
+
}
|
|
90
|
+
if (b.min_quantity > a.min_quantity) {
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
if (a.discount.min_amount > b.discount.min_amount) {
|
|
94
|
+
return -1;
|
|
95
|
+
}
|
|
96
|
+
if (b.discount.min_amount > a.discount.min_amount) {
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
return 0;
|
|
100
|
+
});
|
|
101
|
+
// prevent applying duplicated kit discount for same items
|
|
102
|
+
let discountedItemIds = [];
|
|
103
|
+
|
|
104
|
+
kitDiscounts.forEach((kitDiscount, index) => {
|
|
105
|
+
if (kitDiscount) {
|
|
106
|
+
const productIds = Array.isArray(kitDiscount.product_ids)
|
|
107
|
+
? kitDiscount.product_ids
|
|
108
|
+
: [];
|
|
109
|
+
let kitItems = productIds.length
|
|
110
|
+
? params.items.filter((item) => item.quantity && productIds.indexOf(item.product_id) > -1)
|
|
111
|
+
: params.items;
|
|
112
|
+
kitItems = kitItems.filter((item) => discountedItemIds.indexOf(item._id) === -1);
|
|
113
|
+
if (kitDiscount.min_quantity > 0) {
|
|
114
|
+
// check total items quantity
|
|
115
|
+
let totalQuantity = 0;
|
|
116
|
+
kitItems.forEach(({ quantity }) => {
|
|
117
|
+
totalQuantity += quantity;
|
|
118
|
+
});
|
|
119
|
+
if (totalQuantity < kitDiscount.min_quantity) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (kitDiscount.discount.type === 'fixed' && kitDiscount.cumulative_discount !== false) {
|
|
123
|
+
kitDiscount.discount.value *= Math.floor(totalQuantity / kitDiscount.min_quantity);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!params.amount || !(kitDiscount.discount.min_amount > params.amount.total)) {
|
|
128
|
+
if (kitDiscount.check_all_items !== false) {
|
|
129
|
+
for (let i = 0; i < productIds.length; i++) {
|
|
130
|
+
const productId = productIds[i];
|
|
131
|
+
if (productId
|
|
132
|
+
&& !kitItems.find((item) => item.quantity && item.product_id === productId)) {
|
|
133
|
+
// product not on current cart
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// apply cumulative discount \o/
|
|
139
|
+
addDiscount(kitDiscount.discount, `KIT-${(index + 1)}`, kitDiscount.label);
|
|
140
|
+
discountedItemIds = discountedItemIds.concat(kitItems.map((item) => item.product_id));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// gift products (freebies) campaings
|
|
146
|
+
if (Array.isArray(config.freebies_rules)) {
|
|
147
|
+
const validFreebiesRules = config.freebies_rules.filter((rule) => {
|
|
148
|
+
return validateDateRange(rule)
|
|
149
|
+
&& validateCustomerId(rule, params)
|
|
150
|
+
&& checkCampaignProducts(rule.check_product_ids, params)
|
|
151
|
+
&& Array.isArray(rule.product_ids)
|
|
152
|
+
&& rule.product_ids.length;
|
|
153
|
+
});
|
|
154
|
+
if (validFreebiesRules) {
|
|
155
|
+
let subtotal = 0;
|
|
156
|
+
params.items.forEach((item) => {
|
|
157
|
+
subtotal += (item.quantity * ecomUtils.price(item));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
let bestRule;
|
|
161
|
+
let discountValue = 0;
|
|
162
|
+
for (let i = 0; i < validFreebiesRules.length; i++) {
|
|
163
|
+
const rule = validFreebiesRules[i];
|
|
164
|
+
// start calculating discount
|
|
165
|
+
let value = 0;
|
|
166
|
+
rule.product_ids.forEach((productId) => {
|
|
167
|
+
const item = params.items.find((item) => productId === item.product_id);
|
|
168
|
+
if (item) {
|
|
169
|
+
value += ecomUtils.price(item);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
const fixedSubtotal = subtotal - value;
|
|
173
|
+
if (!bestRule || value > discountValue || bestRule.min_subtotal < rule.min_subtotal) {
|
|
174
|
+
if (!(rule.min_subtotal > fixedSubtotal)) {
|
|
175
|
+
bestRule = rule;
|
|
176
|
+
discountValue = value;
|
|
177
|
+
} else if (!discountValue && subtotal >= rule.min_subtotal) {
|
|
178
|
+
// discount not applicable yet but additional freebies are available
|
|
179
|
+
bestRule = rule;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (bestRule) {
|
|
185
|
+
// provide freebie products \o/
|
|
186
|
+
response.freebie_product_ids = bestRule.product_ids;
|
|
187
|
+
if (discountValue) {
|
|
188
|
+
addDiscount(
|
|
189
|
+
{
|
|
190
|
+
type: 'fixed',
|
|
191
|
+
value: discountValue,
|
|
192
|
+
},
|
|
193
|
+
'FREEBIES',
|
|
194
|
+
bestRule.label,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const discountRules = getValidDiscountRules(config.discount_rules, params);
|
|
203
|
+
if (discountRules.length) {
|
|
204
|
+
const { discountRule, discountMatchEnum } = matchDiscountRule(discountRules, params);
|
|
205
|
+
if (discountRule) {
|
|
206
|
+
if (!checkCampaignProducts(discountRule.product_ids, params)) {
|
|
207
|
+
return {
|
|
208
|
+
available_extra_discount: response.available_extra_discount,
|
|
209
|
+
invalid_coupon_message: params.lang === 'pt_br'
|
|
210
|
+
? 'Nenhum produto da promoção está incluído no carrinho'
|
|
211
|
+
: 'No promotion products are included in the cart',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const excludedProducts = discountRule.excluded_product_ids;
|
|
216
|
+
if (Array.isArray(excludedProducts) && excludedProducts.length && params.items) {
|
|
217
|
+
// must check any excluded product is on cart
|
|
218
|
+
for (let i = 0; i < params.items.length; i++) {
|
|
219
|
+
const item = params.items[i];
|
|
220
|
+
if (item.quantity && excludedProducts.includes(item.product_id)) {
|
|
221
|
+
return {
|
|
222
|
+
available_extra_discount: response.available_extra_discount,
|
|
223
|
+
invalid_coupon_message: params.lang === 'pt_br'
|
|
224
|
+
? `Promoção é inválida para o produto ${item.name}`
|
|
225
|
+
: `Invalid promotion for product ${item.name}`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let { label } = discountRule;
|
|
232
|
+
const { discount } = discountRule;
|
|
233
|
+
if (typeof label !== 'string' || !label) {
|
|
234
|
+
label = params.discount_coupon || `DISCOUNT ${discountMatchEnum}`;
|
|
235
|
+
}
|
|
236
|
+
if (
|
|
237
|
+
discount.apply_at !== 'freight'
|
|
238
|
+
&& (!response.available_extra_discount || !response.available_extra_discount.value
|
|
239
|
+
|| discountRule.default_discount === true || checkOpenPromotion(discountRule))
|
|
240
|
+
) {
|
|
241
|
+
// show current discount rule as available discount to apply
|
|
242
|
+
response.available_extra_discount = {
|
|
243
|
+
label: label.substring(0, 50),
|
|
244
|
+
};
|
|
245
|
+
['min_amount', 'type', 'value'].forEach((field) => {
|
|
246
|
+
if (discount[field]) {
|
|
247
|
+
response.available_extra_discount[field] = discount[field];
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// params object follows list payments request schema:
|
|
253
|
+
// https://apx-mods.e-com.plus/api/v1/apply_discount/schema.json?store_id=100
|
|
254
|
+
if (
|
|
255
|
+
params.amount && params.amount.total > 0
|
|
256
|
+
&& !(discountRule.discount.min_amount > params.amount[discountRule.discount.amount_field || 'total'])
|
|
257
|
+
) {
|
|
258
|
+
if (
|
|
259
|
+
discountRule.cumulative_discount === false
|
|
260
|
+
&& (response.discount_rule || params.amount.discount)
|
|
261
|
+
) {
|
|
262
|
+
// explain discount can't be applied :(
|
|
263
|
+
// https://apx-mods.e-com.plus/api/v1/apply_discount/response_schema.json?store_id=100
|
|
264
|
+
return {
|
|
265
|
+
invalid_coupon_message: params.lang === 'pt_br'
|
|
266
|
+
? 'A promoção não pôde ser aplicada porque este desconto não é cumulativo'
|
|
267
|
+
: 'This discount is not cumulative',
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// we have a discount to apply \o/
|
|
272
|
+
if (addDiscount(discountRule.discount, discountMatchEnum)) {
|
|
273
|
+
// add discount label and description if any
|
|
274
|
+
response.discount_rule.label = label;
|
|
275
|
+
if (discountRule.description) {
|
|
276
|
+
response.discount_rule.description = discountRule.description;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const { customer } = params;
|
|
280
|
+
if (
|
|
281
|
+
customer && customer._id
|
|
282
|
+
&& (discountRule.usage_limit > 0 || discountRule.total_usage_limit > 0)
|
|
283
|
+
) {
|
|
284
|
+
// list orders to check discount usage limits
|
|
285
|
+
// eslint-disable-next-line prefer-template
|
|
286
|
+
const endpoint = 'orders?fields=_id'
|
|
287
|
+
+ `&extra_discount.app.label${(discountRule.case_insensitive ? '%=' : '=')}`
|
|
288
|
+
+ encodeURIComponent(label);
|
|
289
|
+
const usageLimits = [{
|
|
290
|
+
// limit by customer
|
|
291
|
+
query: `&buyers._id=${customer._id}`,
|
|
292
|
+
max: discountRule.usage_limit,
|
|
293
|
+
}, {
|
|
294
|
+
// total limit
|
|
295
|
+
query: '',
|
|
296
|
+
max: discountRule.total_usage_limit,
|
|
297
|
+
}];
|
|
298
|
+
const { apiAuth } = getEnv();
|
|
299
|
+
for (let i = 0; i < usageLimits.length; i++) {
|
|
300
|
+
const { query, max } = usageLimits[i];
|
|
301
|
+
if (max) {
|
|
302
|
+
let countOrders;
|
|
303
|
+
try {
|
|
304
|
+
// send Store API request to list orders with filters
|
|
305
|
+
const { data } = await api.get(`${endpoint}${query}`, { apiAuth });
|
|
306
|
+
countOrders = data.result.length;
|
|
307
|
+
} catch (e) {
|
|
308
|
+
countOrders = max;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (countOrders >= max) {
|
|
312
|
+
// limit reached
|
|
313
|
+
return {
|
|
314
|
+
invalid_coupon_message: params.lang === 'pt_br'
|
|
315
|
+
? 'A promoção não pôde ser aplicada porque já atingiu o limite de usos'
|
|
316
|
+
: 'The promotion could not be applied because it has already reached the usage limit',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return respondSuccess();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// response with no error nor discount applied
|
|
329
|
+
return respondSuccess();
|
|
330
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const ecomUtils = require('@ecomplus/utils');
|
|
2
|
+
|
|
3
|
+
const validateDateRange = (rule) => {
|
|
4
|
+
// filter campaings by date
|
|
5
|
+
const timestamp = Date.now();
|
|
6
|
+
if (rule.date_range) {
|
|
7
|
+
if (rule.date_range.start && new Date(rule.date_range.start).getTime() > timestamp) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (rule.date_range.end && new Date(rule.date_range.end).getTime() < timestamp) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const validateCustomerId = (rule, params) => {
|
|
18
|
+
if (
|
|
19
|
+
Array.isArray(rule.customer_ids)
|
|
20
|
+
&& rule.customer_ids.length
|
|
21
|
+
&& rule.customer_ids.indexOf(params.customer && params.customer._id) === -1
|
|
22
|
+
) {
|
|
23
|
+
// unavailable for current customer
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const checkOpenPromotion = (rule) => {
|
|
30
|
+
return !rule.discount_coupon && !rule.utm_campaign
|
|
31
|
+
&& (!Array.isArray(rule.customer_ids) || !rule.customer_ids.length);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getValidDiscountRules = (discountRules, params, items) => {
|
|
35
|
+
if (Array.isArray(discountRules) && discountRules.length) {
|
|
36
|
+
// validate rules objects
|
|
37
|
+
return discountRules.filter((rule) => {
|
|
38
|
+
if (!rule || !validateCustomerId(rule, params)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(rule.product_ids) && Array.isArray(items)) {
|
|
43
|
+
const checkProductId = (item) => {
|
|
44
|
+
return (!rule.product_ids.length || rule.product_ids.indexOf(item.product_id) > -1);
|
|
45
|
+
};
|
|
46
|
+
// set/add discount value from lowest item price
|
|
47
|
+
let value;
|
|
48
|
+
if (rule.discount_lowest_price) {
|
|
49
|
+
items.forEach((item) => {
|
|
50
|
+
const price = ecomUtils.price(item);
|
|
51
|
+
if (price > 0 && checkProductId(item) && (!value || value > price)) {
|
|
52
|
+
value = price;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} else if (rule.discount_kit_subtotal) {
|
|
56
|
+
value = 0;
|
|
57
|
+
items.forEach((item) => {
|
|
58
|
+
const price = ecomUtils.price(item);
|
|
59
|
+
if (price > 0 && checkProductId(item)) {
|
|
60
|
+
value += price * item.quantity;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (value) {
|
|
65
|
+
if (rule.discount && rule.discount.value) {
|
|
66
|
+
if (rule.discount.type === 'percentage') {
|
|
67
|
+
value *= rule.discount.value / 100;
|
|
68
|
+
} else {
|
|
69
|
+
value += rule.discount.value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
rule.discount = {
|
|
73
|
+
...rule.discount,
|
|
74
|
+
type: 'fixed',
|
|
75
|
+
value,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!rule.discount || !rule.discount.value) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return validateDateRange(rule);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// returns array anyway
|
|
88
|
+
return [];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const matchDiscountRule = (discountRules, params) => {
|
|
92
|
+
// try to match a promotion
|
|
93
|
+
if (params.discount_coupon) {
|
|
94
|
+
// match only by discount coupon
|
|
95
|
+
return {
|
|
96
|
+
discountRule: discountRules.find((rule) => {
|
|
97
|
+
return rule.case_insensitive
|
|
98
|
+
? typeof rule.discount_coupon === 'string'
|
|
99
|
+
&& rule.discount_coupon.toUpperCase() === params.discount_coupon.toUpperCase()
|
|
100
|
+
: rule.discount_coupon === params.discount_coupon;
|
|
101
|
+
}),
|
|
102
|
+
discountMatchEnum: 'COUPON',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// try to match by UTM campaign first
|
|
107
|
+
if (params.utm && params.utm.campaign) {
|
|
108
|
+
const discountRule = discountRules.find((rule) => {
|
|
109
|
+
return rule.case_insensitive
|
|
110
|
+
? typeof rule.utm_campaign === 'string'
|
|
111
|
+
&& rule.utm_campaign.toUpperCase() === params.utm.campaign.toUpperCase()
|
|
112
|
+
: rule.utm_campaign === params.utm.campaign;
|
|
113
|
+
});
|
|
114
|
+
if (discountRule) {
|
|
115
|
+
return {
|
|
116
|
+
discountRule,
|
|
117
|
+
discountMatchEnum: 'UTM',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// then try to match by customer
|
|
123
|
+
if (params.customer && params.customer._id) {
|
|
124
|
+
const discountRule = discountRules.find((rule) => Array.isArray(rule.customer_ids)
|
|
125
|
+
&& rule.customer_ids.indexOf(params.customer._id) > -1);
|
|
126
|
+
if (discountRule) {
|
|
127
|
+
return {
|
|
128
|
+
discountRule,
|
|
129
|
+
discountMatchEnum: 'CUSTOMER',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// last try to match by open promotions
|
|
135
|
+
return {
|
|
136
|
+
discountRule: discountRules.find(checkOpenPromotion),
|
|
137
|
+
discountMatchEnum: 'OPEN',
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const checkCampaignProducts = (campaignProducts, params) => {
|
|
142
|
+
if (Array.isArray(campaignProducts) && campaignProducts.length) {
|
|
143
|
+
// must check at least one campaign product on cart
|
|
144
|
+
let hasProductMatch;
|
|
145
|
+
if (params.items && params.items.length) {
|
|
146
|
+
for (let i = 0; i < campaignProducts.length; i++) {
|
|
147
|
+
if (params.items.find((item) => item.quantity && item.product_id === campaignProducts[i])) {
|
|
148
|
+
hasProductMatch = true;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!hasProductMatch) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
validateDateRange,
|
|
162
|
+
validateCustomerId,
|
|
163
|
+
checkOpenPromotion,
|
|
164
|
+
getValidDiscountRules,
|
|
165
|
+
matchDiscountRule,
|
|
166
|
+
checkCampaignProducts,
|
|
167
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudcommerce/app-discounts",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.57",
|
|
5
5
|
"description": "E-Com Plus Cloud Commerce app for complex discount rules",
|
|
6
6
|
"main": "lib/discounts.js",
|
|
7
7
|
"repository": {
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
},
|
|
17
17
|
"homepage": "https://github.com/ecomplus/cloud-commerce/tree/main/packages/apps/discounts#readme",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@cloudcommerce/api": "0.0.
|
|
20
|
-
"@cloudcommerce/firebase": "0.0.
|
|
19
|
+
"@cloudcommerce/api": "0.0.57",
|
|
20
|
+
"@cloudcommerce/firebase": "0.0.57",
|
|
21
|
+
"@ecomplus/utils": "^1.4.1",
|
|
21
22
|
"firebase-admin": "^11.0.1",
|
|
22
23
|
"firebase-functions": "^3.22.0"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"@cloudcommerce/types": "0.0.
|
|
26
|
+
"@cloudcommerce/types": "0.0.57",
|
|
26
27
|
"@firebase/app-types": "^0.7.0"
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
package/src/discounts.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
2
|
import type { AppModuleBody } from '@cloudcommerce/types';
|
|
3
|
-
import
|
|
3
|
+
import * as handleApplyDiscount from '../lib-cjs/apply-discount.cjs';
|
|
4
4
|
|
|
5
5
|
export const applyDiscount = async (modBody: AppModuleBody) => {
|
|
6
|
-
|
|
7
|
-
return {};
|
|
6
|
+
return handleApplyDiscount(modBody);
|
|
8
7
|
};
|