@unchainedshop/plugins 2.0.0-rc.4 → 2.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.
Files changed (82) hide show
  1. package/jest.config.js +12 -0
  2. package/lib/enrollments/licensed.d.ts +4 -1
  3. package/lib/enrollments/licensed.js +1 -1
  4. package/lib/enrollments/licensed.js.map +1 -1
  5. package/lib/files/gridfs/index.js +12 -3
  6. package/lib/files/gridfs/index.js.map +1 -1
  7. package/lib/plugins-index.d.ts +1 -0
  8. package/lib/plugins-index.js +1 -0
  9. package/lib/plugins-index.js.map +1 -1
  10. package/lib/pricing/discount-100-off.d.ts +2 -1
  11. package/lib/pricing/discount-100-off.js +1 -1
  12. package/lib/pricing/discount-100-off.js.map +1 -1
  13. package/lib/pricing/discount-half-price-manual.d.ts +2 -1
  14. package/lib/pricing/discount-half-price-manual.js +1 -1
  15. package/lib/pricing/discount-half-price-manual.js.map +1 -1
  16. package/lib/pricing/discount-half-price.d.ts +2 -1
  17. package/lib/pricing/discount-half-price.js +1 -1
  18. package/lib/pricing/discount-half-price.js.map +1 -1
  19. package/lib/pricing/order-delivery.d.ts +2 -1
  20. package/lib/pricing/order-delivery.js +1 -1
  21. package/lib/pricing/order-delivery.js.map +1 -1
  22. package/lib/pricing/order-discount.d.ts +10 -1
  23. package/lib/pricing/order-discount.js +5 -4
  24. package/lib/pricing/order-discount.js.map +1 -1
  25. package/lib/pricing/order-items-discount.js +4 -0
  26. package/lib/pricing/order-items-discount.js.map +1 -1
  27. package/lib/pricing/product-catalog-price.d.ts +2 -1
  28. package/lib/pricing/product-catalog-price.js +1 -1
  29. package/lib/pricing/product-catalog-price.js.map +1 -1
  30. package/lib/pricing/product-discount.d.ts +2 -1
  31. package/lib/pricing/product-discount.js +2 -2
  32. package/lib/pricing/product-discount.js.map +1 -1
  33. package/lib/pricing/product-price-rateconversion.d.ts +2 -1
  34. package/lib/pricing/product-price-rateconversion.js +1 -1
  35. package/lib/pricing/product-price-rateconversion.js.map +1 -1
  36. package/lib/pricing/product-price-round.d.ts +2 -0
  37. package/lib/pricing/product-price-round.js +3 -3
  38. package/lib/pricing/product-price-round.js.map +1 -1
  39. package/lib/pricing/product-swiss-tax.d.ts +8 -0
  40. package/lib/pricing/product-swiss-tax.js +3 -3
  41. package/lib/pricing/product-swiss-tax.js.map +1 -1
  42. package/lib/worker/email.d.ts +1 -1
  43. package/lib/worker/email.js +1 -1
  44. package/lib/worker/email.js.map +1 -1
  45. package/lib/worker/push-notification.d.ts +1 -0
  46. package/lib/worker/push-notification.js +73 -0
  47. package/lib/worker/push-notification.js.map +1 -0
  48. package/package.json +23 -23
  49. package/src/enrollments/licensed.ts +1 -1
  50. package/src/files/gridfs/index.ts +14 -3
  51. package/src/plugins-index.ts +1 -0
  52. package/src/pricing/apply-rate.test.ts +92 -0
  53. package/src/pricing/configureProductPriceRounding.test.ts +44 -0
  54. package/src/pricing/discount-100-off.test.ts +31 -0
  55. package/src/pricing/discount-100-off.ts +1 -1
  56. package/src/pricing/discount-half-price-manual.test.ts +42 -0
  57. package/src/pricing/discount-half-price-manual.ts +1 -1
  58. package/src/pricing/discount-half-price.test.ts +40 -0
  59. package/src/pricing/discount-half-price.ts +1 -1
  60. package/src/pricing/free-delivery.test.ts +12 -0
  61. package/src/pricing/getTaxRate.test.ts +69 -0
  62. package/src/pricing/licenced-enrollment.test.ts +37 -0
  63. package/src/pricing/order-delivery.test.ts +9 -0
  64. package/src/pricing/order-delivery.ts +1 -1
  65. package/src/pricing/order-discount.test.ts +206 -0
  66. package/src/pricing/order-discount.ts +6 -4
  67. package/src/pricing/order-items-discount.ts +4 -1
  68. package/{tests/plugins-index.test.js → src/pricing/plugins-index.test.ts} +0 -0
  69. package/src/pricing/product-catalog-price.ts +1 -1
  70. package/src/pricing/product-discount.ts +2 -2
  71. package/src/pricing/product-price-rateconversion.ts +1 -1
  72. package/src/pricing/product-price-round.ts +4 -4
  73. package/src/pricing/product-swiss-tax.test.ts +141 -0
  74. package/src/pricing/product-swiss-tax.ts +3 -3
  75. package/src/pricing/roundToNext.test.ts +24 -0
  76. package/src/worker/email.ts +1 -1
  77. package/src/worker/push-notification.ts +106 -0
  78. package/tsconfig.json +3 -22
  79. package/lib/pricing/utils/cache.d.ts +0 -9
  80. package/lib/pricing/utils/cache.js +0 -29
  81. package/lib/pricing/utils/cache.js.map +0 -1
  82. package/src/pricing/utils/cache.ts +0 -34
