@zoxllc/shopify-checkout-extensions 0.0.2 → 0.0.4
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/migrate.ts +64 -0
- package/package.json +2 -1
- package/src/common/AndSelector.ts +17 -10
- package/src/common/Campaign.ts +27 -12
- package/src/common/CampaignFactory.ts +103 -51
- package/src/common/CartHasItemQualifier.ts +38 -22
- package/src/common/CartQuantityQualifier.ts +41 -20
- package/src/common/CountryCodeQualifier.ts +49 -0
- package/src/common/CustomerEmailQualifier.ts +14 -7
- package/src/common/CustomerTagQualifier.ts +29 -16
- package/src/common/DiscountCart.ts +78 -54
- package/src/common/DiscountInterface.ts +15 -3
- package/src/common/OrSelector.ts +13 -8
- package/src/common/ProductHandleSelector.ts +5 -3
- package/src/common/ProductIdSelector.ts +10 -4
- package/src/common/ProductTagSelector.ts +19 -10
- package/src/common/ProductTypeSelector.ts +10 -4
- package/src/common/SaleItemSelector.ts +2 -1
- package/src/common/Selector.ts +26 -13
- package/src/common/SubscriptionItemSelector.ts +5 -3
- package/src/index.ts +1 -1
- package/src/{common → lineItem}/BuyXGetY.ts +42 -20
- package/src/lineItem/ConditionalDiscount.ts +1 -1
- package/src/lineItem/FixedItemDiscount.ts +2 -4
- package/src/lineItem/PercentageDiscount.ts +2 -4
- package/src/shipping/FixedDiscount.ts +37 -0
- package/src/shipping/RateNameSelector.ts +54 -0
- package/src/shipping/ShippingDiscount.ts +75 -0
- package/src/shipping/api.ts +2097 -0
- package/tests/AndSelector.test.ts +1 -1
- package/tests/CartQuantityQualifier.test.ts +1 -1
- package/tests/CountryCodeQualifier.test.ts +56 -0
- package/tests/CustomerSubscriberQualifier.test.ts +1 -1
- package/tests/CustomerTagQualifier.test.ts +71 -0
- package/tests/DiscountCart.test.ts +1 -1
- package/tests/OrSelector.test.ts +1 -1
- package/tests/ProductIdSelector.test.ts +83 -0
- package/tests/ProductTagSelector.test.ts +1 -1
- package/tests/Qualifier.test.ts +1 -1
- package/tests/RateNameSelector.test.ts +107 -0
- package/tests/SaleItemSelector.test.ts +1 -1
- package/tests/Selector.test.ts +1 -1
- /package/src/{generated → lineItem}/api.ts +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { DiscountCart } from
|
|
1
|
+
import type { DiscountCart } from './DiscountCart';
|
|
2
2
|
import {
|
|
3
3
|
StringComparisonType,
|
|
4
4
|
QualifierMatchType,
|
|
5
5
|
Qualifier,
|
|
6
|
-
} from
|
|
7
|
-
import { type Selector } from
|
|
6
|
+
} from './Qualifier';
|
|
7
|
+
import { type Selector } from './Selector';
|
|
8
8
|
|
|
9
9
|
export class CustomerEmailQualifier extends Qualifier {
|
|
10
10
|
invert: boolean;
|
|
@@ -34,20 +34,27 @@ export class CustomerEmailQualifier extends Qualifier {
|
|
|
34
34
|
|
|
35
35
|
let matchResult = false;
|
|
36
36
|
|
|
37
|
-
const email =
|
|
37
|
+
const email =
|
|
38
|
+
discountCart.cart.buyerIdentity.customer.email.toLowerCase();
|
|
38
39
|
|
|
39
40
|
switch (this.matchCondition) {
|
|
40
41
|
case StringComparisonType.MATCH:
|
|
41
42
|
matchResult = this.emails.includes(email);
|
|
42
43
|
break;
|
|
43
44
|
case StringComparisonType.CONTAINS:
|
|
44
|
-
matchResult =
|
|
45
|
+
matchResult =
|
|
46
|
+
this.emails.filter((e) => email.includes(e))
|
|
47
|
+
.length > 0;
|
|
45
48
|
break;
|
|
46
49
|
case StringComparisonType.START_WITH:
|
|
47
|
-
matchResult =
|
|
50
|
+
matchResult =
|
|
51
|
+
this.emails.filter((e) => email.startsWith(e))
|
|
52
|
+
.length > 0;
|
|
48
53
|
break;
|
|
49
54
|
case StringComparisonType.END_WITH:
|
|
50
|
-
matchResult =
|
|
55
|
+
matchResult =
|
|
56
|
+
this.emails.filter((e) => email.endsWith(e))
|
|
57
|
+
.length > 0;
|
|
51
58
|
break;
|
|
52
59
|
}
|
|
53
60
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { DiscountCart } from
|
|
1
|
+
import type { DiscountCart } from './DiscountCart';
|
|
2
2
|
import {
|
|
3
3
|
StringComparisonType,
|
|
4
4
|
QualifierMatchType,
|
|
5
5
|
Qualifier,
|
|
6
|
-
} from
|
|
7
|
-
import { type Selector } from
|
|
6
|
+
} from './Qualifier';
|
|
7
|
+
import { type Selector } from './Selector';
|
|
8
8
|
|
|
9
9
|
export class CustomerTagQualifier extends Qualifier {
|
|
10
10
|
invert: boolean;
|
|
@@ -30,21 +30,34 @@ export class CustomerTagQualifier extends Qualifier {
|
|
|
30
30
|
match(discountCart: DiscountCart, selector?: Selector) {
|
|
31
31
|
const matchResult =
|
|
32
32
|
(
|
|
33
|
-
discountCart.cart.buyerIdentity?.customer?.hasTags.filter(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
discountCart.cart.buyerIdentity?.customer?.hasTags.filter(
|
|
34
|
+
(t) => {
|
|
35
|
+
if (t.hasTag) {
|
|
36
|
+
const tag = t.tag.toLowerCase();
|
|
37
|
+
switch (this.matchCondition) {
|
|
38
|
+
case StringComparisonType.MATCH:
|
|
39
|
+
return this.tags.includes(tag);
|
|
40
|
+
case StringComparisonType.CONTAINS:
|
|
41
|
+
return (
|
|
42
|
+
this.tags.filter((e) => tag.includes(e))
|
|
43
|
+
.length > 0
|
|
44
|
+
);
|
|
45
|
+
case StringComparisonType.START_WITH:
|
|
46
|
+
return (
|
|
47
|
+
this.tags.filter((e) =>
|
|
48
|
+
tag.startsWith(e)
|
|
49
|
+
).length > 0
|
|
50
|
+
);
|
|
51
|
+
case StringComparisonType.END_WITH:
|
|
52
|
+
return (
|
|
53
|
+
this.tags.filter((e) => tag.endsWith(e))
|
|
54
|
+
.length > 0
|
|
55
|
+
);
|
|
56
|
+
}
|
|
44
57
|
}
|
|
58
|
+
return false;
|
|
45
59
|
}
|
|
46
|
-
|
|
47
|
-
}) || []
|
|
60
|
+
) || []
|
|
48
61
|
).length > 0;
|
|
49
62
|
|
|
50
63
|
if (this.invert) {
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { RunInput as LineItemRunInput } from '../lineItem/api';
|
|
2
|
+
import type { RunInput as ShippingRunInput } from '../shipping/api';
|
|
3
|
+
import { Discount } from './DiscountInterface';
|
|
4
|
+
|
|
5
|
+
type RunInput = {
|
|
6
|
+
cart: LineItemRunInput['cart'] & ShippingRunInput['cart'];
|
|
7
|
+
};
|
|
5
8
|
|
|
6
9
|
export class DiscountCart {
|
|
7
|
-
cart: RunInput[
|
|
10
|
+
cart: RunInput['cart'];
|
|
8
11
|
subtotalAmount: number;
|
|
9
12
|
appliedDiscountTotal: number;
|
|
10
13
|
discounts: Discount[];
|
|
11
14
|
|
|
12
|
-
constructor(cart: RunInput[
|
|
15
|
+
constructor(cart: RunInput['cart']) {
|
|
13
16
|
this.cart = cart;
|
|
14
17
|
this.subtotalAmount = 0;
|
|
15
18
|
this.appliedDiscountTotal = 0;
|
|
@@ -20,7 +23,7 @@ export class DiscountCart {
|
|
|
20
23
|
|
|
21
24
|
subtotalExcludingVariants(variants: string[]) {
|
|
22
25
|
return this.cart.lines.reduce((prev, curr) => {
|
|
23
|
-
if (curr.merchandise.__typename ==
|
|
26
|
+
if (curr.merchandise.__typename == 'ProductVariant') {
|
|
24
27
|
if (variants.indexOf(curr.merchandise.id) > -1) {
|
|
25
28
|
return prev - curr.cost.subtotalAmount.amount;
|
|
26
29
|
}
|
|
@@ -32,10 +35,10 @@ export class DiscountCart {
|
|
|
32
35
|
|
|
33
36
|
private subtotalExcludingGifts() {
|
|
34
37
|
return this.cart.lines.reduce((prev, curr) => {
|
|
35
|
-
if (curr.merchandise.__typename ==
|
|
38
|
+
if (curr.merchandise.__typename == 'ProductVariant') {
|
|
36
39
|
if (
|
|
37
40
|
curr.merchandise.product.hasTags.filter(
|
|
38
|
-
(t) => t.hasTag && t.tag ==
|
|
41
|
+
(t) => t.hasTag && t.tag == 'meta-exclude-gift'
|
|
39
42
|
).length
|
|
40
43
|
) {
|
|
41
44
|
return prev - curr.cost.subtotalAmount.amount;
|
|
@@ -47,15 +50,18 @@ export class DiscountCart {
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
totalLineItemQuantity() {
|
|
50
|
-
return this.cart.lines.reduce(
|
|
53
|
+
return this.cart.lines.reduce(
|
|
54
|
+
(total, item) => total + item.quantity,
|
|
55
|
+
0
|
|
56
|
+
);
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
totalLineItemQuantityExcludingGifts() {
|
|
54
60
|
return this.cart.lines.reduce((total, item) => {
|
|
55
|
-
if (item.merchandise.__typename ==
|
|
61
|
+
if (item.merchandise.__typename == 'ProductVariant') {
|
|
56
62
|
if (
|
|
57
63
|
item.merchandise.product.hasTags.filter(
|
|
58
|
-
(t) => t.hasTag && t.tag ==
|
|
64
|
+
(t) => t.hasTag && t.tag == 'meta-exclude-gift'
|
|
59
65
|
).length
|
|
60
66
|
) {
|
|
61
67
|
return total;
|
|
@@ -65,8 +71,9 @@ export class DiscountCart {
|
|
|
65
71
|
}, 0);
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
private setupCart(cart: RunInput[
|
|
69
|
-
this.subtotalAmount = cart.cost.subtotalAmount
|
|
74
|
+
private setupCart(cart: RunInput['cart']) {
|
|
75
|
+
this.subtotalAmount = cart.cost.subtotalAmount
|
|
76
|
+
.amount as number;
|
|
70
77
|
// Adjust the subtotal amount by excluding the value from gift items
|
|
71
78
|
this.subtotalAmount = this.subtotalExcludingGifts();
|
|
72
79
|
this.appliedDiscountTotal = 0;
|
|
@@ -75,51 +82,68 @@ export class DiscountCart {
|
|
|
75
82
|
|
|
76
83
|
addDiscount(discount: Discount) {
|
|
77
84
|
// Figure out how much discount has been applied
|
|
78
|
-
const totalDiscounts = discount.targets.reduce(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
85
|
+
const totalDiscounts = discount.targets.reduce(
|
|
86
|
+
(total, target) => {
|
|
87
|
+
// gather target lineItem
|
|
88
|
+
const lineItems = this.cart.lines.filter((line) =>
|
|
89
|
+
line.merchandise.__typename == 'ProductVariant'
|
|
90
|
+
? line.merchandise.id ==
|
|
91
|
+
target.productVariant.id
|
|
92
|
+
: false
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// loop over the line items and do maths
|
|
96
|
+
lineItems.forEach((line) => {
|
|
97
|
+
let discountableItems = target.productVariant
|
|
98
|
+
.quantity
|
|
99
|
+
? target.productVariant.quantity
|
|
100
|
+
: line.quantity;
|
|
101
|
+
|
|
102
|
+
const lineItemCost = parseFloat(
|
|
103
|
+
line.cost.amountPerQuantity.amount
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// TODO: Make sure we're not discounting more items than are available on the line item quantity
|
|
107
|
+
|
|
108
|
+
// When it's a fixed amount, calculate the items per but only if it applies to each
|
|
109
|
+
// individual item...
|
|
110
|
+
if (discount.value.fixedAmount) {
|
|
111
|
+
if (
|
|
112
|
+
discount.value.fixedAmount.appliesToEachItem
|
|
113
|
+
) {
|
|
114
|
+
total +=
|
|
115
|
+
lineItemCost -
|
|
116
|
+
discount.value.fixedAmount?.amount >
|
|
117
|
+
0
|
|
118
|
+
? discount.value.fixedAmount?.amount *
|
|
119
|
+
discountableItems
|
|
120
|
+
: lineItemCost * discountableItems;
|
|
121
|
+
} else {
|
|
122
|
+
total +=
|
|
123
|
+
lineItemCost -
|
|
124
|
+
discount.value.fixedAmount?.amount >
|
|
125
|
+
0
|
|
126
|
+
? discount.value.fixedAmount?.amount
|
|
127
|
+
: lineItemCost;
|
|
128
|
+
}
|
|
129
|
+
} else if (discount.value.percentage) {
|
|
105
130
|
total +=
|
|
106
|
-
lineItemCost
|
|
107
|
-
|
|
108
|
-
|
|
131
|
+
lineItemCost *
|
|
132
|
+
(discount.value.percentage?.value / 100) *
|
|
133
|
+
discountableItems;
|
|
109
134
|
}
|
|
110
|
-
}
|
|
111
|
-
total +=
|
|
112
|
-
lineItemCost *
|
|
113
|
-
(discount.value.percentage?.value / 100) *
|
|
114
|
-
discountableItems;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
135
|
+
});
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
137
|
+
return total;
|
|
138
|
+
},
|
|
139
|
+
0
|
|
140
|
+
);
|
|
120
141
|
|
|
121
142
|
this.appliedDiscountTotal += totalDiscounts;
|
|
122
|
-
console.error(
|
|
143
|
+
console.error(
|
|
144
|
+
'appliedDiscountTotal',
|
|
145
|
+
this.appliedDiscountTotal
|
|
146
|
+
);
|
|
123
147
|
|
|
124
148
|
this.discounts.push(discount);
|
|
125
149
|
}
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
CartDeliveryOption,
|
|
2
3
|
CartLine,
|
|
3
|
-
|
|
4
|
-
} from "extensions/zox-product-discount/generated/api";
|
|
4
|
+
} from '../lineItem/api';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import type { Discount as ItemDiscount } from '../lineItem/api';
|
|
7
|
+
import type { Discount as ShippingDiscount } from '~/shipping/api';
|
|
8
|
+
|
|
9
|
+
export type Discount = ItemDiscount & ShippingDiscount;
|
|
10
|
+
|
|
11
|
+
type ApplyLineItemDiscount = {
|
|
7
12
|
lineItem: CartLine;
|
|
8
13
|
maxDiscounts?: number;
|
|
9
14
|
};
|
|
10
15
|
|
|
16
|
+
type ApplyShippingDiscount = {
|
|
17
|
+
deliveryOption: CartDeliveryOption;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ApplyDiscount = ApplyLineItemDiscount &
|
|
21
|
+
ApplyShippingDiscount;
|
|
22
|
+
|
|
11
23
|
export interface DiscountInterface {
|
|
12
24
|
apply(items: ApplyDiscount[]): Discount;
|
|
13
25
|
}
|
package/src/common/OrSelector.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import type { DiscountCart } from
|
|
2
|
-
import type { Qualifier } from
|
|
3
|
-
import type { Selector } from
|
|
4
|
-
import type { CartLine } from
|
|
1
|
+
import type { DiscountCart } from './DiscountCart';
|
|
2
|
+
import type { Qualifier } from './Qualifier';
|
|
3
|
+
import type { Selector } from './Selector';
|
|
4
|
+
import type { CartLine } from '../lineItem/api';
|
|
5
5
|
|
|
6
6
|
export class OrSelector {
|
|
7
|
-
is_a:
|
|
7
|
+
is_a: 'OrSelector';
|
|
8
8
|
conditions: (Selector | Qualifier)[];
|
|
9
9
|
|
|
10
10
|
constructor(conditions: (Selector | Qualifier)[]) {
|
|
11
|
-
this.is_a =
|
|
11
|
+
this.is_a = 'OrSelector';
|
|
12
12
|
this.conditions = conditions;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
match(
|
|
15
|
+
match(
|
|
16
|
+
item: CartLine | DiscountCart,
|
|
17
|
+
selector?: Selector
|
|
18
|
+
) {
|
|
16
19
|
try {
|
|
17
20
|
const conditionsResult = this.conditions
|
|
18
21
|
.map((condition) => {
|
|
@@ -22,7 +25,9 @@ export class OrSelector {
|
|
|
22
25
|
selector
|
|
23
26
|
);
|
|
24
27
|
} else {
|
|
25
|
-
return (condition as Selector).match(
|
|
28
|
+
return (condition as Selector).match(
|
|
29
|
+
item as CartLine
|
|
30
|
+
);
|
|
26
31
|
}
|
|
27
32
|
})
|
|
28
33
|
.filter((result) => result === true);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CartLine,
|
|
3
3
|
ProductVariant,
|
|
4
|
-
} from
|
|
5
|
-
import { MatchType, Selector } from
|
|
4
|
+
} from '../lineItem/api';
|
|
5
|
+
import { MatchType, Selector } from './Selector';
|
|
6
6
|
|
|
7
7
|
export class ProductHandleSelector extends Selector {
|
|
8
8
|
invert: boolean;
|
|
@@ -18,7 +18,9 @@ export class ProductHandleSelector extends Selector {
|
|
|
18
18
|
const variant = lineItem.merchandise as ProductVariant;
|
|
19
19
|
|
|
20
20
|
const matchResult =
|
|
21
|
-
this.handles.indexOf(
|
|
21
|
+
this.handles.indexOf(
|
|
22
|
+
variant.product.handle.toLowerCase()
|
|
23
|
+
) > -1;
|
|
22
24
|
|
|
23
25
|
if (this.invert) {
|
|
24
26
|
return !matchResult;
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CartLine,
|
|
3
3
|
ProductVariant,
|
|
4
|
-
} from
|
|
5
|
-
import { MatchType, Selector } from
|
|
4
|
+
} from '../lineItem/api';
|
|
5
|
+
import { MatchType, Selector } from './Selector';
|
|
6
6
|
|
|
7
7
|
export class ProductIdSelector extends Selector {
|
|
8
8
|
invert: boolean;
|
|
9
9
|
productIds: string[];
|
|
10
10
|
|
|
11
|
-
constructor(
|
|
11
|
+
constructor(
|
|
12
|
+
matchType: MatchType.IS_ONE | MatchType.NOT_ONE,
|
|
13
|
+
productIds: string[]
|
|
14
|
+
) {
|
|
12
15
|
super();
|
|
13
16
|
this.invert = matchType == MatchType.NOT_ONE;
|
|
14
17
|
this.productIds = productIds;
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
match(subject: unknown): boolean;
|
|
17
21
|
match(lineItem: CartLine) {
|
|
18
22
|
const variant = lineItem.merchandise as ProductVariant;
|
|
19
23
|
|
|
20
|
-
const matchResult = this.productIds.includes(
|
|
24
|
+
const matchResult = this.productIds.includes(
|
|
25
|
+
variant.product.id
|
|
26
|
+
);
|
|
21
27
|
|
|
22
28
|
if (this.invert) {
|
|
23
29
|
return !matchResult;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CartLine,
|
|
3
3
|
ProductVariant,
|
|
4
|
-
} from
|
|
5
|
-
import { Selector } from
|
|
6
|
-
import {
|
|
4
|
+
} from '../lineItem/api';
|
|
5
|
+
import { Selector } from './Selector';
|
|
6
|
+
import {
|
|
7
|
+
QualifierMatchType,
|
|
8
|
+
StringComparisonType,
|
|
9
|
+
} from './Qualifier';
|
|
7
10
|
|
|
8
11
|
export class ProductTagSelector extends Selector {
|
|
9
12
|
invert: boolean;
|
|
@@ -21,21 +24,27 @@ export class ProductTagSelector extends Selector {
|
|
|
21
24
|
this.tags = tags.map((t) => t.toLowerCase());
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
match(subject: unknown): boolean;
|
|
24
28
|
match(lineItem: CartLine) {
|
|
25
29
|
const variant = lineItem.merchandise as ProductVariant;
|
|
26
|
-
const productTags = variant.product.hasTags.reduce(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const productTags = variant.product.hasTags.reduce(
|
|
31
|
+
(tags, t) => {
|
|
32
|
+
if (t.hasTag) {
|
|
33
|
+
tags.push(t.tag.toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
return tags;
|
|
36
|
+
},
|
|
37
|
+
[] as string[]
|
|
38
|
+
);
|
|
32
39
|
|
|
33
40
|
let matchResult: boolean;
|
|
34
41
|
|
|
35
42
|
switch (this.matchCondition) {
|
|
36
43
|
case StringComparisonType.MATCH:
|
|
37
44
|
matchResult =
|
|
38
|
-
productTags.filter((tag) =>
|
|
45
|
+
productTags.filter((tag) =>
|
|
46
|
+
this.tags.includes(tag)
|
|
47
|
+
).length > 0;
|
|
39
48
|
break;
|
|
40
49
|
default:
|
|
41
50
|
matchResult = this.partialMatch(
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CartLine,
|
|
3
3
|
ProductVariant,
|
|
4
|
-
} from
|
|
5
|
-
import { MatchType, Selector } from
|
|
4
|
+
} from '../lineItem/api';
|
|
5
|
+
import { MatchType, Selector } from './Selector';
|
|
6
6
|
|
|
7
7
|
export class ProductTypeSelector extends Selector {
|
|
8
8
|
invert: boolean;
|
|
9
9
|
productTypes: string[];
|
|
10
10
|
|
|
11
|
-
constructor(
|
|
11
|
+
constructor(
|
|
12
|
+
matchType: MatchType,
|
|
13
|
+
productTypes: string[]
|
|
14
|
+
) {
|
|
12
15
|
super();
|
|
13
16
|
this.invert = matchType == MatchType.NOT_ONE;
|
|
14
|
-
this.productTypes = productTypes.map((item) =>
|
|
17
|
+
this.productTypes = productTypes.map((item) =>
|
|
18
|
+
item.toLowerCase()
|
|
19
|
+
);
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
match(subject: unknown): boolean;
|
|
17
23
|
match(lineItem: CartLine) {
|
|
18
24
|
const variant = lineItem.merchandise as ProductVariant;
|
|
19
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CartLine } from '../
|
|
1
|
+
import { CartLine } from '../lineItem/api';
|
|
2
2
|
import { Selector } from './Selector';
|
|
3
3
|
|
|
4
4
|
export enum SaleItemSelectorMatchType {
|
|
@@ -16,6 +16,7 @@ export class SaleItemSelector extends Selector {
|
|
|
16
16
|
this.invert = matchType == SaleItemSelectorMatchType.IS;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
match(subject: unknown): boolean;
|
|
19
20
|
match(lineItem: CartLine) {
|
|
20
21
|
// Check to see if the item is NOT on sale
|
|
21
22
|
const matchResult =
|
package/src/common/Selector.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { CartDeliveryOption } from '~/shipping/api';
|
|
2
|
+
import type { CartLine } from '../lineItem/api';
|
|
3
|
+
import { StringComparisonType } from './Qualifier';
|
|
3
4
|
|
|
4
5
|
export enum MatchType {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
'ALL' = ':all',
|
|
7
|
+
'IS_ONE' = ':is_one',
|
|
8
|
+
'NOT_ONE' = ':not_one',
|
|
9
|
+
'DOES' = ':does',
|
|
10
|
+
'DOES_NOT' = ':does_not',
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export
|
|
13
|
-
match(subject: CartLine
|
|
13
|
+
export type SelectorInterface = {
|
|
14
|
+
match(subject: CartLine): boolean;
|
|
15
|
+
match(subject: CartDeliveryOption): boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class Selector implements SelectorInterface {
|
|
19
|
+
match(subject: CartLine): boolean;
|
|
20
|
+
match(subject: CartDeliveryOption): boolean;
|
|
21
|
+
match(subject: unknown): boolean {
|
|
14
22
|
return true;
|
|
15
23
|
}
|
|
16
24
|
|
|
@@ -30,19 +38,24 @@ export class Selector {
|
|
|
30
38
|
possibleMatches: string[]
|
|
31
39
|
) {
|
|
32
40
|
// Convert to an array if it isn't one
|
|
33
|
-
const subject = Array.isArray(itemInfo)
|
|
41
|
+
const subject = Array.isArray(itemInfo)
|
|
42
|
+
? itemInfo
|
|
43
|
+
: [itemInfo];
|
|
34
44
|
let foundMatch = false;
|
|
35
45
|
possibleMatches.forEach((possibility) => {
|
|
36
46
|
subject.forEach((search) => {
|
|
37
47
|
switch (matchType) {
|
|
38
48
|
case StringComparisonType.CONTAINS:
|
|
39
|
-
if (search.search(possibility) > -1)
|
|
49
|
+
if (search.search(possibility) > -1)
|
|
50
|
+
foundMatch = true;
|
|
40
51
|
break;
|
|
41
52
|
case StringComparisonType.START_WITH:
|
|
42
|
-
if (search.startsWith(possibility))
|
|
53
|
+
if (search.startsWith(possibility))
|
|
54
|
+
foundMatch = true;
|
|
43
55
|
break;
|
|
44
56
|
case StringComparisonType.END_WITH:
|
|
45
|
-
if (search.endsWith(possibility))
|
|
57
|
+
if (search.endsWith(possibility))
|
|
58
|
+
foundMatch = true;
|
|
46
59
|
break;
|
|
47
60
|
}
|
|
48
61
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CartLine } from
|
|
2
|
-
import { MatchType, Selector } from
|
|
1
|
+
import type { CartLine } from '../lineItem/api';
|
|
2
|
+
import { MatchType, Selector } from './Selector';
|
|
3
3
|
|
|
4
4
|
export class SubscriptionItemSelector extends Selector {
|
|
5
5
|
invert: boolean;
|
|
@@ -9,8 +9,10 @@ export class SubscriptionItemSelector extends Selector {
|
|
|
9
9
|
this.invert = matchType == MatchType.NOT_ONE;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
match(subject: unknown): boolean;
|
|
12
13
|
match(lineItem: CartLine) {
|
|
13
|
-
const matchResult =
|
|
14
|
+
const matchResult =
|
|
15
|
+
lineItem.sellingPlanAllocation !== undefined;
|
|
14
16
|
|
|
15
17
|
if (this.invert) {
|
|
16
18
|
return !matchResult;
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { ConditionalDiscount } from './lineItem/ConditionalDiscount';
|
|
|
2
2
|
export { FixedItemDiscount } from './lineItem/FixedItemDiscount';
|
|
3
3
|
export { PercentageDiscount } from './lineItem/PercentageDiscount';
|
|
4
4
|
export { AndSelector } from './common/AndSelector';
|
|
5
|
-
export { BuyXGetY } from './
|
|
5
|
+
export { BuyXGetY } from './lineItem/BuyXGetY';
|
|
6
6
|
export { Campaign } from './common/Campaign';
|
|
7
7
|
export type {
|
|
8
8
|
CampaignType,
|