@zoxllc/shopify-checkout-extensions 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +3 -0
  2. package/package.json +31 -0
  3. package/src/common/AndSelector.ts +35 -0
  4. package/src/common/BuyXGetY.ts +109 -0
  5. package/src/common/Campaign.ts +55 -0
  6. package/src/common/CampaignConfiguration.ts +10 -0
  7. package/src/common/CampaignFactory.ts +180 -0
  8. package/src/common/CartAmountQualifier.ts +64 -0
  9. package/src/common/CartHasItemQualifier.ts +64 -0
  10. package/src/common/CartQuantityQualifier.ts +73 -0
  11. package/src/common/CustomerEmailQualifier.ts +60 -0
  12. package/src/common/CustomerSubscriberQualifier.ts +32 -0
  13. package/src/common/CustomerTagQualifier.ts +56 -0
  14. package/src/common/DiscountCart.ts +130 -0
  15. package/src/common/DiscountInterface.ts +13 -0
  16. package/src/common/OrSelector.ts +35 -0
  17. package/src/common/PostCartAmountQualifier.ts +21 -0
  18. package/src/common/ProductHandleSelector.ts +29 -0
  19. package/src/common/ProductIdSelector.ts +28 -0
  20. package/src/common/ProductTagSelector.ts +54 -0
  21. package/src/common/ProductTypeSelector.ts +34 -0
  22. package/src/common/Qualifier.ts +67 -0
  23. package/src/common/SaleItemSelector.ts +35 -0
  24. package/src/common/Selector.ts +53 -0
  25. package/src/common/SubscriptionItemSelector.ts +21 -0
  26. package/src/generated/api.ts +2103 -0
  27. package/src/index.ts +39 -0
  28. package/src/lineItem/ConditionalDiscount.ts +102 -0
  29. package/src/lineItem/DiscountCodeList.ts +91 -0
  30. package/src/lineItem/FixedItemDiscount.ts +53 -0
  31. package/src/lineItem/PercentageDiscount.ts +46 -0
  32. package/tests/AndSelector.test.ts +27 -0
  33. package/tests/CartQuantityQualifier.test.ts +381 -0
  34. package/tests/CustomerSubscriberQualifier.test.ts +101 -0
  35. package/tests/DiscountCart.test.ts +115 -0
  36. package/tests/OrSelector.test.ts +27 -0
  37. package/tests/ProductTagSelector.test.ts +75 -0
  38. package/tests/Qualifier.test.ts +193 -0
  39. package/tests/SaleItemSelector.test.ts +113 -0
  40. package/tests/Selector.test.ts +83 -0
  41. package/tsconfig.json +25 -0
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # ZOX Checkout Extensions lib
2
+
3
+ This package aims to provide a common library for dealing with checkout extensions that deal specifically with product discounts or cart validation checks
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@zoxllc/shopify-checkout-extensions",
3
+ "version": "0.0.1",
4
+ "description": "ZOX helper lib for shopify checkout extensions",
5
+ "main": "src/index.ts",
6
+ "directories": {
7
+ "test": "tests"
8
+ },
9
+ "scripts": {
10
+ "test": "vitest"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/zoxllc/shopify-checkout-extensions.git"
15
+ },
16
+ "author": "Sebastian Nievas",
17
+ "license": "ISC",
18
+ "bugs": {
19
+ "url": "https://github.com/zoxllc/shopify-checkout-extensions/issues"
20
+ },
21
+ "homepage": "https://github.com/zoxllc/shopify-checkout-extensions#readme",
22
+ "dependencies": {
23
+ "@shopify/shopify_function": "0.1.0",
24
+ "@types/lodash": "^4.14.202",
25
+ "javy": "0.1.1",
26
+ "lodash": "^4.17.21"
27
+ },
28
+ "devDependencies": {
29
+ "vitest": "^1.3.1"
30
+ }
31
+ }
@@ -0,0 +1,35 @@
1
+ import type { DiscountCart } from "./DiscountCart";
2
+ import type { Qualifier } from "./Qualifier";
3
+ import type { Selector } from "./Selector";
4
+ import type { CartLine } from "extensions/zox-product-discount/generated/api";
5
+
6
+ export class AndSelector {
7
+ is_a: "AndSelector";
8
+ conditions: (Qualifier | Selector)[];
9
+
10
+ constructor(conditions: (Qualifier | Selector)[]) {
11
+ this.is_a = "AndSelector";
12
+ this.conditions = conditions;
13
+ }
14
+
15
+ match(subject: CartLine | DiscountCart, selector?: Selector) {
16
+ try {
17
+ const conditionsResult = this.conditions
18
+ .map((condition) => {
19
+ if (selector) {
20
+ return (condition as Qualifier).match(
21
+ subject as DiscountCart,
22
+ selector,
23
+ );
24
+ } else {
25
+ return (condition as Selector).match(subject as CartLine);
26
+ }
27
+ })
28
+ .filter((result) => result === true);
29
+
30
+ return conditionsResult.length == this.conditions.length;
31
+ } catch (e) {
32
+ return false;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,109 @@
1
+ import { Campaign } from "./Campaign";
2
+ import type { CartLine } from "../../../../extensions/zox-product-discount/generated/api";
3
+ import type { AndSelector } from "./AndSelector";
4
+ import type { DiscountCart } from "./DiscountCart";
5
+ import type { ApplyDiscount, DiscountInterface } from "./DiscountInterface";
6
+ import type { OrSelector } from "./OrSelector";
7
+ import type { Qualifier, QualifierBehavior } from "./Qualifier";
8
+ import type { Selector } from "./Selector";
9
+
10
+ export class BuyXGetY extends Campaign {
11
+ lineItemSelector?: Selector;
12
+ getItemSelector?: Selector;
13
+ discount: DiscountInterface;
14
+ buyX: number;
15
+ getY: number;
16
+ maxSets?: number;
17
+
18
+ constructor(
19
+ behavior: QualifierBehavior,
20
+ conditions: [AndSelector | OrSelector | Qualifier],
21
+ discount: DiscountInterface,
22
+ buyItemSelector: Selector,
23
+ getItemSelector: Selector,
24
+ buyX: number,
25
+ getY: number,
26
+ maxSets?: number
27
+ ) {
28
+ super(behavior, conditions);
29
+ this.lineItemSelector = buyItemSelector;
30
+ this.getItemSelector = getItemSelector;
31
+ this.discount = discount;
32
+ this.buyX = buyX;
33
+ this.getY = getY;
34
+ this.maxSets = maxSets;
35
+ }
36
+
37
+ run(discountCart: DiscountCart) {
38
+ if (this.qualifies(discountCart)) {
39
+ // Find the items that qualify for buyX
40
+ let applicableBuyItems = discountCart.cart.lines;
41
+ if (this.lineItemSelector) {
42
+ applicableBuyItems = discountCart.cart.lines.filter((lineItem) => {
43
+ return this.lineItemSelector?.match(lineItem as CartLine);
44
+ });
45
+ }
46
+
47
+ if (
48
+ applicableBuyItems.reduce((qty, item) => (qty += item.quantity), 0) <
49
+ this.buyX
50
+ ) {
51
+ return discountCart;
52
+ }
53
+
54
+ // Find the items that qualify for getX
55
+ let eligibleGetItems = discountCart.cart.lines;
56
+ if (this.getItemSelector) {
57
+ eligibleGetItems = discountCart.cart.lines.filter((lineItem) => {
58
+ return this.getItemSelector?.match(lineItem as CartLine);
59
+ });
60
+ }
61
+
62
+ // Check if cart qualifies for discounts and limit the discount sets
63
+ const purchasedQuantity = applicableBuyItems.reduce(
64
+ (total, item) => (total += item.quantity),
65
+ 0
66
+ );
67
+ const discountableSets = this.maxSets
68
+ ? [purchasedQuantity / this.buyX, this.maxSets].sort((a, b) => a - b)[0]
69
+ : Math.floor(purchasedQuantity / this.buyX);
70
+
71
+ if (discountableSets > 0) {
72
+ let discountableQuantity =
73
+ discountableSets *
74
+ (this.getY ? this.getY : discountCart.totalLineItemQuantity());
75
+
76
+ // Apply the discounts (sort to discount lower priced items first)
77
+ eligibleGetItems = eligibleGetItems.sort(
78
+ (item) => item.cost.amountPerQuantity.amount
79
+ );
80
+
81
+ const itemsToApplyDiscounts: ApplyDiscount[] = [];
82
+
83
+ eligibleGetItems.forEach((lineItem) => {
84
+ if (discountableQuantity == 0) {
85
+ return;
86
+ }
87
+
88
+ if (lineItem.quantity <= discountableQuantity) {
89
+ itemsToApplyDiscounts.push({ lineItem } as ApplyDiscount);
90
+ discountableQuantity -= lineItem.quantity;
91
+ } else {
92
+ itemsToApplyDiscounts.push({
93
+ lineItem,
94
+ maxDiscounts: discountableQuantity,
95
+ } as ApplyDiscount);
96
+ discountableQuantity = 0;
97
+ }
98
+ });
99
+
100
+ if (itemsToApplyDiscounts.length) {
101
+ const discount = this.discount.apply(itemsToApplyDiscounts);
102
+ discountCart.addDiscount(discount);
103
+ }
104
+ }
105
+ }
106
+
107
+ return discountCart;
108
+ }
109
+ }
@@ -0,0 +1,55 @@
1
+ import type { AndSelector } from "./AndSelector";
2
+ import type { DiscountCart } from "./DiscountCart";
3
+ import type { OrSelector } from "./OrSelector";
4
+ import type { Selector } from "./Selector";
5
+ import { type Qualifier, QualifierBehavior } from "./Qualifier";
6
+
7
+ interface CampaignInterface {
8
+ runWithHooks(cart: DiscountCart): DiscountCart;
9
+ beforeRun?(cart: DiscountCart): DiscountCart;
10
+ run?(cart: DiscountCart): DiscountCart;
11
+ afterRun(cart: DiscountCart): DiscountCart;
12
+ }
13
+
14
+ export class Campaign implements CampaignInterface {
15
+ behavior: QualifierBehavior;
16
+ qualifiers: [AndSelector | OrSelector | Qualifier];
17
+ lineItemSelector?: Selector;
18
+ message?: string;
19
+
20
+ constructor(
21
+ behavior: QualifierBehavior,
22
+ qualifiers: [AndSelector | OrSelector | Qualifier]
23
+ ) {
24
+ this.behavior = behavior;
25
+ this.qualifiers = qualifiers;
26
+ }
27
+
28
+ qualifies(discountCart: DiscountCart): boolean {
29
+ // I have a feeling this needs to be revised... Check campaign.rb
30
+ const qualifierResults = this.qualifiers.map((qualifier) =>
31
+ qualifier.match(discountCart, this.lineItemSelector)
32
+ );
33
+
34
+ if (this.behavior == QualifierBehavior.ALL) {
35
+ return qualifierResults.filter((i) => i === false).length == 0;
36
+ } else if (this.behavior == QualifierBehavior.ANY) {
37
+ return qualifierResults.filter((i) => i === true).length > 0;
38
+ } else {
39
+ return true;
40
+ }
41
+ }
42
+
43
+ runWithHooks(cart: DiscountCart) {
44
+ this.run(cart);
45
+ return this.afterRun(cart);
46
+ }
47
+
48
+ run(cart: DiscountCart) {
49
+ return cart;
50
+ }
51
+
52
+ afterRun(cart: DiscountCart) {
53
+ return cart;
54
+ }
55
+ }
@@ -0,0 +1,10 @@
1
+ export enum CampaignType {
2
+ ConditionalDiscount = "ConditionalDiscount",
3
+ }
4
+
5
+ export type CampaignConfiguration = {
6
+ priority: number;
7
+ campaignType: CampaignType;
8
+ name: string;
9
+ arguments: string;
10
+ };
@@ -0,0 +1,180 @@
1
+ import { OrSelector } from "./OrSelector";
2
+ import type { Campaign } from "./Campaign";
3
+ import type { DiscountInterface } from "./DiscountInterface";
4
+ import type {
5
+ NumericalComparisonType,
6
+ Qualifier,
7
+ QualifierBehavior,
8
+ QualifierMatchType,
9
+ StringComparisonType,
10
+ } from "./Qualifier";
11
+ import type { MatchType, Selector } from "./Selector";
12
+ import { ConditionalDiscount } from "../lineItem/ConditionalDiscount";
13
+ import { FixedItemDiscount } from "../lineItem/FixedItemDiscount";
14
+ import { PercentageDiscount } from "../lineItem/PercentageDiscount";
15
+ import { AndSelector } from "./AndSelector";
16
+ import { BuyXGetY } from "./BuyXGetY";
17
+ import {
18
+ type CartAmountBehavior,
19
+ CartAmountQualifier,
20
+ } from "./CartAmountQualifier";
21
+ import {
22
+ type CartQuantityBehavior,
23
+ CartQuantityQualifier,
24
+ } from "./CartQuantityQualifier";
25
+ import { CustomerEmailQualifier } from "./CustomerEmailQualifier";
26
+ import { CustomerSubscriberQualifier } from "./CustomerSubscriberQualifier";
27
+ import { CustomerTagQualifier } from "./CustomerTagQualifier";
28
+ import { PostCartAmountQualifier } from "./PostCartAmountQualifier";
29
+ import { ProductIdSelector } from "./ProductIdSelector";
30
+ import { ProductTagSelector } from "./ProductTagSelector";
31
+ import { ProductTypeSelector } from "./ProductTypeSelector";
32
+ import {
33
+ SaleItemSelector,
34
+ type SaleItemSelectorMatchType,
35
+ } from "./SaleItemSelector";
36
+ import { SubscriptionItemSelector } from "./SubscriptionItemSelector";
37
+
38
+ export type InputConfig = {
39
+ __type: string;
40
+ inputs: (
41
+ | boolean
42
+ | number
43
+ | string
44
+ | InputConfig
45
+ | boolean[]
46
+ | number[]
47
+ | string[]
48
+ | InputConfig[]
49
+ )[];
50
+ };
51
+
52
+ export class CampaignFactory {
53
+ static createFromConfig(config: InputConfig) {
54
+ return CampaignFactory.createInputsObject(config) as Campaign;
55
+ }
56
+
57
+ private static parseInputConfig(
58
+ input:
59
+ | boolean
60
+ | number
61
+ | string
62
+ | InputConfig
63
+ | boolean[]
64
+ | number[]
65
+ | string[]
66
+ | InputConfig[]
67
+ ) {
68
+ if (
69
+ typeof input == "string" ||
70
+ typeof input == "number" ||
71
+ typeof input == "boolean" ||
72
+ input === null
73
+ ) {
74
+ return input;
75
+ } else {
76
+ return CampaignFactory.createInputsObject(input as InputConfig);
77
+ }
78
+ }
79
+
80
+ static createInputsObject(
81
+ config: InputConfig
82
+ ):
83
+ | Campaign
84
+ | Qualifier
85
+ | AndSelector
86
+ | OrSelector
87
+ | Selector
88
+ | DiscountInterface
89
+ | undefined {
90
+ const args = config.inputs.map((input) => {
91
+ if (Array.isArray(input)) {
92
+ return input.map((i) => CampaignFactory.parseInputConfig(i));
93
+ } else {
94
+ return CampaignFactory.parseInputConfig(input);
95
+ }
96
+ });
97
+
98
+ switch (config.__type) {
99
+ case "ConditionalDiscount":
100
+ return new ConditionalDiscount(
101
+ args[0] as QualifierBehavior,
102
+ args[1] as [AndSelector | OrSelector | Qualifier],
103
+ args[2] as DiscountInterface,
104
+ args[3] as Selector,
105
+ args[4] as number
106
+ );
107
+ case "BuyXGetY":
108
+ return new BuyXGetY(
109
+ args[0] as QualifierBehavior,
110
+ args[1] as [AndSelector | OrSelector | Qualifier],
111
+ args[2] as DiscountInterface,
112
+ args[3] as Selector,
113
+ args[4] as Selector,
114
+ args[5] as number,
115
+ args[6] as number,
116
+ args[7] as number
117
+ );
118
+ case "AndSelector":
119
+ return new AndSelector([...args] as (Selector | Qualifier)[]);
120
+ case "OrSelector":
121
+ return new OrSelector([...args] as (Selector | Qualifier)[]);
122
+ case "PostCartAmountQualifier":
123
+ return new PostCartAmountQualifier(
124
+ args[0] as NumericalComparisonType,
125
+ args[1] as number
126
+ );
127
+ case "CartAmountQualifier":
128
+ return new CartAmountQualifier(
129
+ args[0] as CartAmountBehavior,
130
+ args[1] as NumericalComparisonType,
131
+ args[2] as number
132
+ );
133
+ case "CartQuantityQualifier":
134
+ return new CartQuantityQualifier(
135
+ args[0] as CartQuantityBehavior,
136
+ args[1] as NumericalComparisonType,
137
+ args[2] as number
138
+ );
139
+ case "SaleItemSelector":
140
+ return new SaleItemSelector(args[0] as SaleItemSelectorMatchType);
141
+ case "ProductIdSelector":
142
+ return new ProductIdSelector(args[0] as MatchType, args[1] as string[]);
143
+ case "ProductTypeSelector":
144
+ return new ProductTypeSelector(
145
+ args[0] as MatchType,
146
+ args[1] as string[]
147
+ );
148
+ case "ProductTagSelector":
149
+ return new ProductTagSelector(
150
+ args[0] as QualifierMatchType,
151
+ args[1] as StringComparisonType,
152
+ args[2] as string[]
153
+ );
154
+ case "PercentageDiscount":
155
+ return new PercentageDiscount(args[0] as number, args[1] as string);
156
+ case "FixedItemDiscount":
157
+ return new FixedItemDiscount(
158
+ args[0] as number,
159
+ args[1] as boolean,
160
+ args[2] as string
161
+ );
162
+ case "SubscriptionItemSelector":
163
+ return new SubscriptionItemSelector(args[0] as MatchType);
164
+ case "CustomerSubscriberQualifier":
165
+ return new CustomerSubscriberQualifier(args[0] as QualifierMatchType);
166
+ case "CustomerEmailQualifier":
167
+ return new CustomerEmailQualifier(
168
+ args[0] as QualifierMatchType,
169
+ args[1] as StringComparisonType,
170
+ args[2] as string[]
171
+ );
172
+ case "CustomerTagQualifier":
173
+ return new CustomerTagQualifier(
174
+ args[0] as QualifierMatchType,
175
+ args[1] as StringComparisonType,
176
+ args[2] as string[]
177
+ );
178
+ }
179
+ }
180
+ }
@@ -0,0 +1,64 @@
1
+ import type { DiscountCart } from "./DiscountCart";
2
+ import { type NumericalComparisonType, Qualifier } from "./Qualifier";
3
+ import { type Selector } from "./Selector";
4
+
5
+ export enum CartAmountBehavior {
6
+ CART = ":cart",
7
+ ITEM = ":item",
8
+ DIFF_CART = ":diff_cart",
9
+ DIFF_ITEM = ":diff_item",
10
+ }
11
+
12
+ export class CartAmountQualifier extends Qualifier {
13
+ behavior: CartAmountBehavior;
14
+ comparisonType: NumericalComparisonType;
15
+ amount: number;
16
+
17
+ constructor(
18
+ behavior: CartAmountBehavior,
19
+ comparisonType: NumericalComparisonType,
20
+ amount: number
21
+ ) {
22
+ super();
23
+ this.behavior = behavior;
24
+ this.comparisonType = comparisonType;
25
+ this.amount = amount;
26
+ }
27
+
28
+ /**
29
+ * Determines if the carts amount
30
+ * @param cart
31
+ * @param selector
32
+ */
33
+ match(discountCart: DiscountCart, selector?: Selector) {
34
+ let total =
35
+ parseFloat(discountCart.cart.cost.subtotalAmount.amount) -
36
+ discountCart.appliedDiscountTotal;
37
+
38
+ switch (this.behavior) {
39
+ case CartAmountBehavior.CART:
40
+ case CartAmountBehavior.ITEM:
41
+ return this.compareAmounts(total, this.comparisonType, this.amount);
42
+ case CartAmountBehavior.DIFF_CART:
43
+ return this.compareAmounts(
44
+ parseFloat(discountCart.cart.cost.subtotalAmount.amount) -
45
+ this.amount,
46
+ this.comparisonType,
47
+ total
48
+ );
49
+ case CartAmountBehavior.DIFF_ITEM:
50
+ const originalLineTotal = discountCart.cart.lines.reduce(
51
+ (runningTotal: number, line) => {
52
+ runningTotal += parseFloat(line.cost.subtotalAmount.amount);
53
+ return runningTotal;
54
+ },
55
+ total
56
+ );
57
+ return this.compareAmounts(
58
+ originalLineTotal - this.amount,
59
+ this.comparisonType,
60
+ total
61
+ );
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,64 @@
1
+ import type { CartLine } from "extensions/zox-product-discount/generated/api";
2
+ import type { DiscountCart } from "./DiscountCart";
3
+ import { type NumericalComparisonType, Qualifier } from "./Qualifier";
4
+ import { type Selector } from "./Selector";
5
+
6
+ export enum CartHasItemBehavior {
7
+ QUANTITY = ":quantity",
8
+ SUBTOTAL = ":subtotal",
9
+ }
10
+
11
+ export class CartHasItemQualifier extends Qualifier {
12
+ quantityOrSubtotal: CartHasItemBehavior;
13
+ comparisonType: NumericalComparisonType;
14
+ amount: number;
15
+ itemSelector: Selector;
16
+
17
+ constructor(
18
+ quantityOrSubtotal: CartHasItemBehavior,
19
+ comparisonType: NumericalComparisonType,
20
+ amount: number,
21
+ itemSelector: Selector
22
+ ) {
23
+ super();
24
+ this.quantityOrSubtotal = quantityOrSubtotal;
25
+ this.comparisonType = comparisonType;
26
+ this.amount =
27
+ this.quantityOrSubtotal == CartHasItemBehavior.SUBTOTAL
28
+ ? amount
29
+ : Math.floor(amount);
30
+ this.itemSelector = itemSelector;
31
+ }
32
+
33
+ /**
34
+ * Determines if the carts amount
35
+ * @param cart
36
+ * @param selector
37
+ */
38
+ match(discountCart: DiscountCart, selector?: Selector) {
39
+ let total = 0;
40
+
41
+ switch (this.quantityOrSubtotal) {
42
+ case CartHasItemBehavior.QUANTITY:
43
+ total = discountCart.cart.lines.reduce((quantity, line) => {
44
+ return (
45
+ quantity +
46
+ (this.itemSelector.match(line as CartLine) ? line.quantity : 0)
47
+ );
48
+ }, 0);
49
+ break;
50
+ case CartHasItemBehavior.SUBTOTAL:
51
+ total = discountCart.cart.lines.reduce((amount, line) => {
52
+ return (
53
+ amount +
54
+ (this.itemSelector.match(line as CartLine)
55
+ ? line.cost.subtotalAmount.amount
56
+ : 0)
57
+ );
58
+ }, 0);
59
+ break;
60
+ }
61
+
62
+ return this.compareAmounts(total, this.comparisonType, this.amount);
63
+ }
64
+ }
@@ -0,0 +1,73 @@
1
+ import type { CartLine } from "extensions/zox-product-discount/generated/api";
2
+ import type { DiscountCart } from "./DiscountCart";
3
+ import { type NumericalComparisonType, Qualifier } from "./Qualifier";
4
+ import { type Selector } from "./Selector";
5
+
6
+ export enum CartQuantityBehavior {
7
+ CART = ":cart",
8
+ ITEM = ":item",
9
+ LINE_ANY = ":line_any",
10
+ LINE_ALL = ":line_all",
11
+ }
12
+
13
+ export class CartQuantityQualifier extends Qualifier {
14
+ totalMethod: CartQuantityBehavior;
15
+ comparisonType: NumericalComparisonType;
16
+ quantity: number;
17
+
18
+ constructor(
19
+ totalMethod: CartQuantityBehavior,
20
+ comparisonType: NumericalComparisonType,
21
+ quantity: number
22
+ ) {
23
+ super();
24
+ this.totalMethod = totalMethod;
25
+ this.comparisonType = comparisonType;
26
+ this.quantity = quantity;
27
+ }
28
+
29
+ /**
30
+ * Determines if the carts amount
31
+ * @param cart
32
+ * @param selector
33
+ */
34
+ match(discountCart: DiscountCart, selector?: Selector) {
35
+ let total = 0;
36
+
37
+ switch (this.totalMethod) {
38
+ case CartQuantityBehavior.ITEM:
39
+ total = discountCart.cart.lines.reduce((totalQty, line) => {
40
+ return (
41
+ totalQty +
42
+ ((selector ? selector.match(line as CartLine) : true)
43
+ ? line.quantity
44
+ : 0)
45
+ );
46
+ }, 0);
47
+ break;
48
+ case CartQuantityBehavior.CART:
49
+ total = discountCart.totalLineItemQuantityExcludingGifts();
50
+ break;
51
+ }
52
+
53
+ if (
54
+ this.totalMethod == CartQuantityBehavior.LINE_ANY ||
55
+ this.totalMethod == CartQuantityBehavior.LINE_ALL
56
+ ) {
57
+ const qualifiedItems = discountCart.cart.lines
58
+ .filter((line) => (selector ? selector.match(line as CartLine) : true))
59
+ .map((line) =>
60
+ this.compareAmounts(line.quantity, this.comparisonType, this.quantity)
61
+ );
62
+
63
+ switch (this.totalMethod) {
64
+ case CartQuantityBehavior.LINE_ANY:
65
+ return qualifiedItems.indexOf(true) > -1;
66
+ case CartQuantityBehavior.LINE_ALL:
67
+ return qualifiedItems.indexOf(false) == -1;
68
+ }
69
+ } else {
70
+ return this.compareAmounts(total, this.comparisonType, this.quantity);
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,60 @@
1
+ import type { DiscountCart } from "./DiscountCart";
2
+ import {
3
+ StringComparisonType,
4
+ QualifierMatchType,
5
+ Qualifier,
6
+ } from "./Qualifier";
7
+ import { type Selector } from "./Selector";
8
+
9
+ export class CustomerEmailQualifier extends Qualifier {
10
+ invert: boolean;
11
+ matchCondition: StringComparisonType;
12
+ emails: string[];
13
+
14
+ constructor(
15
+ matchType: QualifierMatchType,
16
+ matchCondition: StringComparisonType,
17
+ emails: string[]
18
+ ) {
19
+ super();
20
+ this.invert = matchType == QualifierMatchType.DOES_NOT;
21
+ this.matchCondition = matchCondition;
22
+ this.emails = emails.map((e) => e.toLowerCase());
23
+ }
24
+
25
+ /**
26
+ * Determines if the customer's email matches
27
+ * @param discountCart DiscountCart
28
+ * @param selector Selector
29
+ */
30
+ match(discountCart: DiscountCart, selector?: Selector) {
31
+ if (!discountCart.cart.buyerIdentity?.customer?.email) {
32
+ return false;
33
+ }
34
+
35
+ let matchResult = false;
36
+
37
+ const email = discountCart.cart.buyerIdentity.customer.email;
38
+
39
+ switch (this.matchCondition) {
40
+ case StringComparisonType.MATCH:
41
+ matchResult = this.emails.includes(email);
42
+ break;
43
+ case StringComparisonType.CONTAINS:
44
+ matchResult = this.emails.filter((e) => e.includes(email)).length > 0;
45
+ break;
46
+ case StringComparisonType.START_WITH:
47
+ matchResult = this.emails.filter((e) => e.startsWith(email)).length > 0;
48
+ break;
49
+ case StringComparisonType.END_WITH:
50
+ matchResult = this.emails.filter((e) => e.endsWith(email)).length > 0;
51
+ break;
52
+ }
53
+
54
+ if (this.invert) {
55
+ return !matchResult;
56
+ }
57
+
58
+ return matchResult;
59
+ }
60
+ }