@@ -0,0 +1,206 @@
1
+ import { applyDiscountToMultipleShares, calculateAmountToSplit, resolveAmountAndTax, resolveRatioAndTaxDivisorForPricingSheet } from "./order-discount.js";
2
+
3
+
4
+
5
+ describe("OrderDiscount helpers ", () => {
6
+ describe("resolveRatioAndTaxDivisorForPricingSheet", () => {
7
+ it('total is 0 and pricing is not provided', () => {
8
+ const result = resolveRatioAndTaxDivisorForPricingSheet(null, 0);
9
+ expect(result).toEqual({ ratio: 1, taxDivisor: 1 });
10
+ });
11
+ it('total is not 0 and pricing is not provided', () => {
12
+ const result = resolveRatioAndTaxDivisorForPricingSheet(null, 10);
13
+ expect(result).toEqual({ ratio: 1, taxDivisor: 1 });
14
+ });
15
+ it('total is 0 and pricing is provided', () => {
16
+ const pricing = {
17
+ taxSum: () => 10,
18
+ gross: () => 20,
19
+ };
20
+ const result = resolveRatioAndTaxDivisorForPricingSheet(pricing, 0);
21
+ expect(result).toEqual({ ratio: 1, taxDivisor: 1 });
22
+ });
23
+
24
+ it('gross - tax is 0', () => {
25
+ const pricing = {
26
+ taxSum: () => 10,
27
+ gross: () => 10,
28
+ };
29
+ const result = resolveRatioAndTaxDivisorForPricingSheet(pricing, 20);
30
+ expect(result).toEqual({ ratio: 0, taxDivisor: 0 });
31
+ });
32
+
33
+ it('gross - tax is not 0', () => {
34
+ const pricing = {
35
+ taxSum: () => 10,
36
+ gross: () => 20,
37
+ };
38
+ const result = resolveRatioAndTaxDivisorForPricingSheet(pricing, 20);
39
+ expect(result).toEqual({ ratio: 1, taxDivisor: 2 });
40
+ });
41
+
42
+ it('taxSum is 0', () => {
43
+ const pricing = {
44
+ taxSum: () => 0,
45
+ gross: () => 20,
46
+ };
47
+ const result = resolveRatioAndTaxDivisorForPricingSheet(pricing, 20);
48
+ expect(result).toEqual({ ratio: 1, taxDivisor: 1 });
49
+ });
50
+
51
+ })
52
+
53
+
54
+
55
+ describe("resolveAmountAndTax", () => {
56
+
57
+ it('ratio is 0 and taxDivisor is 0', () => {
58
+ const result = resolveAmountAndTax({ ratio: 0, taxDivisor: 0 }, 100);
59
+ expect(result).toEqual([0, 0]);
60
+ });
61
+
62
+ it('ratio is not 0 and taxDivisor is 0', () => {
63
+ const result = resolveAmountAndTax({ ratio: 0.5, taxDivisor: 0 }, 100);
64
+ expect(result).toEqual([50, 0]);
65
+ });
66
+
67
+ it('ratio is 0 and taxDivisor is not 0', () => {
68
+ const result = resolveAmountAndTax({ ratio: 0, taxDivisor: 2 }, 100);
69
+ expect(result).toEqual([0, 0]);
70
+ });
71
+
72
+
73
+ it('ratio and taxDivisor are not 0', () => {
74
+ const result = resolveAmountAndTax({ ratio: 0.5, taxDivisor: 2 }, 100);
75
+ expect(result).toEqual([50, 25]);
76
+ });
77
+
78
+ it('ratio and taxDivisor are not finite number', () => {
79
+ const result = resolveAmountAndTax({ ratio: Number.NaN, taxDivisor: Number.NaN }, 100);
80
+ expect(result).toEqual([0, 0]);
81
+ });
82
+
83
+
84
+
85
+ })
86
+
87
+ describe("resolveAmountAndTax", () => {
88
+ it('shares are empty', () => {
89
+ const result = applyDiscountToMultipleShares([], 100);
90
+ expect(result).toEqual([0, 0]);
91
+ });
92
+
93
+ it('shares have one share', () => {
94
+ const shares = [{ ratio: 0.5, taxDivisor: 2 }];
95
+ const result = applyDiscountToMultipleShares(shares, 100);
96
+ expect(result).toEqual([50, 25]);
97
+ });
98
+
99
+ it('shares have multiple shares', () => {
100
+ const shares = [
101
+ { ratio: 0.5, taxDivisor: 2 },
102
+ { ratio: 0.2, taxDivisor: 1.5 },
103
+ ];
104
+ const result = applyDiscountToMultipleShares(shares, 100);
105
+ expect(result).toEqual([70, 31.666666666666664]);
106
+ });
107
+
108
+ })
109
+
110
+ describe('calculateAmountToSplit', () => {
111
+ it('calculates the correct amount to split when using a rate', () => {
112
+ const configuration = {
113
+ rate: 0.1,
114
+ alreadyDeductedForDiscount: 0,
115
+ amountLeft: 100,
116
+ };
117
+ const amount = 1000;
118
+ expect(calculateAmountToSplit(configuration, amount)).toBe(100);
119
+ });
120
+
121
+ it('calculates the correct amount to split when using a fixed rate', () => {
122
+ const configuration = {
123
+ fixedRate: 50,
124
+ alreadyDeductedForDiscount: 0,
125
+ amountLeft: 100,
126
+ };
127
+ const amount = 1000;
128
+ expect(calculateAmountToSplit(configuration, amount)).toBe(50);
129
+ });
130
+
131
+ it('calculates the correct amount to split when alreadyDeductedForDiscount is non-zero', () => {
132
+ const configuration = {
133
+ rate: 0.1,
134
+ alreadyDeductedForDiscount: 10,
135
+ amountLeft: 100,
136
+ };
137
+ const amount = 1000;
138
+ expect(calculateAmountToSplit(configuration, amount)).toBe(90);
139
+ });
140
+
141
+
142
+ it('calculates the correct amount to split when amountLeft is less than the amount to deduct', () => {
143
+ const configuration = {
144
+ rate: 0.1,
145
+ alreadyDeductedForDiscount: 0,
146
+ amountLeft: 50,
147
+ };
148
+ const amount = 1000;
149
+ expect(calculateAmountToSplit(configuration, amount)).toBe(50);
150
+ });
151
+
152
+ it('returns 0 when the amount to deduct is less than or equal to alreadyDeductedForDiscount', () => {
153
+ const configuration = {
154
+ rate: 0.1,
155
+ alreadyDeductedForDiscount: 100,
156
+ amountLeft: 100,
157
+ };
158
+ const amount = 1000;
159
+ expect(calculateAmountToSplit(configuration, amount)).toBe(0);
160
+ });
161
+
162
+ it('returns 0 when the amount is less than or equal to 0', () => {
163
+ const configuration = {
164
+ rate: 0.1,
165
+ alreadyDeductedForDiscount: 0,
166
+ amountLeft: 100,
167
+ };
168
+ const amount = 0;
169
+ expect(calculateAmountToSplit(configuration, amount)).toBe(0);
170
+ });
171
+
172
+ it('returns 0 when amountLeft is 0', () => {
173
+ const configuration = {
174
+ rate: 0.1,
175
+ alreadyDeductedForDiscount: 0,
176
+ amountLeft: 0,
177
+ };
178
+ const amount = 1000;
179
+ expect(calculateAmountToSplit(configuration, amount)).toBe(0);
180
+ });
181
+
182
+ it('returns 0 when amount is negative', () => {
183
+ const configuration = {
184
+ rate: 0.1,
185
+ alreadyDeductedForDiscount: 0,
186
+ amountLeft: 100,
187
+ };
188
+ const amount = -1000;
189
+ expect(calculateAmountToSplit(configuration, amount)).toBe(0);
190
+ });
191
+
192
+ it('returns 0 when configuration rate is negative', () => {
193
+ const configuration = {
194
+ rate: -0.1,
195
+ alreadyDeductedForDiscount: 0,
196
+ amountLeft: 100,
197
+ };
198
+ const amount = 1000;
199
+ expect(calculateAmountToSplit(configuration, amount)).toBe(0);
200
+ });
201
+
202
+
203
+ });
204
+
205
+
206
+ })
@@ -1,7 +1,7 @@
1
1
  import { IOrderPricingAdapter, OrderPricingRowCategory } from '@unchainedshop/types/orders.pricing.js';
