@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.
- package/README.md +3 -0
- package/package.json +31 -0
- package/src/common/AndSelector.ts +35 -0
- package/src/common/BuyXGetY.ts +109 -0
- package/src/common/Campaign.ts +55 -0
- package/src/common/CampaignConfiguration.ts +10 -0
- package/src/common/CampaignFactory.ts +180 -0
- package/src/common/CartAmountQualifier.ts +64 -0
- package/src/common/CartHasItemQualifier.ts +64 -0
- package/src/common/CartQuantityQualifier.ts +73 -0
- package/src/common/CustomerEmailQualifier.ts +60 -0
- package/src/common/CustomerSubscriberQualifier.ts +32 -0
- package/src/common/CustomerTagQualifier.ts +56 -0
- package/src/common/DiscountCart.ts +130 -0
- package/src/common/DiscountInterface.ts +13 -0
- package/src/common/OrSelector.ts +35 -0
- package/src/common/PostCartAmountQualifier.ts +21 -0
- package/src/common/ProductHandleSelector.ts +29 -0
- package/src/common/ProductIdSelector.ts +28 -0
- package/src/common/ProductTagSelector.ts +54 -0
- package/src/common/ProductTypeSelector.ts +34 -0
- package/src/common/Qualifier.ts +67 -0
- package/src/common/SaleItemSelector.ts +35 -0
- package/src/common/Selector.ts +53 -0
- package/src/common/SubscriptionItemSelector.ts +21 -0
- package/src/generated/api.ts +2103 -0
- package/src/index.ts +39 -0
- package/src/lineItem/ConditionalDiscount.ts +102 -0
- package/src/lineItem/DiscountCodeList.ts +91 -0
- package/src/lineItem/FixedItemDiscount.ts +53 -0
- package/src/lineItem/PercentageDiscount.ts +46 -0
- package/tests/AndSelector.test.ts +27 -0
- package/tests/CartQuantityQualifier.test.ts +381 -0
- package/tests/CustomerSubscriberQualifier.test.ts +101 -0
- package/tests/DiscountCart.test.ts +115 -0
- package/tests/OrSelector.test.ts +27 -0
- package/tests/ProductTagSelector.test.ts +75 -0
- package/tests/Qualifier.test.ts +193 -0
- package/tests/SaleItemSelector.test.ts +113 -0
- package/tests/Selector.test.ts +83 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DiscountCart } from "./DiscountCart";
|
|
2
|
+
import { QualifierMatchType, Qualifier } from "./Qualifier";
|
|
3
|
+
import { type Selector } from "./Selector";
|
|
4
|
+
|
|
5
|
+
export class CustomerSubscriberQualifier extends Qualifier {
|
|
6
|
+
invert: boolean;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Whether the customer contains an active subscription or not.
|
|
10
|
+
* @param matchType Pass :DOES or :DOES_NOT respectively
|
|
11
|
+
*/
|
|
12
|
+
constructor(matchType: QualifierMatchType) {
|
|
13
|
+
super();
|
|
14
|
+
this.invert = matchType == QualifierMatchType.DOES_NOT;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Determines if the customer has specified tags
|
|
19
|
+
* @param discountCart DiscountCart
|
|
20
|
+
* @param selector Selector
|
|
21
|
+
*/
|
|
22
|
+
match(discountCart: DiscountCart, selector?: Selector) {
|
|
23
|
+
const matchResult =
|
|
24
|
+
discountCart.cart.buyerIdentity?.customer?.isActiveSubscriber || false;
|
|
25
|
+
|
|
26
|
+
if (this.invert) {
|
|
27
|
+
return !matchResult;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return matchResult;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 CustomerTagQualifier extends Qualifier {
|
|
10
|
+
invert: boolean;
|
|
11
|
+
matchCondition: StringComparisonType;
|
|
12
|
+
tags: string[];
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
matchType: QualifierMatchType,
|
|
16
|
+
matchCondition: StringComparisonType,
|
|
17
|
+
tags: string[]
|
|
18
|
+
) {
|
|
19
|
+
super();
|
|
20
|
+
this.invert = matchType == QualifierMatchType.DOES_NOT;
|
|
21
|
+
this.matchCondition = matchCondition;
|
|
22
|
+
this.tags = tags.map((e) => e.toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determines if the customer has specified tags
|
|
27
|
+
* @param discountCart DiscountCart
|
|
28
|
+
* @param selector Selector
|
|
29
|
+
*/
|
|
30
|
+
match(discountCart: DiscountCart, selector?: Selector) {
|
|
31
|
+
const matchResult =
|
|
32
|
+
(
|
|
33
|
+
discountCart.cart.buyerIdentity?.customer?.hasTags.filter((t) => {
|
|
34
|
+
if (t.hasTag) {
|
|
35
|
+
switch (this.matchCondition) {
|
|
36
|
+
case StringComparisonType.MATCH:
|
|
37
|
+
return this.tags.includes(t.tag);
|
|
38
|
+
case StringComparisonType.CONTAINS:
|
|
39
|
+
return this.tags.filter((e) => e.includes(t.tag)).length > 0;
|
|
40
|
+
case StringComparisonType.START_WITH:
|
|
41
|
+
return this.tags.filter((e) => e.startsWith(t.tag)).length > 0;
|
|
42
|
+
case StringComparisonType.END_WITH:
|
|
43
|
+
return this.tags.filter((e) => e.endsWith(t.tag)).length > 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}) || []
|
|
48
|
+
).length > 0;
|
|
49
|
+
|
|
50
|
+
if (this.invert) {
|
|
51
|
+
return !matchResult;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return matchResult;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RunInput,
|
|
3
|
+
Discount,
|
|
4
|
+
} from "extensions/zox-product-discount/generated/api";
|
|
5
|
+
|
|
6
|
+
export class DiscountCart {
|
|
7
|
+
cart: RunInput["cart"];
|
|
8
|
+
subtotalAmount: number;
|
|
9
|
+
appliedDiscountTotal: number;
|
|
10
|
+
discounts: Discount[];
|
|
11
|
+
|
|
12
|
+
constructor(cart: RunInput["cart"]) {
|
|
13
|
+
this.cart = cart;
|
|
14
|
+
this.subtotalAmount = 0;
|
|
15
|
+
this.appliedDiscountTotal = 0;
|
|
16
|
+
this.discounts = [];
|
|
17
|
+
|
|
18
|
+
this.setupCart(cart);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
subtotalExcludingVariants(variants: string[]) {
|
|
22
|
+
return this.cart.lines.reduce((prev, curr) => {
|
|
23
|
+
if (curr.merchandise.__typename == "ProductVariant") {
|
|
24
|
+
if (variants.indexOf(curr.merchandise.id) > -1) {
|
|
25
|
+
return prev - curr.cost.subtotalAmount.amount;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return prev;
|
|
30
|
+
}, this.subtotalExcludingGifts());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private subtotalExcludingGifts() {
|
|
34
|
+
return this.cart.lines.reduce((prev, curr) => {
|
|
35
|
+
if (curr.merchandise.__typename == "ProductVariant") {
|
|
36
|
+
if (
|
|
37
|
+
curr.merchandise.product.hasTags.filter(
|
|
38
|
+
(t) => t.hasTag && t.tag == "meta-exclude-gift"
|
|
39
|
+
).length
|
|
40
|
+
) {
|
|
41
|
+
return prev - curr.cost.subtotalAmount.amount;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return prev;
|
|
46
|
+
}, this.subtotalAmount);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
totalLineItemQuantity() {
|
|
50
|
+
return this.cart.lines.reduce((total, item) => total + item.quantity, 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
totalLineItemQuantityExcludingGifts() {
|
|
54
|
+
return this.cart.lines.reduce((total, item) => {
|
|
55
|
+
if (item.merchandise.__typename == "ProductVariant") {
|
|
56
|
+
if (
|
|
57
|
+
item.merchandise.product.hasTags.filter(
|
|
58
|
+
(t) => t.hasTag && t.tag == "meta-exclude-gift"
|
|
59
|
+
).length
|
|
60
|
+
) {
|
|
61
|
+
return total;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return total + item.quantity;
|
|
65
|
+
}, 0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private setupCart(cart: RunInput["cart"]) {
|
|
69
|
+
this.subtotalAmount = cart.cost.subtotalAmount.amount as number;
|
|
70
|
+
// Adjust the subtotal amount by excluding the value from gift items
|
|
71
|
+
this.subtotalAmount = this.subtotalExcludingGifts();
|
|
72
|
+
this.appliedDiscountTotal = 0;
|
|
73
|
+
this.discounts = [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
addDiscount(discount: Discount) {
|
|
77
|
+
// Figure out how much discount has been applied
|
|
78
|
+
const totalDiscounts = discount.targets.reduce((total, target) => {
|
|
79
|
+
// gather target lineItem
|
|
80
|
+
const lineItems = this.cart.lines.filter((line) =>
|
|
81
|
+
line.merchandise.__typename == "ProductVariant"
|
|
82
|
+
? line.merchandise.id == target.productVariant.id
|
|
83
|
+
: false
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// loop over the line items and do maths
|
|
87
|
+
lineItems.forEach((line) => {
|
|
88
|
+
let discountableItems = target.productVariant.quantity
|
|
89
|
+
? target.productVariant.quantity
|
|
90
|
+
: line.quantity;
|
|
91
|
+
|
|
92
|
+
const lineItemCost = parseFloat(line.cost.amountPerQuantity.amount);
|
|
93
|
+
|
|
94
|
+
// TODO: Make sure we're not discounting more items than are available on the line item quantity
|
|
95
|
+
|
|
96
|
+
// When it's a fixed amount, calculate the items per but only if it applies to each
|
|
97
|
+
// individual item...
|
|
98
|
+
if (discount.value.fixedAmount) {
|
|
99
|
+
if (discount.value.fixedAmount.appliesToEachItem) {
|
|
100
|
+
total +=
|
|
101
|
+
lineItemCost - discount.value.fixedAmount?.amount > 0
|
|
102
|
+
? discount.value.fixedAmount?.amount * discountableItems
|
|
103
|
+
: lineItemCost * discountableItems;
|
|
104
|
+
} else {
|
|
105
|
+
total +=
|
|
106
|
+
lineItemCost - discount.value.fixedAmount?.amount > 0
|
|
107
|
+
? discount.value.fixedAmount?.amount
|
|
108
|
+
: lineItemCost;
|
|
109
|
+
}
|
|
110
|
+
} else if (discount.value.percentage) {
|
|
111
|
+
total +=
|
|
112
|
+
lineItemCost *
|
|
113
|
+
(discount.value.percentage?.value / 100) *
|
|
114
|
+
discountableItems;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return total;
|
|
119
|
+
}, 0);
|
|
120
|
+
|
|
121
|
+
this.appliedDiscountTotal += totalDiscounts;
|
|
122
|
+
console.error("appliedDiscountTotal", this.appliedDiscountTotal);
|
|
123
|
+
|
|
124
|
+
this.discounts.push(discount);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
reset() {
|
|
128
|
+
this.setupCart(this.cart);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CartLine,
|
|
3
|
+
Discount,
|
|
4
|
+
} from "extensions/zox-product-discount/generated/api";
|
|
5
|
+
|
|
6
|
+
export type ApplyDiscount = {
|
|
7
|
+
lineItem: CartLine;
|
|
8
|
+
maxDiscounts?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface DiscountInterface {
|
|
12
|
+
apply(items: ApplyDiscount[]): Discount;
|
|
13
|
+
}
|
|
@@ -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 OrSelector {
|
|
7
|
+
is_a: "OrSelector";
|
|
8
|
+
conditions: (Selector | Qualifier)[];
|
|
9
|
+
|
|
10
|
+
constructor(conditions: (Selector | Qualifier)[]) {
|
|
11
|
+
this.is_a = "OrSelector";
|
|
12
|
+
this.conditions = conditions;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
match(item: CartLine | DiscountCart, selector?: Selector) {
|
|
16
|
+
try {
|
|
17
|
+
const conditionsResult = this.conditions
|
|
18
|
+
.map((condition) => {
|
|
19
|
+
if (selector) {
|
|
20
|
+
return (condition as Qualifier).match(
|
|
21
|
+
item as DiscountCart,
|
|
22
|
+
selector
|
|
23
|
+
);
|
|
24
|
+
} else {
|
|
25
|
+
return (condition as Selector).match(item as CartLine);
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
.filter((result) => result === true);
|
|
29
|
+
|
|
30
|
+
return conditionsResult.length > 0;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DiscountCart } from "./DiscountCart";
|
|
2
|
+
import { type NumericalComparisonType, Qualifier } from "./Qualifier";
|
|
3
|
+
|
|
4
|
+
export class PostCartAmountQualifier extends Qualifier {
|
|
5
|
+
comparisonType: NumericalComparisonType;
|
|
6
|
+
amount: number;
|
|
7
|
+
|
|
8
|
+
constructor(comparisonType: NumericalComparisonType, amount: number) {
|
|
9
|
+
super();
|
|
10
|
+
this.comparisonType = comparisonType;
|
|
11
|
+
this.amount = amount;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
match(discountCart: DiscountCart) {
|
|
15
|
+
return this.compareAmounts(
|
|
16
|
+
discountCart.subtotalAmount - discountCart.appliedDiscountTotal,
|
|
17
|
+
this.comparisonType,
|
|
18
|
+
this.amount
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CartLine,
|
|
3
|
+
ProductVariant,
|
|
4
|
+
} from "extensions/zox-product-discount/generated/api";
|
|
5
|
+
import { MatchType, Selector } from "./Selector";
|
|
6
|
+
|
|
7
|
+
export class ProductHandleSelector extends Selector {
|
|
8
|
+
invert: boolean;
|
|
9
|
+
handles: string[];
|
|
10
|
+
|
|
11
|
+
constructor(matchType: MatchType, handles: string[]) {
|
|
12
|
+
super();
|
|
13
|
+
this.invert = matchType == MatchType.NOT_ONE;
|
|
14
|
+
this.handles = handles.map((t) => t.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
match(lineItem: CartLine) {
|
|
18
|
+
const variant = lineItem.merchandise as ProductVariant;
|
|
19
|
+
|
|
20
|
+
const matchResult =
|
|
21
|
+
this.handles.indexOf(variant.product.handle.toLowerCase()) > -1;
|
|
22
|
+
|
|
23
|
+
if (this.invert) {
|
|
24
|
+
return !matchResult;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return matchResult;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CartLine,
|
|
3
|
+
ProductVariant,
|
|
4
|
+
} from "extensions/zox-product-discount/generated/api";
|
|
5
|
+
import { MatchType, Selector } from "./Selector";
|
|
6
|
+
|
|
7
|
+
export class ProductIdSelector extends Selector {
|
|
8
|
+
invert: boolean;
|
|
9
|
+
productIds: string[];
|
|
10
|
+
|
|
11
|
+
constructor(matchType: MatchType, productIds: string[]) {
|
|
12
|
+
super();
|
|
13
|
+
this.invert = matchType == MatchType.NOT_ONE;
|
|
14
|
+
this.productIds = productIds;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
match(lineItem: CartLine) {
|
|
18
|
+
const variant = lineItem.merchandise as ProductVariant;
|
|
19
|
+
|
|
20
|
+
const matchResult = this.productIds.includes(variant.product.id);
|
|
21
|
+
|
|
22
|
+
if (this.invert) {
|
|
23
|
+
return !matchResult;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return matchResult;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CartLine,
|
|
3
|
+
ProductVariant,
|
|
4
|
+
} from "extensions/zox-product-discount/generated/api";
|
|
5
|
+
import { Selector } from "./Selector";
|
|
6
|
+
import { QualifierMatchType, StringComparisonType } from "./Qualifier";
|
|
7
|
+
|
|
8
|
+
export class ProductTagSelector extends Selector {
|
|
9
|
+
invert: boolean;
|
|
10
|
+
matchCondition: StringComparisonType;
|
|
11
|
+
tags: string[];
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
matchType: QualifierMatchType,
|
|
15
|
+
matchCondition: StringComparisonType,
|
|
16
|
+
tags: string[]
|
|
17
|
+
) {
|
|
18
|
+
super();
|
|
19
|
+
this.matchCondition = matchCondition;
|
|
20
|
+
this.invert = matchType == QualifierMatchType.DOES_NOT;
|
|
21
|
+
this.tags = tags.map((t) => t.toLowerCase());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
match(lineItem: CartLine) {
|
|
25
|
+
const variant = lineItem.merchandise as ProductVariant;
|
|
26
|
+
const productTags = variant.product.hasTags.reduce((tags, t) => {
|
|
27
|
+
if (t.hasTag) {
|
|
28
|
+
tags.push(t.tag.toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
return tags;
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
let matchResult: boolean;
|
|
34
|
+
|
|
35
|
+
switch (this.matchCondition) {
|
|
36
|
+
case StringComparisonType.MATCH:
|
|
37
|
+
matchResult =
|
|
38
|
+
productTags.filter((tag) => this.tags.includes(tag)).length > 0;
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
matchResult = this.partialMatch(
|
|
42
|
+
this.matchCondition,
|
|
43
|
+
productTags,
|
|
44
|
+
this.tags
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (this.invert) {
|
|
49
|
+
return !matchResult;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return matchResult;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CartLine,
|
|
3
|
+
ProductVariant,
|
|
4
|
+
} from "extensions/zox-product-discount/generated/api";
|
|
5
|
+
import { MatchType, Selector } from "./Selector";
|
|
6
|
+
|
|
7
|
+
export class ProductTypeSelector extends Selector {
|
|
8
|
+
invert: boolean;
|
|
9
|
+
productTypes: string[];
|
|
10
|
+
|
|
11
|
+
constructor(matchType: MatchType, productTypes: string[]) {
|
|
12
|
+
super();
|
|
13
|
+
this.invert = matchType == MatchType.NOT_ONE;
|
|
14
|
+
this.productTypes = productTypes.map((item) => item.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
match(lineItem: CartLine) {
|
|
18
|
+
const variant = lineItem.merchandise as ProductVariant;
|
|
19
|
+
|
|
20
|
+
if (variant.product.productType) {
|
|
21
|
+
const matchResult = this.productTypes.includes(
|
|
22
|
+
variant.product.productType.toLowerCase()
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (this.invert) {
|
|
26
|
+
return !matchResult;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return matchResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { DiscountCart } from './DiscountCart';
|
|
2
|
+
import type { Selector } from './Selector';
|
|
3
|
+
|
|
4
|
+
export enum QualifierMatchType {
|
|
5
|
+
DOES = ':does',
|
|
6
|
+
DOES_NOT = ':does_not',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum StringComparisonType {
|
|
10
|
+
MATCH = ':match',
|
|
11
|
+
CONTAINS = ':contains',
|
|
12
|
+
START_WITH = ':start_with',
|
|
13
|
+
END_WITH = ':end_with',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export enum NumericalComparisonType {
|
|
17
|
+
GREATER_THAN = ':greater_than',
|
|
18
|
+
GREATER_THAN_OR_EQUAL = ':greater_than_or_equal',
|
|
19
|
+
LESS_THAN = ':less_than',
|
|
20
|
+
LESS_THAN_OR_EQUAL = ':less_than_or_equal',
|
|
21
|
+
EQUAL_TO = ':equal_to',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// export enum ComparisonType {
|
|
25
|
+
// GREATER_THAN = ":greater_than",
|
|
26
|
+
// GREATER_THAN_OR_EQUAL = ":greater_than_or_equal",
|
|
27
|
+
// LESS_THAN = ":less_than",
|
|
28
|
+
// LESS_THAN_OR_EQUAL = ":less_than_or_equal",
|
|
29
|
+
// EQUAL_TO = ":equal_to",
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
export enum QualifierBehavior {
|
|
33
|
+
ALL = ':all',
|
|
34
|
+
ANY = ':any',
|
|
35
|
+
}
|
|
36
|
+
export class Qualifier {
|
|
37
|
+
is_a: 'Qualifier';
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
this.is_a = 'Qualifier';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
compareAmounts(
|
|
44
|
+
compare: number,
|
|
45
|
+
comparisonType: NumericalComparisonType,
|
|
46
|
+
compareTo: number
|
|
47
|
+
) {
|
|
48
|
+
switch (comparisonType) {
|
|
49
|
+
case NumericalComparisonType.GREATER_THAN:
|
|
50
|
+
return compare > compareTo;
|
|
51
|
+
case NumericalComparisonType.GREATER_THAN_OR_EQUAL:
|
|
52
|
+
return compare >= compareTo;
|
|
53
|
+
case NumericalComparisonType.LESS_THAN:
|
|
54
|
+
return compare < compareTo;
|
|
55
|
+
case NumericalComparisonType.LESS_THAN_OR_EQUAL:
|
|
56
|
+
return compare <= compareTo;
|
|
57
|
+
case NumericalComparisonType.EQUAL_TO:
|
|
58
|
+
return compare == compareTo;
|
|
59
|
+
default:
|
|
60
|
+
throw new Error('Invalid comparison type');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
match(discountCart: DiscountCart, selector?: Selector) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CartLine } from '../generated/api';
|
|
2
|
+
import { Selector } from './Selector';
|
|
3
|
+
|
|
4
|
+
export enum SaleItemSelectorMatchType {
|
|
5
|
+
IS = ':is',
|
|
6
|
+
NOT = ':not',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class SaleItemSelector extends Selector {
|
|
10
|
+
invert: boolean;
|
|
11
|
+
|
|
12
|
+
constructor(matchType: SaleItemSelectorMatchType) {
|
|
13
|
+
super();
|
|
14
|
+
// Unlike other common scenarios, we invert if we pass an :IS
|
|
15
|
+
// because it is easier to figure out of the item is NOT on sale.
|
|
16
|
+
this.invert = matchType == SaleItemSelectorMatchType.IS;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
match(lineItem: CartLine) {
|
|
20
|
+
// Check to see if the item is NOT on sale
|
|
21
|
+
const matchResult =
|
|
22
|
+
!lineItem.cost.compareAtAmountPerQuantity ||
|
|
23
|
+
parseFloat(
|
|
24
|
+
lineItem.cost.compareAtAmountPerQuantity.amount
|
|
25
|
+
) <=
|
|
26
|
+
parseFloat(lineItem.cost.amountPerQuantity.amount);
|
|
27
|
+
|
|
28
|
+
// If we want to check that the item IS on sale, we'll invert the result
|
|
29
|
+
if (this.invert) {
|
|
30
|
+
return !matchResult;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return matchResult;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CartLine } from "extensions/zox-product-discount/generated/api";
|
|
2
|
+
import { StringComparisonType } from "./Qualifier";
|
|
3
|
+
|
|
4
|
+
export enum MatchType {
|
|
5
|
+
"ALL" = ":all",
|
|
6
|
+
"IS_ONE" = ":is_one",
|
|
7
|
+
"NOT_ONE" = ":not_one",
|
|
8
|
+
"DOES" = ":does",
|
|
9
|
+
"DOES_NOT" = ":does_not",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class Selector {
|
|
13
|
+
match(subject: CartLine, selector?: Selector) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Checks each member of the subject array for possible matches. Returns true when any possible match is made.
|
|
19
|
+
* @param matchType StringComparisonType
|
|
20
|
+
* @param itemInfo string | string[]
|
|
21
|
+
* @param possibleMatches string[]
|
|
22
|
+
* @returns boolean
|
|
23
|
+
*/
|
|
24
|
+
partialMatch(
|
|
25
|
+
matchType:
|
|
26
|
+
| StringComparisonType.CONTAINS
|
|
27
|
+
| StringComparisonType.START_WITH
|
|
28
|
+
| StringComparisonType.END_WITH,
|
|
29
|
+
itemInfo: string | string[],
|
|
30
|
+
possibleMatches: string[]
|
|
31
|
+
) {
|
|
32
|
+
// Convert to an array if it isn't one
|
|
33
|
+
const subject = Array.isArray(itemInfo) ? itemInfo : [itemInfo];
|
|
34
|
+
let foundMatch = false;
|
|
35
|
+
possibleMatches.forEach((possibility) => {
|
|
36
|
+
subject.forEach((search) => {
|
|
37
|
+
switch (matchType) {
|
|
38
|
+
case StringComparisonType.CONTAINS:
|
|
39
|
+
if (search.search(possibility) > -1) foundMatch = true;
|
|
40
|
+
break;
|
|
41
|
+
case StringComparisonType.START_WITH:
|
|
42
|
+
if (search.startsWith(possibility)) foundMatch = true;
|
|
43
|
+
break;
|
|
44
|
+
case StringComparisonType.END_WITH:
|
|
45
|
+
if (search.endsWith(possibility)) foundMatch = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return foundMatch;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CartLine } from "extensions/zox-product-discount/generated/api";
|
|
2
|
+
import { MatchType, Selector } from "./Selector";
|
|
3
|
+
|
|
4
|
+
export class SubscriptionItemSelector extends Selector {
|
|
5
|
+
invert: boolean;
|
|
6
|
+
|
|
7
|
+
constructor(matchType: MatchType) {
|
|
8
|
+
super();
|
|
9
|
+
this.invert = matchType == MatchType.NOT_ONE;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
match(lineItem: CartLine) {
|
|
13
|
+
const matchResult = lineItem.sellingPlanAllocation !== undefined;
|
|
14
|
+
|
|
15
|
+
if (this.invert) {
|
|
16
|
+
return !matchResult;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|