@zoxllc/shopify-checkout-extensions 0.0.2 → 0.0.3
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/package.json +1 -1
- package/src/common/AndSelector.ts +1 -1
- package/src/common/BuyXGetY.ts +41 -19
- package/src/common/CartHasItemQualifier.ts +38 -22
- package/src/common/CartQuantityQualifier.ts +41 -20
- package/src/common/DiscountCart.ts +1 -4
- package/src/common/DiscountInterface.ts +1 -4
- package/src/common/OrSelector.ts +1 -1
- package/src/common/ProductHandleSelector.ts +1 -1
- package/src/common/ProductIdSelector.ts +1 -1
- package/src/common/ProductTagSelector.ts +18 -10
- package/src/common/ProductTypeSelector.ts +1 -1
- package/src/common/Selector.ts +16 -11
- package/src/common/SubscriptionItemSelector.ts +4 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DiscountCart } from "./DiscountCart";
|
|
2
2
|
import type { Qualifier } from "./Qualifier";
|
|
3
3
|
import type { Selector } from "./Selector";
|
|
4
|
-
import type { CartLine } from
|
|
4
|
+
import type { CartLine } from '../generated/api';
|
|
5
5
|
|
|
6
6
|
export class AndSelector {
|
|
7
7
|
is_a: "AndSelector";
|
package/src/common/BuyXGetY.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Campaign } from "./Campaign";
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import type {
|
|
2
|
+
import type { AndSelector } from './AndSelector';
|
|
3
|
+
import type { DiscountCart } from './DiscountCart';
|
|
4
|
+
import type {
|
|
5
|
+
ApplyDiscount,
|
|
6
|
+
DiscountInterface,
|
|
7
|
+
} from './DiscountInterface';
|
|
8
|
+
import type { OrSelector } from './OrSelector';
|
|
9
|
+
import type {
|
|
10
|
+
Qualifier,
|
|
11
|
+
QualifierBehavior,
|
|
12
|
+
} from './Qualifier';
|
|
13
|
+
import type { Selector } from './Selector';
|
|
9
14
|
|
|
10
15
|
export class BuyXGetY extends Campaign {
|
|
11
16
|
lineItemSelector?: Selector;
|
|
@@ -39,14 +44,19 @@ export class BuyXGetY extends Campaign {
|
|
|
39
44
|
// Find the items that qualify for buyX
|
|
40
45
|
let applicableBuyItems = discountCart.cart.lines;
|
|
41
46
|
if (this.lineItemSelector) {
|
|
42
|
-
applicableBuyItems = discountCart.cart.lines.filter(
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
applicableBuyItems = discountCart.cart.lines.filter(
|
|
48
|
+
(lineItem) => {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
return this.lineItemSelector?.match(lineItem);
|
|
51
|
+
}
|
|
52
|
+
);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
if (
|
|
48
|
-
applicableBuyItems.reduce(
|
|
49
|
-
|
|
56
|
+
applicableBuyItems.reduce(
|
|
57
|
+
(qty, item) => (qty += item.quantity),
|
|
58
|
+
0
|
|
59
|
+
) < this.buyX
|
|
50
60
|
) {
|
|
51
61
|
return discountCart;
|
|
52
62
|
}
|
|
@@ -54,9 +64,12 @@ export class BuyXGetY extends Campaign {
|
|
|
54
64
|
// Find the items that qualify for getX
|
|
55
65
|
let eligibleGetItems = discountCart.cart.lines;
|
|
56
66
|
if (this.getItemSelector) {
|
|
57
|
-
eligibleGetItems = discountCart.cart.lines.filter(
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
eligibleGetItems = discountCart.cart.lines.filter(
|
|
68
|
+
(lineItem) => {
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
return this.getItemSelector?.match(lineItem);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
// Check if cart qualifies for discounts and limit the discount sets
|
|
@@ -65,13 +78,18 @@ export class BuyXGetY extends Campaign {
|
|
|
65
78
|
0
|
|
66
79
|
);
|
|
67
80
|
const discountableSets = this.maxSets
|
|
68
|
-
? [
|
|
81
|
+
? [
|
|
82
|
+
purchasedQuantity / this.buyX,
|
|
83
|
+
this.maxSets,
|
|
84
|
+
].sort((a, b) => a - b)[0]
|
|
69
85
|
: Math.floor(purchasedQuantity / this.buyX);
|
|
70
86
|
|
|
71
87
|
if (discountableSets > 0) {
|
|
72
88
|
let discountableQuantity =
|
|
73
89
|
discountableSets *
|
|
74
|
-
(this.getY
|
|
90
|
+
(this.getY
|
|
91
|
+
? this.getY
|
|
92
|
+
: discountCart.totalLineItemQuantity());
|
|
75
93
|
|
|
76
94
|
// Apply the discounts (sort to discount lower priced items first)
|
|
77
95
|
eligibleGetItems = eligibleGetItems.sort(
|
|
@@ -86,7 +104,9 @@ export class BuyXGetY extends Campaign {
|
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
if (lineItem.quantity <= discountableQuantity) {
|
|
89
|
-
itemsToApplyDiscounts.push({
|
|
107
|
+
itemsToApplyDiscounts.push({
|
|
108
|
+
lineItem,
|
|
109
|
+
} as ApplyDiscount);
|
|
90
110
|
discountableQuantity -= lineItem.quantity;
|
|
91
111
|
} else {
|
|
92
112
|
itemsToApplyDiscounts.push({
|
|
@@ -98,7 +118,9 @@ export class BuyXGetY extends Campaign {
|
|
|
98
118
|
});
|
|
99
119
|
|
|
100
120
|
if (itemsToApplyDiscounts.length) {
|
|
101
|
-
const discount = this.discount.apply(
|
|
121
|
+
const discount = this.discount.apply(
|
|
122
|
+
itemsToApplyDiscounts
|
|
123
|
+
);
|
|
102
124
|
discountCart.addDiscount(discount);
|
|
103
125
|
}
|
|
104
126
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import type { CartLine } from
|
|
2
|
-
import type { DiscountCart } from
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import type { CartLine } from '../generated/api';
|
|
2
|
+
import type { DiscountCart } from './DiscountCart';
|
|
3
|
+
import {
|
|
4
|
+
type NumericalComparisonType,
|
|
5
|
+
Qualifier,
|
|
6
|
+
} from './Qualifier';
|
|
7
|
+
import { type Selector } from './Selector';
|
|
5
8
|
|
|
6
9
|
export enum CartHasItemBehavior {
|
|
7
|
-
QUANTITY =
|
|
8
|
-
SUBTOTAL =
|
|
10
|
+
QUANTITY = ':quantity',
|
|
11
|
+
SUBTOTAL = ':subtotal',
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
export class CartHasItemQualifier extends Qualifier {
|
|
@@ -24,7 +27,8 @@ export class CartHasItemQualifier extends Qualifier {
|
|
|
24
27
|
this.quantityOrSubtotal = quantityOrSubtotal;
|
|
25
28
|
this.comparisonType = comparisonType;
|
|
26
29
|
this.amount =
|
|
27
|
-
this.quantityOrSubtotal ==
|
|
30
|
+
this.quantityOrSubtotal ==
|
|
31
|
+
CartHasItemBehavior.SUBTOTAL
|
|
28
32
|
? amount
|
|
29
33
|
: Math.floor(amount);
|
|
30
34
|
this.itemSelector = itemSelector;
|
|
@@ -40,25 +44,37 @@ export class CartHasItemQualifier extends Qualifier {
|
|
|
40
44
|
|
|
41
45
|
switch (this.quantityOrSubtotal) {
|
|
42
46
|
case CartHasItemBehavior.QUANTITY:
|
|
43
|
-
total = discountCart.cart.lines.reduce(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
total = discountCart.cart.lines.reduce(
|
|
48
|
+
(quantity, line) => {
|
|
49
|
+
return (
|
|
50
|
+
quantity +
|
|
51
|
+
(this.itemSelector.match(line as CartLine)
|
|
52
|
+
? line.quantity
|
|
53
|
+
: 0)
|
|
54
|
+
);
|
|
55
|
+
},
|
|
56
|
+
0
|
|
57
|
+
);
|
|
49
58
|
break;
|
|
50
59
|
case CartHasItemBehavior.SUBTOTAL:
|
|
51
|
-
total = discountCart.cart.lines.reduce(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
total = discountCart.cart.lines.reduce(
|
|
61
|
+
(amount, line) => {
|
|
62
|
+
return (
|
|
63
|
+
amount +
|
|
64
|
+
(this.itemSelector.match(line as CartLine)
|
|
65
|
+
? line.cost.subtotalAmount.amount
|
|
66
|
+
: 0)
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
0
|
|
70
|
+
);
|
|
59
71
|
break;
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
return this.compareAmounts(
|
|
74
|
+
return this.compareAmounts(
|
|
75
|
+
total,
|
|
76
|
+
this.comparisonType,
|
|
77
|
+
this.amount
|
|
78
|
+
);
|
|
63
79
|
}
|
|
64
80
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type { CartLine } from
|
|
2
|
-
import type { DiscountCart } from
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import type { CartLine } from '../generated/api';
|
|
2
|
+
import type { DiscountCart } from './DiscountCart';
|
|
3
|
+
import {
|
|
4
|
+
type NumericalComparisonType,
|
|
5
|
+
Qualifier,
|
|
6
|
+
} from './Qualifier';
|
|
7
|
+
import { type Selector } from './Selector';
|
|
5
8
|
|
|
6
9
|
export enum CartQuantityBehavior {
|
|
7
|
-
CART =
|
|
8
|
-
ITEM =
|
|
9
|
-
LINE_ANY =
|
|
10
|
-
LINE_ALL =
|
|
10
|
+
CART = ':cart',
|
|
11
|
+
ITEM = ':item',
|
|
12
|
+
LINE_ANY = ':line_any',
|
|
13
|
+
LINE_ALL = ':line_all',
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export class CartQuantityQualifier extends Qualifier {
|
|
@@ -36,17 +39,25 @@ export class CartQuantityQualifier extends Qualifier {
|
|
|
36
39
|
|
|
37
40
|
switch (this.totalMethod) {
|
|
38
41
|
case CartQuantityBehavior.ITEM:
|
|
39
|
-
total = discountCart.cart.lines.reduce(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
total = discountCart.cart.lines.reduce(
|
|
43
|
+
(totalQty, line) => {
|
|
44
|
+
return (
|
|
45
|
+
totalQty +
|
|
46
|
+
((
|
|
47
|
+
selector
|
|
48
|
+
? selector.match(line as CartLine)
|
|
49
|
+
: true
|
|
50
|
+
)
|
|
51
|
+
? line.quantity
|
|
52
|
+
: 0)
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
0
|
|
56
|
+
);
|
|
47
57
|
break;
|
|
48
58
|
case CartQuantityBehavior.CART:
|
|
49
|
-
total =
|
|
59
|
+
total =
|
|
60
|
+
discountCart.totalLineItemQuantityExcludingGifts();
|
|
50
61
|
break;
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -55,9 +66,15 @@ export class CartQuantityQualifier extends Qualifier {
|
|
|
55
66
|
this.totalMethod == CartQuantityBehavior.LINE_ALL
|
|
56
67
|
) {
|
|
57
68
|
const qualifiedItems = discountCart.cart.lines
|
|
58
|
-
.filter((line) =>
|
|
69
|
+
.filter((line) =>
|
|
70
|
+
selector ? selector.match(line as CartLine) : true
|
|
71
|
+
)
|
|
59
72
|
.map((line) =>
|
|
60
|
-
this.compareAmounts(
|
|
73
|
+
this.compareAmounts(
|
|
74
|
+
line.quantity,
|
|
75
|
+
this.comparisonType,
|
|
76
|
+
this.quantity
|
|
77
|
+
)
|
|
61
78
|
);
|
|
62
79
|
|
|
63
80
|
switch (this.totalMethod) {
|
|
@@ -67,7 +84,11 @@ export class CartQuantityQualifier extends Qualifier {
|
|
|
67
84
|
return qualifiedItems.indexOf(false) == -1;
|
|
68
85
|
}
|
|
69
86
|
} else {
|
|
70
|
-
return this.compareAmounts(
|
|
87
|
+
return this.compareAmounts(
|
|
88
|
+
total,
|
|
89
|
+
this.comparisonType,
|
|
90
|
+
this.quantity
|
|
91
|
+
);
|
|
71
92
|
}
|
|
72
93
|
}
|
|
73
94
|
}
|
package/src/common/OrSelector.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DiscountCart } from "./DiscountCart";
|
|
2
2
|
import type { Qualifier } from "./Qualifier";
|
|
3
3
|
import type { Selector } from "./Selector";
|
|
4
|
-
import type { CartLine } from
|
|
4
|
+
import type { CartLine } from '../generated/api';
|
|
5
5
|
|
|
6
6
|
export class OrSelector {
|
|
7
7
|
is_a: "OrSelector";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CartLine,
|
|
3
3
|
ProductVariant,
|
|
4
|
-
} from
|
|
5
|
-
import { Selector } from
|
|
6
|
-
import {
|
|
4
|
+
} from '../generated/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;
|
|
@@ -23,19 +26,24 @@ export class ProductTagSelector extends Selector {
|
|
|
23
26
|
|
|
24
27
|
match(lineItem: CartLine) {
|
|
25
28
|
const variant = lineItem.merchandise as ProductVariant;
|
|
26
|
-
const productTags = variant.product.hasTags.reduce(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const productTags = variant.product.hasTags.reduce(
|
|
30
|
+
(tags, t) => {
|
|
31
|
+
if (t.hasTag) {
|
|
32
|
+
tags.push(t.tag.toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
return tags;
|
|
35
|
+
},
|
|
36
|
+
[] as string[]
|
|
37
|
+
);
|
|
32
38
|
|
|
33
39
|
let matchResult: boolean;
|
|
34
40
|
|
|
35
41
|
switch (this.matchCondition) {
|
|
36
42
|
case StringComparisonType.MATCH:
|
|
37
43
|
matchResult =
|
|
38
|
-
productTags.filter((tag) =>
|
|
44
|
+
productTags.filter((tag) =>
|
|
45
|
+
this.tags.includes(tag)
|
|
46
|
+
).length > 0;
|
|
39
47
|
break;
|
|
40
48
|
default:
|
|
41
49
|
matchResult = this.partialMatch(
|
package/src/common/Selector.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { CartLine } from
|
|
2
|
-
import { StringComparisonType } from
|
|
1
|
+
import type { CartLine } from '../generated/api';
|
|
2
|
+
import { StringComparisonType } from './Qualifier';
|
|
3
3
|
|
|
4
4
|
export enum MatchType {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
'ALL' = ':all',
|
|
6
|
+
'IS_ONE' = ':is_one',
|
|
7
|
+
'NOT_ONE' = ':not_one',
|
|
8
|
+
'DOES' = ':does',
|
|
9
|
+
'DOES_NOT' = ':does_not',
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export class Selector {
|
|
@@ -30,19 +30,24 @@ export class Selector {
|
|
|
30
30
|
possibleMatches: string[]
|
|
31
31
|
) {
|
|
32
32
|
// Convert to an array if it isn't one
|
|
33
|
-
const subject = Array.isArray(itemInfo)
|
|
33
|
+
const subject = Array.isArray(itemInfo)
|
|
34
|
+
? itemInfo
|
|
35
|
+
: [itemInfo];
|
|
34
36
|
let foundMatch = false;
|
|
35
37
|
possibleMatches.forEach((possibility) => {
|
|
36
38
|
subject.forEach((search) => {
|
|
37
39
|
switch (matchType) {
|
|
38
40
|
case StringComparisonType.CONTAINS:
|
|
39
|
-
if (search.search(possibility) > -1)
|
|
41
|
+
if (search.search(possibility) > -1)
|
|
42
|
+
foundMatch = true;
|
|
40
43
|
break;
|
|
41
44
|
case StringComparisonType.START_WITH:
|
|
42
|
-
if (search.startsWith(possibility))
|
|
45
|
+
if (search.startsWith(possibility))
|
|
46
|
+
foundMatch = true;
|
|
43
47
|
break;
|
|
44
48
|
case StringComparisonType.END_WITH:
|
|
45
|
-
if (search.endsWith(possibility))
|
|
49
|
+
if (search.endsWith(possibility))
|
|
50
|
+
foundMatch = true;
|
|
46
51
|
break;
|
|
47
52
|
}
|
|
48
53
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CartLine } from
|
|
2
|
-
import { MatchType, Selector } from
|
|
1
|
+
import type { CartLine } from '../generated/api';
|
|
2
|
+
import { MatchType, Selector } from './Selector';
|
|
3
3
|
|
|
4
4
|
export class SubscriptionItemSelector extends Selector {
|
|
5
5
|
invert: boolean;
|
|
@@ -10,7 +10,8 @@ export class SubscriptionItemSelector extends Selector {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
match(lineItem: CartLine) {
|
|
13
|
-
const matchResult =
|
|
13
|
+
const matchResult =
|
|
14
|
+
lineItem.sellingPlanAllocation !== undefined;
|
|
14
15
|
|
|
15
16
|
if (this.invert) {
|
|
16
17
|
return !matchResult;
|