2
2
  import { OrderPricingDirector, OrderPricingAdapter } from '@unchainedshop/core-orders';
3
3
 
4
- const resolveRatioAndTaxDivisorForPricingSheet = (pricing, total) => {
4
+ export const resolveRatioAndTaxDivisorForPricingSheet = (pricing, total) => {
5
5
  if (total === 0 || !pricing) {
6
6
  return {
7
7
  ratio: 1,
@@ -22,14 +22,16 @@ const resolveRatioAndTaxDivisorForPricingSheet = (pricing, total) => {
22
22
  };
23
23
  };
24
24
 
25
- const resolveAmountAndTax = ({ ratio, taxDivisor }, amount) => {
25
+ export const resolveAmountAndTax = ({ ratio, taxDivisor }, amount) => {
26
26
  const shareAmount = Number.isFinite(ratio) ? amount * ratio : 0;
27
27
  const shareTaxAmount =
28
28
  Number.isFinite(taxDivisor) && taxDivisor !== 0 ? shareAmount - shareAmount / taxDivisor : 0;
29
29
  return [shareAmount, shareTaxAmount];
30
30
  };
31
31
 
32
- const applyDiscountToMultipleShares = (shares, amount) => {
32
+ // TODO: move to separate file because its used in multiple places
33
+
34
+ export const applyDiscountToMultipleShares = (shares, amount) => {
33
35
  return shares.reduce(
34
36
  ([currentDiscountAmount, currentTaxAmount], share) => {
35
37
  const [shareAmount, shareTaxAmount] = resolveAmountAndTax(share, amount);
@@ -39,7 +41,7 @@ const applyDiscountToMultipleShares = (shares, amount) => {
39
41
  );
40
42
  };
41
43
 
42
- const calculateAmountToSplit = (configuration, amount) => {
44
+ export const calculateAmountToSplit = (configuration, amount) => {
43
45
  const deductionAmount = configuration.rate ? amount * configuration.rate : configuration.fixedRate;
44
46
 
45
47
  const leftInDiscount = Math.max(0, deductionAmount - (configuration.alreadyDeductedForDiscount || 0));
@@ -1,6 +1,6 @@
1
1
  import { IOrderPricingAdapter, OrderPricingRowCategory } from '@unchainedshop/types/orders.pricing.js';
2
2
  import { OrderPricingDirector, OrderPricingAdapter } from '@unchainedshop/core-orders';
3
-
3
+ // TODO: move into separate file and share multiple places
4
4
  const resolveRatioAndTaxDivisorForPricingSheet = (pricing, total) => {
5
5
  if (total === 0 || !pricing) {
6
6
  return {
@@ -16,12 +16,14 @@ const resolveRatioAndTaxDivisorForPricingSheet = (pricing, total) => {
16
16
  };
17
17
  };
18
18
 
19
+ // TODO: move into separate file and share multiple places
19
20
  const resolveAmountAndTax = ({ ratio, taxDivisor }, amount) => {
20
21
  const shareAmount = Number.isFinite(ratio) ? amount * ratio : 0;
21
22
  const shareTaxAmount = Number.isFinite(taxDivisor) ? shareAmount - shareAmount / taxDivisor : 0;
22
23
  return [shareAmount, shareTaxAmount];
23
24
  };
24
25
 
26
+ // TODO: move into separate file and share multiple places
25
27
  const applyDiscountToMultipleShares = (shares, amount) => {
26
28
  return shares.reduce(
27
29
  ([currentDiscountAmount, currentTaxAmount], share) => {
@@ -32,6 +34,7 @@ const applyDiscountToMultipleShares = (shares, amount) => {
32
34
  );
33
35
  };
34
36
 
37
+ // TODO: move into separate file and share multiple places
35
38
  const calculateAmountToSplit = (configuration, amount) => {
36
39
  const deductionAmount = configuration.rate
37
40
  ? amount * configuration.rate
@@ -1,7 +1,7 @@
1
1
  import { IProductPricingAdapter } from '@unchainedshop/types/products.pricing.js';
2
2
  import { ProductPricingDirector, ProductPricingAdapter } from '@unchainedshop/core-products';
3
3
 
4
- const ProductPrice: IProductPricingAdapter = {
4
+ export const ProductPrice: IProductPricingAdapter = {
5
5
  ...ProductPricingAdapter,
6
6
 
7
7
  key: 'shop.unchained.pricing.product-price',
@@ -5,9 +5,9 @@ import {
5
5
  } from '@unchainedshop/types/products.pricing.js';
6
6
  import { ProductPricingDirector, ProductPricingAdapter } from '@unchainedshop/core-products';
7
7
 
8
- const applyRate = (configuration: DiscountConfiguration, amount) => {
8
+ export const applyRate = (configuration: DiscountConfiguration, amount) => {
9
9
  const { rate, fixedRate } = configuration;
10
- return rate ? amount * rate : Math.min(fixedRate, amount);
10
+ return rate ? amount * rate : Math.min(fixedRate || 0, amount);
11
11
  };
12
12
  const ProductDiscount: IProductPricingAdapter = {
13
13
  ...ProductPricingAdapter,
@@ -1,7 +1,7 @@
1
1
  import { IProductPricingAdapter } from '@unchainedshop/types/products.pricing.js';
2
2
  import { ProductPricingDirector, ProductPricingAdapter } from '@unchainedshop/core-products';
3
3
 
4
- const ProductPriceRateConversion: IProductPricingAdapter = {
4
+ export const ProductPriceRateConversion: IProductPricingAdapter = {
5
5
  ...ProductPricingAdapter,
6
6
 
7
7
  key: 'shop.unchained.pricing.rate-conversion',
@@ -1,8 +1,8 @@
1
1
  import { IProductPricingAdapter } from '@unchainedshop/types/products.pricing.js';
2
- import { ProductPricingDirector, ProductPricingAdapter } from '@unchainedshop/core-products';
3
-
4
- const roundToNext = (value, precision) => Math.ceil(value / precision) * precision;
2
+ import { ProductPricingAdapter, ProductPricingDirector } from '@unchainedshop/core-products';
5
3
 
4
+ export const roundToNext = (value, precision) => Math.ceil(value / precision) * precision;
5
+ // TODO: Move to types
6
6
  // String is referencing to a currency
7
7
  interface PriceRoundSettings {
8
8
  configurations: Record<string, number>;
@@ -10,7 +10,7 @@ interface PriceRoundSettings {
10
10
  skip: Array<string>;
11
11
  }
12
12
 
13
- const ProductPriceRoundSettings: PriceRoundSettings = {
13
+ export const ProductPriceRoundSettings: PriceRoundSettings = {
14
14
  configurations: {},
15
15
  defaultPrecision: 50,
16
16
  skip: [],
@@ -0,0 +1,141 @@
1
+ import { getTaxRate, isDeliveryAddressInSwitzerland, SwissTaxCategories } from './product-swiss-tax.js';
2
+
3
+ describe("ProductSwissTax", () => {
4
+ describe('SwissTaxCategories', () => {
5
+ it('DEFAULT rate', () => {
6
+ expect(SwissTaxCategories.DEFAULT.rate()).toBe(0.077);
7
+ });
8
+
9
+ it('REDUCED rate', () => {
10
+ expect(SwissTaxCategories.REDUCED.rate()).toBe(0.025);
11
+ });
12
+
13
+ it('SPECIAL rate', () => {
14
+ expect(SwissTaxCategories.SPECIAL.rate()).toBe(0.037);
15
+ });
16
+ });
17
+
18
+ describe('getTaxRate', () => {
19
+ it('default rate', () => {
20
+ const context: any = {
21
+ product: {
22
+ tags: []
23
+ },
24
+ provider: {
25
+ configuration: [],
26
+ },
27
+ };
28
+ expect(getTaxRate(context)).toBe(0.077);
29
+ });
30
+
31
+ it('reduced rate', () => {
32
+ const context: any = {
33
+ product: {
34
+ tags: ['swiss-tax-category:reduced']
35
+ },
36
+ provider: {
37
+ configuration: [
38
+ {
39
+ key: 'swiss-tax-category',
40
+ value: SwissTaxCategories.REDUCED.rate(),
41
+ },
42
+ ],
43
+ },
44
+ };
45
+ expect(getTaxRate(context)).toBe(0.025);
46
+ });
47
+
48
+ it('special rate', () => {
49
+ const context: any = {
50
+ product: {
51
+ tags: ['swiss-tax-category:special']
52
+ },
53
+ provider: {
54
+ configuration: [
55
+ {
56
+ key: 'swiss-tax-category',
57
+ value: SwissTaxCategories.SPECIAL.rate(),
58
+ },
59
+ ],
60
+ },
61
+ };
62
+ expect(getTaxRate(context)).toBe(0.037);
63
+ });
64
+
65
+ it('default rate', () => {
66
+ const context: any = {
67
+ product: {
68
+ tags: ['swiss-tax-category:default']
69
+ },
70
+ provider: {
71
+ configuration: [
72
+ {
73
+ key: 'swiss-tax-category',
74
+ value: SwissTaxCategories.DEFAULT.rate(),
75
+ },
76
+ ],
77
+ },
78
+ };
79
+ expect(getTaxRate(context)).toBe(0.077);
80
+ });
81
+
82
+ })
83
+
84
+ describe('isDeliveryAddressInSwitzerland', () => {
85
+ const context = {
86
+ modules: {
87
+ orders: {
88
+ deliveries: {
89
+ findDelivery: import.meta.jest.fn(async({orderDeliveryId}) =>{
90
+ if(orderDeliveryId === 'CH' || orderDeliveryId === 'LI' )
91
+ return {context: { address: { countryCode: 'CH'}}}
92
+ if(orderDeliveryId === null)
93
+ return {context: { address: null}}
94
+ return {context: { address: {countryCode: 'RT'}}}
95
+ })
96
+ }
97
+ }
98
+ },
99
+ order : {
100
+ deliveryId: 'CH',
101
+ billingAddress: {countryCode: 'LI'}
102
+ },
103
+ country: 'CH'
104
+ }
105
+ it('Should return true when passed country code CH', async () => {
106
+ expect(await isDeliveryAddressInSwitzerland({...context, country: 'ch', order: null})).toBe(true);
107
+ });
108
+
109
+ it('Should return true when passed country code LI', async () => {
110
+ expect(await isDeliveryAddressInSwitzerland({...context, country: 'LI', order: null})).toBe(true);
111
+ });
112
+
113
+ it('Should return false when country is neither CH nor LI and order is null', async () => {
114
+ expect(await isDeliveryAddressInSwitzerland({...context, country: 'ET', order: null})).toBe(false);
115
+ });
116
+
117
+ it('Order should take precedence over country parameter', async () => {
118
+ // not CH or LI order so returns false
119
+ expect(await isDeliveryAddressInSwitzerland({...context, country: 'CH', order : {
120
+ deliveryId: 'ET',
121
+ billingAddress: {countryCode: 'ET'}
122
+ }})).toBe(false);
123
+ });
124
+
125
+ it('If order is not from CH and LI but billingAddress is, it should take precedence over country ', async () => {
126
+ expect(await isDeliveryAddressInSwitzerland({...context, country: 'IT', order : {
127
+ deliveryId: null,
128
+ billingAddress: { countryCode: 'CH'}
129
+ }})).toBe(true);
130
+ });
131
+
132
+ it('Should return false if billingAddress is neither CH or LI and order does not have address', async () => {
133
+ expect(await isDeliveryAddressInSwitzerland({...context, country: null, order : {
134
+ deliveryId: null,
135
+ billingAddress: {countryCode: 'HH'}
136
+ }})).toBe(false);
137
+ });
138
+ });
139
+
140
+
141
+ })
@@ -24,7 +24,7 @@ export const SwissTaxCategories = {
24
24
  },
25
25
  };
26
26
 
27
- const getTaxRate = (context: ProductPricingAdapterContext) => {
27
+ export const getTaxRate = (context: ProductPricingAdapterContext) => {
28
28
  const { product } = context;
29
29
 
30
30
  if (product.tags?.includes(SwissTaxCategories.REDUCED.tag)) {
@@ -36,7 +36,7 @@ const getTaxRate = (context: ProductPricingAdapterContext) => {
36
36
  return SwissTaxCategories.DEFAULT.rate();
37
37
  };
38
38
 
39
- const isDeliveryAddressInSwitzerland = async ({ order, country, modules }) => {
39
+ export const isDeliveryAddressInSwitzerland = async ({ order, country, modules }) => {
40
40
  let countryCode = country?.toUpperCase().trim();
41
41
 
42
42
  if (order) {
@@ -53,7 +53,7 @@ const isDeliveryAddressInSwitzerland = async ({ order, country, modules }) => {
53
53
  return countryCode === 'CH' || countryCode === 'LI';
54
54
  };
55
55
 
56
- const ProductSwissTax: IProductPricingAdapter = {
56
+ export const ProductSwissTax: IProductPricingAdapter = {
57
57
  ...ProductPricingAdapter,
58
58
 
59
59
  key: 'shop.unchained.pricing.product-swiss-tax',
@@ -0,0 +1,24 @@
1
+ import { roundToNext } from "./product-price-round.js";
2
+
3
+ describe('roundToNext', () => {
4
+ it('rounds to the next multiple of precision correctly when the value is positive', () => {
5
+ expect(roundToNext(5, 2)).toBe(6);
6
+ expect(roundToNext(10, 5)).toBe(10);
7
+ expect(roundToNext(13, 5)).toBe(15);
8
+ });
9
+
10
+ it('rounds to the next multiple of precision correctly when the value is negative', () => {
11
+ expect(roundToNext(-5, 2)).toBe(-4);
12
+ expect(roundToNext(-10, 5)).toBe(-10);
13
+ expect(roundToNext(-13, 5)).toBe(-10);
14
+ });
15
+
16
+ it('returns 0 when the value is 0', () => {
17
+ expect(roundToNext(0, 5)).toBe(0);
18
+ });
19
+
20
+ it('returns the same value when the precision is 1', () => {
21
+ expect(roundToNext(5, 1)).toBe(5);
22
+ });
23
+
24
+ });
@@ -9,7 +9,7 @@ import nodemailer from 'nodemailer';
9
9
 
10
10
  const logger = createLogger('unchained:plugins:worker:email');
11
11
 
12
- const checkEmailInterceptionEnabled = () => {
12
+ export const checkEmailInterceptionEnabled = () => {
13
13
  return process.env.NODE_ENV !== 'production' && !process.env.UNCHAINED_DISABLE_EMAIL_INTERCEPTION;
14
14
  };
15
15
 
@@ -0,0 +1,106 @@
1
+ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
2
+ import { createLogger } from '@unchainedshop/logger';
3
+ import { IWorkerAdapter } from '@unchainedshop/types/worker.js';
4
+ import webPush from 'web-push';
5
+
6
+ const { PUSH_NOTIFICATION_PUBLIC_KEY, PUSH_NOTIFICATION_PRIVATE_KEY } = process.env;
7
+
8
+ const logger = createLogger('unchained:plugins:worker:push-notification');
9
+
10
+ type NotificationOptions = {
11
+ vapidDetails: {
12
+ subject: string;
13
+ publicKey: string;
14
+ privateKey: string;
15
+ };
16
+ TTL: number;
17
+ urgency?: 'very-low' | 'low' | 'normal' | 'high';
18
+ topic?: string;
19
+ };
20
+
21
+ const PushNotificationWorkerPlugin: IWorkerAdapter<
22
+ {
23
+ subscription: any;
24
+ payload: string;
25
+ subject: string;
26
+ urgency?: 'very-low' | 'low' | 'normal' | 'high';
27
+ topic?: string;
28
+ },
29
+ any
30
+ > = {
31
+ ...WorkerAdapter,
32
+
33
+ key: 'shop.unchained.worker-plugin.push-notification',
34
+ label: 'Push Notification',
35
+
36
+ version: '1.0',
37
+
38
+ type: 'PUSH',
39
+
40
+ doWork: async ({ subscription, subject, payload, urgency = null, topic = null }) => {
41
+ logger.debug(`${PushNotificationWorkerPlugin.key} -> doWork: Push -> ${subject}`);
42
+ if (!PUSH_NOTIFICATION_PUBLIC_KEY)
43
+ return {
44
+ success: false,
45
+ error: {
46
+ name: 'VAPID_PUBLIC_KEY_REQUIRED',
47
+ message: 'vapidPublicKey key required to send push notifications',
48
+ },
49
+ };
50
+ if (!PUSH_NOTIFICATION_PRIVATE_KEY)
51
+ return {
52
+ success: false,
53
+ error: {
54
+ name: 'VAPID_PRIVATE_KEY_REQUIRED',
55
+ message: 'vapidPrivateKey key are required to send push notifications',
56
+ },
57
+ };
58
+
59
+ if (!subscription) {
60
+ return {
61
+ success: false,
62
+ error: {
63
+ name: 'USER_SUBSCRIPTION_OBJECT_REQUIRED',
64
+ message: 'PUSH service subscription required',
65
+ },
66
+ };
67
+ }
68
+
69
+ const options: NotificationOptions = {
70
+ vapidDetails: {
71
+ subject,
72
+ publicKey: PUSH_NOTIFICATION_PUBLIC_KEY,
73
+ privateKey: PUSH_NOTIFICATION_PRIVATE_KEY,
74
+ },
75
+ TTL: 60,
76
+ };
77
+
78
+ if (urgency) {
79
+ options.urgency = urgency;
80
+ }
81
+
82
+ if (topic) {
83
+ options.topic = topic;
84
+ }
85
+
86
+ try {
87
+ await webPush.sendNotification(subscription, payload, options);
88
+
89
+ return {
90
+ success: true,
91
+ error: null,
92
+ };
93
+ } catch (err) {
94
+ return {
95
+ success: false,
96
+ error: {
97
+ name: err.name,
98
+ message: err.message,
99
+ stack: err.stack,
100
+ },
101
+ };
102
+ }
103
+ },
104
+ };
105
+
106
+ WorkerDirector.registerAdapter(PushNotificationWorkerPlugin);
package/tsconfig.json CHANGED
@@ -1,35 +1,16 @@
1
1
  {
2
+ "extends": "../shared/base.tsconfig.json",
2
3
  "compilerOptions": {
3
- "allowJs": true,
4
- "allowSyntheticDefaultImports": true,
5
- "declaration": true,
6
- "esModuleInterop": true,
7
- "experimentalDecorators": true,
8
- "forceConsistentCasingInFileNames": true,
9
4
  "rootDir": "src",
10
- "lib": [
11
- "esnext"
12
- ],
13
- "module": "Node16",
14
- "moduleResolution": "Node16",
15
- "noImplicitReturns": true,
16
- "noUnusedLocals": false,
17
5
  "outDir": "lib",
18
- "preserveWatchOutput": true,
19
- "skipLibCheck": true,
20
- "sourceMap": true,
21
- "target": "ES2022",
22
- "types": [
23
- "node",
24
- "jest"
25
- ],
26
6
  "paths": {
27
7
  "postfinancecheckout": [
28
8
  "./src/payment/postfinance-checkout/silence-postfinancecheckout.d.ts"
29
9
  ],
30
10
  }
31
11
  },
12
+ "exclude": ["**/*.test.ts", "**/*.test.js", "tests"],
32
13
  "include": [
33
- "src"
14
+ "./src"
34
15
  ]
35
16
  }
@@ -1,9 +0,0 @@
1
- import 'abort-controller/polyfill.js';
2
- declare class Cache {
3
- cache: any;
4
- constructor(ttlSeconds?: number);
5
- get(key: any, currentFuntion: any): any;
6
- del(keys: any): void;
7
- flushAll(): void;
8
- }
9
- export default Cache;