@zoxllc/shopify-checkout-extensions 0.0.3 → 0.0.5

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 +1 -1
  7. package/src/common/CartQuantityQualifier.ts +1 -1
  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 -51
  12. package/src/common/DiscountInterface.ts +17 -2
  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 +2 -1
  17. package/src/common/ProductTypeSelector.ts +10 -4
  18. package/src/common/SaleItemSelector.ts +2 -1
  19. package/src/common/Selector.ts +11 -3
  20. package/src/common/SubscriptionItemSelector.ts +2 -1
  21. package/src/index.ts +1 -1
  22. package/src/{common → lineItem}/BuyXGetY.ts +7 -7
  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 +2014 -0
  30. package/tests/AndSelector.test.ts +1 -1
  31. package/tests/CartQuantityQualifier.test.ts +1 -1
  32. package/tests/CountryCodeQualifier.test.ts +55 -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,12 +1,18 @@
1
- import type { RunInput, Discount } from '../generated/api';
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
+ };
2
8
 
3
9
  export class DiscountCart {
4
- cart: RunInput["cart"];
10
+ cart: RunInput['cart'];
5
11
  subtotalAmount: number;
6
12
  appliedDiscountTotal: number;
7
13
  discounts: Discount[];
8
14
 
9
- constructor(cart: RunInput["cart"]) {
15
+ constructor(cart: RunInput['cart']) {
10
16
  this.cart = cart;
11
17
  this.subtotalAmount = 0;
12
18
  this.appliedDiscountTotal = 0;
@@ -17,7 +23,7 @@ export class DiscountCart {
17
23
 
18
24
  subtotalExcludingVariants(variants: string[]) {
19
25
  return this.cart.lines.reduce((prev, curr) => {
20
- if (curr.merchandise.__typename == "ProductVariant") {
26
+ if (curr.merchandise.__typename == 'ProductVariant') {
21
27
  if (variants.indexOf(curr.merchandise.id) > -1) {
22
28
  return prev - curr.cost.subtotalAmount.amount;
23
29
  }
@@ -29,10 +35,10 @@ export class DiscountCart {
29
35
 
30
36
  private subtotalExcludingGifts() {
31
37
  return this.cart.lines.reduce((prev, curr) => {
32
- if (curr.merchandise.__typename == "ProductVariant") {
38
+ if (curr.merchandise.__typename == 'ProductVariant') {
33
39
  if (
34
40
  curr.merchandise.product.hasTags.filter(
35
- (t) => t.hasTag && t.tag == "meta-exclude-gift"
41
+ (t) => t.hasTag && t.tag == 'meta-exclude-gift'
36
42
  ).length
37
43
  ) {
38
44
  return prev - curr.cost.subtotalAmount.amount;
@@ -44,15 +50,18 @@ export class DiscountCart {
44
50
  }
45
51
 
46
52
  totalLineItemQuantity() {
47
- return this.cart.lines.reduce((total, item) => total + item.quantity, 0);
53
+ return this.cart.lines.reduce(
54
+ (total, item) => total + item.quantity,
55
+ 0
56
+ );
48
57
  }
49
58
 
50
59
  totalLineItemQuantityExcludingGifts() {
51
60
  return this.cart.lines.reduce((total, item) => {
52
- if (item.merchandise.__typename == "ProductVariant") {
61
+ if (item.merchandise.__typename == 'ProductVariant') {
53
62
  if (
54
63
  item.merchandise.product.hasTags.filter(
55
- (t) => t.hasTag && t.tag == "meta-exclude-gift"
64
+ (t) => t.hasTag && t.tag == 'meta-exclude-gift'
56
65
  ).length
57
66
  ) {
58
67
  return total;
@@ -62,8 +71,9 @@ export class DiscountCart {
62
71
  }, 0);
63
72
  }
64
73
 
65
- private setupCart(cart: RunInput["cart"]) {
66
- this.subtotalAmount = cart.cost.subtotalAmount.amount as number;
74
+ private setupCart(cart: RunInput['cart']) {
75
+ this.subtotalAmount = cart.cost.subtotalAmount
76
+ .amount as number;
67
77
  // Adjust the subtotal amount by excluding the value from gift items
68
78
  this.subtotalAmount = this.subtotalExcludingGifts();
69
79
  this.appliedDiscountTotal = 0;
@@ -72,51 +82,68 @@ export class DiscountCart {
72
82
 
73
83
  addDiscount(discount: Discount) {
74
84
  // Figure out how much discount has been applied
75
- const totalDiscounts = discount.targets.reduce((total, target) => {
76
- // gather target lineItem
77
- const lineItems = this.cart.lines.filter((line) =>
78
- line.merchandise.__typename == "ProductVariant"
79
- ? line.merchandise.id == target.productVariant.id
80
- : false
81
- );
82
-
83
- // loop over the line items and do maths
84
- lineItems.forEach((line) => {
85
- let discountableItems = target.productVariant.quantity
86
- ? target.productVariant.quantity
87
- : line.quantity;
88
-
89
- const lineItemCost = parseFloat(line.cost.amountPerQuantity.amount);
90
-
91
- // TODO: Make sure we're not discounting more items than are available on the line item quantity
92
-
93
- // When it's a fixed amount, calculate the items per but only if it applies to each
94
- // individual item...
95
- if (discount.value.fixedAmount) {
96
- if (discount.value.fixedAmount.appliesToEachItem) {
97
- total +=
98
- lineItemCost - discount.value.fixedAmount?.amount > 0
99
- ? discount.value.fixedAmount?.amount * discountableItems
100
- : lineItemCost * discountableItems;
101
- } else {
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) {
102
130
  total +=
103
- lineItemCost - discount.value.fixedAmount?.amount > 0
104
- ? discount.value.fixedAmount?.amount
105
- : lineItemCost;
131
+ lineItemCost *
132
+ (discount.value.percentage?.value / 100) *
133
+ discountableItems;
106
134
  }
107
- } else if (discount.value.percentage) {
108
- total +=
109
- lineItemCost *
110
- (discount.value.percentage?.value / 100) *
111
- discountableItems;
112
- }
113
- });
135
+ });
114
136
 
115
- return total;
116
- }, 0);
137
+ return total;
138
+ },
139
+ 0
140
+ );
117
141
 
118
142
  this.appliedDiscountTotal += totalDiscounts;
119
- console.error("appliedDiscountTotal", this.appliedDiscountTotal);
143
+ console.error(
144
+ 'appliedDiscountTotal',
145
+ this.appliedDiscountTotal
146
+ );
120
147
 
121
148
  this.discounts.push(discount);
122
149
  }
@@ -1,10 +1,25 @@
1
- import type { CartLine, Discount } from '../generated/api';
1
+ import type {
2
+ CartDeliveryOption,
3
+ CartLine,
4
+ } from '../lineItem/api';
2
5
 
3
- export type ApplyDiscount = {
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 = {
4
12
  lineItem: CartLine;
5
13
  maxDiscounts?: number;
6
14
  };
7
15
 
16
+ type ApplyShippingDiscount = {
17
+ deliveryOption: CartDeliveryOption;
18
+ };
19
+
20
+ export type ApplyDiscount = ApplyLineItemDiscount &
21
+ ApplyShippingDiscount;
22
+
8
23
  export interface DiscountInterface {
9
24
  apply(items: ApplyDiscount[]): Discount;
10
25
  }
@@ -1,18 +1,21 @@
1
- import type { DiscountCart } from "./DiscountCart";
2
- import type { Qualifier } from "./Qualifier";
3
- import type { Selector } from "./Selector";
4
- import type { CartLine } from '../generated/api';
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: "OrSelector";
7
+ is_a: 'OrSelector';
8
8
  conditions: (Selector | Qualifier)[];
9
9
 
10
10
  constructor(conditions: (Selector | Qualifier)[]) {
11
- this.is_a = "OrSelector";
11
+ this.is_a = 'OrSelector';
12
12
  this.conditions = conditions;
13
13
  }
14
14
 
15
- match(item: CartLine | DiscountCart, selector?: Selector) {
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(item as CartLine);
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 '../generated/api';
5
- import { MatchType, Selector } from "./Selector";
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(variant.product.handle.toLowerCase()) > -1;
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 '../generated/api';
5
- import { MatchType, Selector } from "./Selector";
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(matchType: MatchType, productIds: string[]) {
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(variant.product.id);
24
+ const matchResult = this.productIds.includes(
25
+ variant.product.id
26
+ );
21
27
 
22
28
  if (this.invert) {
23
29
  return !matchResult;
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  CartLine,
3
3
  ProductVariant,
4
- } from '../generated/api';
4
+ } from '../lineItem/api';
5
5
  import { Selector } from './Selector';
6
6
  import {
7
7
  QualifierMatchType,
@@ -24,6 +24,7 @@ export class ProductTagSelector extends Selector {
24
24
  this.tags = tags.map((t) => t.toLowerCase());
25
25
  }
26
26
 
27
+ match(subject: unknown): boolean;
27
28
  match(lineItem: CartLine) {
28
29
  const variant = lineItem.merchandise as ProductVariant;
29
30
  const productTags = variant.product.hasTags.reduce(
@@ -1,19 +1,25 @@
1
1
  import type {
2
2
  CartLine,
3
3
  ProductVariant,
4
- } from '../generated/api';
5
- import { MatchType, Selector } from "./Selector";
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(matchType: MatchType, productTypes: string[]) {
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) => item.toLowerCase());
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 '../generated/api';
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 =
@@ -1,4 +1,5 @@
1
- import type { CartLine } from '../generated/api';
1
+ import type { CartDeliveryOption } from '~/shipping/api';
2
+ import type { CartLine } from '../lineItem/api';
2
3
  import { StringComparisonType } from './Qualifier';
3
4
 
4
5
  export enum MatchType {
@@ -9,8 +10,15 @@ export enum MatchType {
9
10
  'DOES_NOT' = ':does_not',
10
11
  }
11
12
 
12
- export class Selector {
13
- match(subject: CartLine, selector?: Selector) {
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
 
@@ -1,4 +1,4 @@
1
- import type { CartLine } from '../generated/api';
1
+ import type { CartLine } from '../lineItem/api';
2
2
  import { MatchType, Selector } from './Selector';
3
3
 
4
4
  export class SubscriptionItemSelector extends Selector {
@@ -9,6 +9,7 @@ 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
14
  const matchResult =
14
15
  lineItem.sellingPlanAllocation !== undefined;
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 './common/BuyXGetY';
5
+ export { BuyXGetY } from './lineItem/BuyXGetY';
6
6
  export { Campaign } from './common/Campaign';
7
7
  export type {
8
8
  CampaignType,
@@ -1,16 +1,16 @@
1
- import { Campaign } from "./Campaign";
2
- import type { AndSelector } from './AndSelector';
3
- import type { DiscountCart } from './DiscountCart';
1
+ import { Campaign } from '../common/Campaign';
2
+ import type { AndSelector } from '../common/AndSelector';
3
+ import type { DiscountCart } from '../common/DiscountCart';
4
4
  import type {
5
5
  ApplyDiscount,
6
6
  DiscountInterface,
7
- } from './DiscountInterface';
8
- import type { OrSelector } from './OrSelector';
7
+ } from '../common/DiscountInterface';
8
+ import type { OrSelector } from '../common/OrSelector';
9
9
  import type {
10
10
  Qualifier,
11
11
  QualifierBehavior,
12
- } from './Qualifier';
13
- import type { Selector } from './Selector';
12
+ } from '../common/Qualifier';
13
+ import type { Selector } from '../common/Selector';
14
14
 
15
15
  export class BuyXGetY extends Campaign {
16
16
  lineItemSelector?: Selector;
@@ -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
+ }