@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.
Files changed (43) hide show
  1. package/migrate.ts +64 -0
  2. package/package.json +2 -1
  3. package/src/common/AndSelector.ts +17 -10
  4. package/src/common/Campaign.ts +27 -12
  5. package/src/common/CampaignFactory.ts +103 -51
  6. package/src/common/CartHasItemQualifier.ts +38 -22
  7. package/src/common/CartQuantityQualifier.ts +41 -20
  8. package/src/common/CountryCodeQualifier.ts +49 -0
  9. package/src/common/CustomerEmailQualifier.ts +14 -7
  10. package/src/common/CustomerTagQualifier.ts +29 -16
  11. package/src/common/DiscountCart.ts +78 -54
  12. package/src/common/DiscountInterface.ts +15 -3
  13. package/src/common/OrSelector.ts +13 -8
  14. package/src/common/ProductHandleSelector.ts +5 -3
  15. package/src/common/ProductIdSelector.ts +10 -4
  16. package/src/common/ProductTagSelector.ts +19 -10
  17. package/src/common/ProductTypeSelector.ts +10 -4
  18. package/src/common/SaleItemSelector.ts +2 -1
  19. package/src/common/Selector.ts +26 -13
  20. package/src/common/SubscriptionItemSelector.ts +5 -3
  21. package/src/index.ts +1 -1
  22. package/src/{common → lineItem}/BuyXGetY.ts +42 -20
  23. package/src/lineItem/ConditionalDiscount.ts +1 -1
  24. package/src/lineItem/FixedItemDiscount.ts +2 -4
  25. package/src/lineItem/PercentageDiscount.ts +2 -4
  26. package/src/shipping/FixedDiscount.ts +37 -0
  27. package/src/shipping/RateNameSelector.ts +54 -0
  28. package/src/shipping/ShippingDiscount.ts +75 -0
  29. package/src/shipping/api.ts +2097 -0
  30. package/tests/AndSelector.test.ts +1 -1
  31. package/tests/CartQuantityQualifier.test.ts +1 -1
  32. package/tests/CountryCodeQualifier.test.ts +56 -0
  33. package/tests/CustomerSubscriberQualifier.test.ts +1 -1
  34. package/tests/CustomerTagQualifier.test.ts +71 -0
  35. package/tests/DiscountCart.test.ts +1 -1
  36. package/tests/OrSelector.test.ts +1 -1
  37. package/tests/ProductIdSelector.test.ts +83 -0
  38. package/tests/ProductTagSelector.test.ts +1 -1
  39. package/tests/Qualifier.test.ts +1 -1
  40. package/tests/RateNameSelector.test.ts +107 -0
  41. package/tests/SaleItemSelector.test.ts +1 -1
  42. package/tests/Selector.test.ts +1 -1
  43. /package/src/{generated → lineItem}/api.ts +0 -0
@@ -1,11 +1,16 @@
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";
1
+ import { Campaign } from '../common/Campaign';
2
+ import type { AndSelector } from '../common/AndSelector';
3
+ import type { DiscountCart } from '../common/DiscountCart';
4
+ import type {
5
+ ApplyDiscount,
6
+ DiscountInterface,
7
+ } from '../common/DiscountInterface';
8
+ import type { OrSelector } from '../common/OrSelector';
9
+ import type {
10
+ Qualifier,
11
+ QualifierBehavior,
12
+ } from '../common/Qualifier';
13
+ import type { Selector } from '../common/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((lineItem) => {
43
- return this.lineItemSelector?.match(lineItem as CartLine);
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((qty, item) => (qty += item.quantity), 0) <
49
- this.buyX
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((lineItem) => {
58
- return this.getItemSelector?.match(lineItem as CartLine);
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
- ? [purchasedQuantity / this.buyX, this.maxSets].sort((a, b) => a - b)[0]
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 ? this.getY : discountCart.totalLineItemQuantity());
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({ lineItem } as ApplyDiscount);
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(itemsToApplyDiscounts);
121
+ const discount = this.discount.apply(
122
+ itemsToApplyDiscounts
123
+ );
102
124
  discountCart.addDiscount(discount);
103
125
  }
104
126
  }
@@ -1,4 +1,4 @@
1
- import type { CartLine } from '../generated/api';
1
+ import type { CartLine } from './api';
2
2
  import { Campaign } from '../common/Campaign';
3
3
  import type { DiscountCart } from '../common/DiscountCart';
4
4
  import type {
@@ -1,9 +1,7 @@
1
- import type {
2
- Discount,
3
- ProductVariant,
4
- } from '../generated/api';
1
+ import type { ProductVariant } from './api';
5
2
  import type {
6
3
  ApplyDiscount,
4
+ Discount,
7
5
  DiscountInterface,
8
6
  } from '../common/DiscountInterface';
9
7
 
@@ -1,8 +1,6 @@
1
+ import type { ProductVariant } from './api';
1
2
  import type {
2
3
  Discount,
3
- ProductVariant,
4
- } from '../generated/api';
5
- import type {
6
4
  ApplyDiscount,
7
5
  DiscountInterface,
8
6
  } from '../common/DiscountInterface';
@@ -41,6 +39,6 @@ export class PercentageDiscount
41
39
  },
42
40
  },
43
41
  message: this.message,
44
- };
42
+ } as Discount;
45
43
  }
46
44
  }
@@ -0,0 +1,37 @@
1
+ import type {
2
+ ApplyDiscount,
3
+ Discount,
4
+ DiscountInterface,
5
+ } from '../common/DiscountInterface';
6
+
7
+ export class FixedDiscount implements DiscountInterface {
8
+ amount: number;
9
+ message: string;
10
+
11
+ constructor(amount: number, message: string) {
12
+ this.amount = amount;
13
+ this.message = message;
14
+ }
15
+
16
+ apply(items: ApplyDiscount[]): Discount {
17
+ const targets = items.map(
18
+ ({ deliveryOption: { handle } }) => {
19
+ return {
20
+ deliveryOption: {
21
+ handle,
22
+ },
23
+ };
24
+ }
25
+ );
26
+
27
+ return {
28
+ targets,
29
+ value: {
30
+ fixedAmount: {
31
+ amount: this.amount,
32
+ },
33
+ },
34
+ message: this.message,
35
+ } as Discount;
36
+ }
37
+ }
@@ -0,0 +1,54 @@
1
+ import type { CartDeliveryOption } from '../shipping/api';
2
+ import { MatchType, Selector } from '../common/Selector';
3
+ import { StringComparisonType } from '../common/Qualifier';
4
+
5
+ export class RateNameSelector extends Selector {
6
+ invert: boolean;
7
+ matchCondition: StringComparisonType;
8
+ names: string[];
9
+
10
+ constructor(
11
+ matchType: MatchType.DOES | MatchType.DOES_NOT,
12
+ matchCondition: StringComparisonType,
13
+ names: string[]
14
+ ) {
15
+ super();
16
+ this.invert = matchType == MatchType.DOES_NOT;
17
+ this.matchCondition = matchCondition;
18
+ this.names = names.map((e) => e.toLowerCase());
19
+ }
20
+
21
+ match(subject: unknown): boolean;
22
+ match(subject: CartDeliveryOption) {
23
+ let matchResult;
24
+
25
+ const title = subject?.title?.toLowerCase() || '';
26
+
27
+ switch (this.matchCondition) {
28
+ case StringComparisonType.MATCH:
29
+ matchResult = this.names.includes(title);
30
+ break;
31
+ case StringComparisonType.CONTAINS:
32
+ matchResult =
33
+ this.names.filter((e) => title.indexOf(e) > -1)
34
+ .length > 0;
35
+ break;
36
+ case StringComparisonType.START_WITH:
37
+ matchResult =
38
+ this.names.filter((e) => title.startsWith(e))
39
+ .length > 0;
40
+ break;
41
+ case StringComparisonType.END_WITH:
42
+ matchResult =
43
+ this.names.filter((e) => title.endsWith(e))
44
+ .length > 0;
45
+ break;
46
+ }
47
+
48
+ if (this.invert) {
49
+ return !matchResult;
50
+ }
51
+
52
+ return matchResult;
53
+ }
54
+ }
@@ -0,0 +1,75 @@
1
+ import type { CartDeliveryOption } from './api';
2
+ import { Campaign } from '../common/Campaign';
3
+ import type { DiscountCart } from '../common/DiscountCart';
4
+ import type {
5
+ ApplyDiscount,
6
+ DiscountInterface,
7
+ } from '../common/DiscountInterface';
8
+ import type {
9
+ QualifierBehavior,
10
+ Qualifier,
11
+ } from '../common/Qualifier';
12
+ import type { Selector } from '../common/Selector';
13
+ import type { AndSelector } from '../common/AndSelector';
14
+ import type { OrSelector } from '../common/OrSelector';
15
+
16
+ export class ShippingDiscount extends Campaign {
17
+ discount: DiscountInterface;
18
+ rateSelector: Selector;
19
+
20
+ constructor(
21
+ behavior: QualifierBehavior,
22
+ qualifiers:
23
+ | (Qualifier | AndSelector | OrSelector)[]
24
+ | null,
25
+ discount: DiscountInterface,
26
+ rateSelector: Selector
27
+ ) {
28
+ super(behavior, qualifiers);
29
+ this.discount = discount;
30
+ this.rateSelector = rateSelector;
31
+ }
32
+
33
+ run(discountCart: DiscountCart) {
34
+ // First we see if this cart qualifies for this discount
35
+ if (this.qualifies(discountCart)) {
36
+ // Now that it qualifies, we can determine the applicableDeliveryOptions
37
+ // to apply discounts to by...
38
+ // Loop through all deliveryGroups
39
+ const applicableDiscounts =
40
+ discountCart.cart.deliveryGroups
41
+ .reduce((deliveryOptions, deliveryGroup) => {
42
+ // Loop through its delivery options
43
+ deliveryGroup.deliveryOptions.forEach((o) => {
44
+ const deliveryOption =
45
+ o as CartDeliveryOption;
46
+ // 2. Looping any deliveryOptions within the group
47
+ if (
48
+ this.rateSelector &&
49
+ this.rateSelector.match(deliveryOption)
50
+ ) {
51
+ // if this is a match, we want to apply this discount
52
+ deliveryOptions.push(deliveryOption);
53
+ }
54
+ });
55
+
56
+ return deliveryOptions;
57
+ }, [] as CartDeliveryOption[])
58
+ .map((deliveryOption) => {
59
+ return {
60
+ deliveryOption: {
61
+ handle: deliveryOption.handle,
62
+ },
63
+ } as ApplyDiscount;
64
+ });
65
+
66
+ if (applicableDiscounts.length) {
67
+ const discount = this.discount.apply(
68
+ applicableDiscounts
69
+ );
70
+ discountCart.addDiscount(discount);
71
+ }
72
+ }
73
+ return discountCart;
74
+ }
75
+ }