@unchainedshop/plugins 2.2.2 → 2.3.0
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/lib/payment/cryptopay/plugin.js +3 -2
- package/lib/payment/cryptopay/plugin.js.map +1 -1
- package/lib/payment/postfinance-checkout/types.js +2 -2
- package/lib/payment/postfinance-checkout/types.js.map +1 -1
- package/lib/pricing/order-discount.d.ts +2 -10
- package/lib/pricing/order-discount.js +12 -47
- package/lib/pricing/order-discount.js.map +1 -1
- package/lib/pricing/order-items-discount.js +3 -37
- package/lib/pricing/order-items-discount.js.map +1 -1
- package/lib/pricing/product-discount.d.ts +1 -2
- package/lib/pricing/product-discount.js +2 -5
- package/lib/pricing/product-discount.js.map +1 -1
- package/lib/pricing/product-price-round.d.ts +0 -1
- package/lib/pricing/product-price-round.js +2 -2
- package/lib/pricing/product-price-round.js.map +1 -1
- package/lib/warehousing/google-sheets.js +2 -2
- package/lib/warehousing/google-sheets.js.map +1 -1
- package/lib/worker/email.js +2 -2
- package/lib/worker/email.js.map +1 -1
- package/lib/worker/enrollment-order-generator.js +1 -1
- package/lib/worker/enrollment-order-generator.js.map +1 -1
- package/lib/worker/sms.js +7 -0
- package/lib/worker/sms.js.map +1 -1
- package/lib/worker/update-coinbase-rates.js +0 -4
- package/lib/worker/update-coinbase-rates.js.map +1 -1
- package/lib/worker/update-ecb-rates.js +0 -3
- package/lib/worker/update-ecb-rates.js.map +1 -1
- package/lib/worker/update-token-ownership.js +1 -5
- package/lib/worker/update-token-ownership.js.map +1 -1
- package/package.json +29 -26
- package/src/payment/cryptopay/plugin.ts +5 -3
- package/src/pricing/configureProductPriceRounding.test.ts +0 -2
- package/src/pricing/free-delivery.test.ts +1 -1
- package/src/pricing/order-delivery.test.ts +1 -1
- package/src/pricing/order-discount.test.ts +4 -203
- package/src/pricing/order-discount.ts +21 -65
- package/src/pricing/order-items-discount.ts +4 -45
- package/src/pricing/product-discount.ts +3 -6
- package/src/pricing/product-price-round.ts +2 -4
- package/src/warehousing/google-sheets.ts +2 -2
- package/src/worker/email.ts +2 -2
- package/src/worker/enrollment-order-generator.ts +1 -1
- package/src/worker/sms.ts +8 -0
- package/src/worker/update-coinbase-rates.ts +0 -4
- package/src/worker/update-ecb-rates.ts +0 -3
- package/src/worker/update-token-ownership.ts +1 -5
- package/tsconfig.json +3 -7
- package/src/pricing/apply-rate.test.ts +0 -92
- package/src/pricing/getTaxRate.test.ts +0 -69
- package/src/pricing/roundToNext.test.ts +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/plugins",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"main": "lib/plugins-index.js",
|
|
5
5
|
"types": "lib/plugins-index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -72,31 +72,36 @@
|
|
|
72
72
|
},
|
|
73
73
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
74
74
|
"optionalDependencies": {
|
|
75
|
+
"bip32": "^4.0.0",
|
|
75
76
|
"escape-string-regexp": "^5.0.0",
|
|
76
|
-
"
|
|
77
|
+
"ethers": "^6.6.2",
|
|
78
|
+
"express": "^4.18.2",
|
|
79
|
+
"mongodb": "^4.0.0",
|
|
80
|
+
"stripe": "^12.11.0",
|
|
81
|
+
"tiny-secp256k1": "^2.2.3"
|
|
77
82
|
},
|
|
78
83
|
"devDependencies": {
|
|
79
|
-
"@types/node": "^18.16.
|
|
80
|
-
"@unchainedshop/api": "^2.
|
|
81
|
-
"@unchainedshop/core-delivery": "^2.
|
|
82
|
-
"@unchainedshop/core-enrollments": "^2.
|
|
83
|
-
"@unchainedshop/core-filters": "^2.
|
|
84
|
-
"@unchainedshop/core-messaging": "^2.
|
|
85
|
-
"@unchainedshop/core-orders": "^2.
|
|
86
|
-
"@unchainedshop/core-payment": "^2.
|
|
87
|
-
"@unchainedshop/core-products": "^2.
|
|
88
|
-
"@unchainedshop/core-quotations": "^2.
|
|
89
|
-
"@unchainedshop/core-warehousing": "^2.
|
|
90
|
-
"@unchainedshop/core-worker": "^2.
|
|
91
|
-
"@unchainedshop/events": "^2.
|
|
92
|
-
"@unchainedshop/file-upload": "^2.
|
|
93
|
-
"@unchainedshop/logger": "^2.
|
|
94
|
-
"@unchainedshop/types": "^2.
|
|
95
|
-
"@unchainedshop/utils": "^2.
|
|
84
|
+
"@types/node": "^18.16.19",
|
|
85
|
+
"@unchainedshop/api": "^2.3.0",
|
|
86
|
+
"@unchainedshop/core-delivery": "^2.3.0",
|
|
87
|
+
"@unchainedshop/core-enrollments": "^2.3.0",
|
|
88
|
+
"@unchainedshop/core-filters": "^2.3.0",
|
|
89
|
+
"@unchainedshop/core-messaging": "^2.3.0",
|
|
90
|
+
"@unchainedshop/core-orders": "^2.3.0",
|
|
91
|
+
"@unchainedshop/core-payment": "^2.3.0",
|
|
92
|
+
"@unchainedshop/core-products": "^2.3.0",
|
|
93
|
+
"@unchainedshop/core-quotations": "^2.3.0",
|
|
94
|
+
"@unchainedshop/core-warehousing": "^2.3.0",
|
|
95
|
+
"@unchainedshop/core-worker": "^2.3.0",
|
|
96
|
+
"@unchainedshop/events": "^2.3.0",
|
|
97
|
+
"@unchainedshop/file-upload": "^2.3.0",
|
|
98
|
+
"@unchainedshop/logger": "^2.3.0",
|
|
99
|
+
"@unchainedshop/types": "^2.3.0",
|
|
100
|
+
"@unchainedshop/utils": "^2.3.0",
|
|
96
101
|
"bluebird": "^3.7.2",
|
|
97
102
|
"event-iterator": "^2.0.0",
|
|
98
103
|
"express": "^4.18.2",
|
|
99
|
-
"jest": "^29.
|
|
104
|
+
"jest": "^29.6.0",
|
|
100
105
|
"JSONStream": "^1.3.5",
|
|
101
106
|
"minio": "^7.1.1",
|
|
102
107
|
"mongodb": "^4.16.0",
|
|
@@ -105,11 +110,9 @@
|
|
|
105
110
|
"open": "^9.1.0",
|
|
106
111
|
"redis": "^4.6.7",
|
|
107
112
|
"request": "^2.88.2",
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"xml-js": "^1.6.11",
|
|
113
|
-
"xml2js": "0.4.23"
|
|
113
|
+
"ts-jest": "^29.1.1",
|
|
114
|
+
"typescript": "^5.1.6",
|
|
115
|
+
"web-push": "^3.6.3",
|
|
116
|
+
"xml-js": "^1.6.11"
|
|
114
117
|
}
|
|
115
118
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IPaymentAdapter } from '@unchainedshop/types/payments.js';
|
|
2
2
|
import { PaymentAdapter, PaymentDirector, PaymentError } from '@unchainedshop/core-payment';
|
|
3
|
-
import { ethers } from 'ethers';
|
|
3
|
+
import { HDNodeVoidWallet, HDNodeWallet, ethers } from 'ethers';
|
|
4
4
|
import { BIP32Factory } from 'bip32';
|
|
5
5
|
import * as ecc from 'tiny-secp256k1';
|
|
6
6
|
import * as bitcoin from 'bitcoinjs-lib';
|
|
@@ -34,7 +34,7 @@ type CryptopayAddress = {
|
|
|
34
34
|
const getDerivationPath = (currency: CryptopayCurrencies, index: number): string => {
|
|
35
35
|
const address = `${(parseInt(CRYPTOPAY_DERIVATION_START, 10) || 0) + index}`;
|
|
36
36
|
if (currency === CryptopayCurrencies.ETH) {
|
|
37
|
-
const pathComponents = ethers.
|
|
37
|
+
const pathComponents = ethers.defaultPath.split('/');
|
|
38
38
|
pathComponents[pathComponents.length - 1] = address;
|
|
39
39
|
return pathComponents.join('/');
|
|
40
40
|
}
|
|
@@ -170,7 +170,9 @@ const Cryptopay: IPaymentAdapter = {
|
|
|
170
170
|
if (CRYPTOPAY_ETH_XPUB) {
|
|
171
171
|
// we neuter for security reasons, it's still quite complicated for most ethereum clients to show an appropriate
|
|
172
172
|
// xpub, that's why
|
|
173
|
-
const
|
|
173
|
+
const hdWallet = ethers.HDNodeWallet.fromExtendedKey(CRYPTOPAY_ETH_XPUB);
|
|
174
|
+
const hardenedMaster = (hdWallet as HDNodeWallet)?.neuter() || (hdWallet as HDNodeVoidWallet);
|
|
175
|
+
|
|
174
176
|
const ethDerivationNumber = await modules.cryptopay.getNextDerivationNumber(
|
|
175
177
|
CryptopayCurrencies.ETH,
|
|
176
178
|
);
|
|
@@ -5,7 +5,7 @@ import { DeliveryFreePrice } from "./free-delivery.js";
|
|
|
5
5
|
describe("DeliveryFreePrice", () => {
|
|
6
6
|
|
|
7
7
|
it('should return true', () => {
|
|
8
|
-
expect(DeliveryFreePrice.isActivatedFor(undefined)).toBeTruthy();
|
|
8
|
+
expect(DeliveryFreePrice.isActivatedFor(undefined as any)).toBeTruthy();
|
|
9
9
|
|
|
10
10
|
});
|
|
11
11
|
|
|
@@ -1,206 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OrderDiscount } from "./order-discount.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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]);
|
|
3
|
+
describe("OrderDelivery ", () => {
|
|
4
|
+
it('isActivatedFor', () => {
|
|
5
|
+
expect(OrderDiscount.isActivatedFor({} as any)).toBeTruthy();
|
|
97
6
|
});
|
|
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
7
|
})
|
|
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,55 +1,8 @@
|
|
|
1
1
|
import { IOrderPricingAdapter, OrderPricingRowCategory } from '@unchainedshop/types/orders.pricing.js';
|
|
2
2
|
import { OrderPricingDirector, OrderPricingAdapter } from '@unchainedshop/core-orders';
|
|
3
|
+
import { calculation as calcUtils } from '@unchainedshop/utils';
|
|
3
4
|
|
|
4
|
-
export const
|
|
5
|
-
if (total === 0 || !pricing) {
|
|
6
|
-
return {
|
|
7
|
-
ratio: 1,
|
|
8
|
-
taxDivisor: 1,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
const tax = pricing.taxSum();
|
|
12
|
-
const gross = pricing.gross();
|
|
13
|
-
if (gross - tax === 0) {
|
|
14
|
-
return {
|
|
15
|
-
ratio: 0,
|
|
16
|
-
taxDivisor: 0,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
return {
|
|
20
|
-
ratio: gross / total,
|
|
21
|
-
taxDivisor: gross / (gross - tax),
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const resolveAmountAndTax = ({ ratio, taxDivisor }, amount) => {
|
|
26
|
-
const shareAmount = Number.isFinite(ratio) ? amount * ratio : 0;
|
|
27
|
-
const shareTaxAmount =
|
|
28
|
-
Number.isFinite(taxDivisor) && taxDivisor !== 0 ? shareAmount - shareAmount / taxDivisor : 0;
|
|
29
|
-
return [shareAmount, shareTaxAmount];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// TODO: move to separate file because its used in multiple places
|
|
33
|
-
|
|
34
|
-
export const applyDiscountToMultipleShares = (shares, amount) => {
|
|
35
|
-
return shares.reduce(
|
|
36
|
-
([currentDiscountAmount, currentTaxAmount], share) => {
|
|
37
|
-
const [shareAmount, shareTaxAmount] = resolveAmountAndTax(share, amount);
|
|
38
|
-
return [currentDiscountAmount + shareAmount, currentTaxAmount + shareTaxAmount];
|
|
39
|
-
},
|
|
40
|
-
[0, 0],
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const calculateAmountToSplit = (configuration, amount) => {
|
|
45
|
-
const deductionAmount = configuration.rate ? amount * configuration.rate : configuration.fixedRate;
|
|
46
|
-
|
|
47
|
-
const leftInDiscount = Math.max(0, deductionAmount - (configuration.alreadyDeductedForDiscount || 0));
|
|
48
|
-
const leftToDeduct = Math.min(configuration.amountLeft, leftInDiscount);
|
|
49
|
-
return Math.max(0, leftToDeduct);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const OrderDiscount: IOrderPricingAdapter = {
|
|
5
|
+
export const OrderDiscount: IOrderPricingAdapter = {
|
|
53
6
|
...OrderPricingAdapter,
|
|
54
7
|
|
|
55
8
|
key: 'shop.unchained.pricing.order-discount',
|
|
@@ -84,18 +37,18 @@ const OrderDiscount: IOrderPricingAdapter = {
|
|
|
84
37
|
});
|
|
85
38
|
|
|
86
39
|
const itemShares = orderPositions.map((orderPosition) =>
|
|
87
|
-
resolveRatioAndTaxDivisorForPricingSheet(
|
|
40
|
+
calcUtils.resolveRatioAndTaxDivisorForPricingSheet(
|
|
88
41
|
modules.orders.positions.pricingSheet(orderPosition, order.currency, params.context),
|
|
89
42
|
totalAmountOfItems,
|
|
90
43
|
),
|
|
91
44
|
);
|
|
92
45
|
|
|
93
|
-
const deliveryShare = resolveRatioAndTaxDivisorForPricingSheet(
|
|
46
|
+
const deliveryShare = calcUtils.resolveRatioAndTaxDivisorForPricingSheet(
|
|
94
47
|
orderDelivery &&
|
|
95
48
|
modules.orders.deliveries.pricingSheet(orderDelivery, order.currency, params.context),
|
|
96
49
|
totalAmountOfPaymentAndDelivery,
|
|
97
50
|
);
|
|
98
|
-
const paymentShare = resolveRatioAndTaxDivisorForPricingSheet(
|
|
51
|
+
const paymentShare = calcUtils.resolveRatioAndTaxDivisorForPricingSheet(
|
|
99
52
|
orderPayment &&
|
|
100
53
|
modules.orders.payments.pricingSheet(orderPayment, order.currency, params.context),
|
|
101
54
|
totalAmountOfPaymentAndDelivery,
|
|
@@ -105,28 +58,31 @@ const OrderDiscount: IOrderPricingAdapter = {
|
|
|
105
58
|
|
|
106
59
|
params.discounts.forEach(({ configuration, discountId }) => {
|
|
107
60
|
// First, we deduce the discount from the items
|
|
108
|
-
let
|
|
109
|
-
|
|
61
|
+
let alreadyDeducted = 0;
|
|
62
|
+
|
|
63
|
+
const leftInDiscountToSplit = calcUtils.calculateAmountToSplit(
|
|
64
|
+
{ ...configuration, alreadyDeducted },
|
|
65
|
+
totalAmountOfItems,
|
|
66
|
+
);
|
|
67
|
+
const [itemsDiscountAmount, itemsTaxAmount] = calcUtils.applyDiscountToMultipleShares(
|
|
110
68
|
itemShares,
|
|
111
|
-
|
|
112
|
-
{ ...configuration, amountLeft, alreadyDeductedForDiscount },
|
|
113
|
-
totalAmountOfItems,
|
|
114
|
-
),
|
|
69
|
+
Math.max(0, Math.min(amountLeft, leftInDiscountToSplit)),
|
|
115
70
|
);
|
|
116
71
|
amountLeft -= itemsDiscountAmount;
|
|
117
|
-
|
|
72
|
+
alreadyDeducted += itemsDiscountAmount;
|
|
118
73
|
|
|
119
74
|
// After the items, we deduct the remaining discount from payment & delivery fees
|
|
75
|
+
const leftInFeesToSplit = calcUtils.calculateAmountToSplit(
|
|
76
|
+
{ ...configuration, alreadyDeducted },
|
|
77
|
+
totalAmountOfPaymentAndDelivery,
|
|
78
|
+
);
|
|
120
79
|
const [deliveryAndPaymentDiscountAmount, deliveryAndPaymentTaxAmount] =
|
|
121
|
-
applyDiscountToMultipleShares(
|
|
80
|
+
calcUtils.applyDiscountToMultipleShares(
|
|
122
81
|
[deliveryShare, paymentShare],
|
|
123
|
-
|
|
124
|
-
{ ...configuration, amountLeft, alreadyDeductedForDiscount },
|
|
125
|
-
totalAmountOfPaymentAndDelivery,
|
|
126
|
-
),
|
|
82
|
+
Math.max(0, Math.min(amountLeft, leftInFeesToSplit)),
|
|
127
83
|
);
|
|
128
84
|
amountLeft -= deliveryAndPaymentDiscountAmount;
|
|
129
|
-
|
|
85
|
+
alreadyDeducted += itemsDiscountAmount;
|
|
130
86
|
|
|
131
87
|
const discountAmount = itemsDiscountAmount + deliveryAndPaymentDiscountAmount;
|
|
132
88
|
const taxAmount = itemsTaxAmount + deliveryAndPaymentTaxAmount;
|
|
@@ -1,47 +1,6 @@
|
|
|
1
1
|
import { IOrderPricingAdapter, OrderPricingRowCategory } from '@unchainedshop/types/orders.pricing.js';
|
|
2
2
|
import { OrderPricingDirector, OrderPricingAdapter } from '@unchainedshop/core-orders';
|
|
3
|
-
|
|
4
|
-
const resolveRatioAndTaxDivisorForPricingSheet = (pricing, total) => {
|
|
5
|
-
if (total === 0 || !pricing) {
|
|
6
|
-
return {
|
|
7
|
-
ratio: 1,
|
|
8
|
-
taxDivisor: 1,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
const tax = pricing.taxSum();
|
|
12
|
-
const gross = pricing.gross();
|
|
13
|
-
return {
|
|
14
|
-
ratio: gross / total,
|
|
15
|
-
taxDivisor: gross / (gross - tax),
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// TODO: move into separate file and share multiple places
|
|
20
|
-
const resolveAmountAndTax = ({ ratio, taxDivisor }, amount) => {
|
|
21
|
-
const shareAmount = Number.isFinite(ratio) ? amount * ratio : 0;
|
|
22
|
-
const shareTaxAmount = Number.isFinite(taxDivisor) ? shareAmount - shareAmount / taxDivisor : 0;
|
|
23
|
-
return [shareAmount, shareTaxAmount];
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// TODO: move into separate file and share multiple places
|
|
27
|
-
const applyDiscountToMultipleShares = (shares, amount) => {
|
|
28
|
-
return shares.reduce(
|
|
29
|
-
([currentDiscountAmount, currentTaxAmount], share) => {
|
|
30
|
-
const [shareAmount, shareTaxAmount] = resolveAmountAndTax(share, amount);
|
|
31
|
-
return [currentDiscountAmount + shareAmount, currentTaxAmount + shareTaxAmount];
|
|
32
|
-
},
|
|
33
|
-
[0, 0],
|
|
34
|
-
);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// TODO: move into separate file and share multiple places
|
|
38
|
-
const calculateAmountToSplit = (configuration, amount) => {
|
|
39
|
-
const deductionAmount = configuration.rate
|
|
40
|
-
? amount * configuration.rate
|
|
41
|
-
: Math.min(configuration.fixedRate, amount);
|
|
42
|
-
|
|
43
|
-
return Math.max(0, deductionAmount - (configuration.alreadyDeducted || 0));
|
|
44
|
-
};
|
|
3
|
+
import { calculation as calcUtils } from '@unchainedshop/utils';
|
|
45
4
|
|
|
46
5
|
const OrderItemsDiscount: IOrderPricingAdapter = {
|
|
47
6
|
...OrderPricingAdapter,
|
|
@@ -72,7 +31,7 @@ const OrderItemsDiscount: IOrderPricingAdapter = {
|
|
|
72
31
|
});
|
|
73
32
|
|
|
74
33
|
const itemShares = orderPositions.map((orderPosition) =>
|
|
75
|
-
resolveRatioAndTaxDivisorForPricingSheet(
|
|
34
|
+
calcUtils.resolveRatioAndTaxDivisorForPricingSheet(
|
|
76
35
|
modules.orders.positions.pricingSheet(orderPosition, order.currency, params.context),
|
|
77
36
|
totalAmountOfItems,
|
|
78
37
|
),
|
|
@@ -82,9 +41,9 @@ const OrderItemsDiscount: IOrderPricingAdapter = {
|
|
|
82
41
|
|
|
83
42
|
params.discounts.forEach(({ configuration, discountId }) => {
|
|
84
43
|
// First, we deduce the discount from the items
|
|
85
|
-
const [itemsDiscountAmount, itemsTaxAmount] = applyDiscountToMultipleShares(
|
|
44
|
+
const [itemsDiscountAmount, itemsTaxAmount] = calcUtils.applyDiscountToMultipleShares(
|
|
86
45
|
itemShares,
|
|
87
|
-
calculateAmountToSplit({ ...configuration, alreadyDeducted }, totalAmountOfItems),
|
|
46
|
+
calcUtils.calculateAmountToSplit({ ...configuration, alreadyDeducted }, totalAmountOfItems),
|
|
88
47
|
);
|
|
89
48
|
alreadyDeducted = +itemsDiscountAmount;
|
|
90
49
|
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import type { Discount
|
|
1
|
+
import type { Discount } from '@unchainedshop/types/discount.js';
|
|
2
2
|
import {
|
|
3
3
|
IProductPricingAdapter,
|
|
4
4
|
ProductPricingRowCategory,
|
|
5
5
|
} from '@unchainedshop/types/products.pricing.js';
|
|
6
6
|
import { ProductPricingDirector, ProductPricingAdapter } from '@unchainedshop/core-products';
|
|
7
|
+
import { calculation as calcUtils } from '@unchainedshop/utils';
|
|
7
8
|
|
|
8
|
-
export const applyRate = (configuration: DiscountConfiguration, amount) => {
|
|
9
|
-
const { rate, fixedRate } = configuration;
|
|
10
|
-
return rate ? amount * rate : Math.min(fixedRate || 0, amount);
|
|
11
|
-
};
|
|
12
9
|
const ProductDiscount: IProductPricingAdapter = {
|
|
13
10
|
...ProductPricingAdapter,
|
|
14
11
|
|
|
@@ -27,7 +24,7 @@ const ProductDiscount: IProductPricingAdapter = {
|
|
|
27
24
|
const addDiscount = (discount: Discount, total: number, isTaxable: boolean) => {
|
|
28
25
|
const { configuration, discountId } = discount;
|
|
29
26
|
const { isNetPrice = false, ...meta } = configuration;
|
|
30
|
-
const amount = applyRate(configuration, total);
|
|
27
|
+
const amount = calcUtils.applyRate(configuration, total);
|
|
31
28
|
|
|
32
29
|
pricingAdapter.resultSheet().addDiscount({
|
|
33
30
|
amount: amount * -1,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { IProductPricingAdapter } from '@unchainedshop/types/products.pricing.js';
|
|
2
2
|
import { ProductPricingAdapter, ProductPricingDirector } from '@unchainedshop/core-products';
|
|
3
|
+
import { calculation as calcUtils } from '@unchainedshop/utils';
|
|
3
4
|
|
|
4
|
-
export const roundToNext = (value, precision) => Math.ceil(value / precision) * precision;
|
|
5
|
-
// TODO: Move to types
|
|
6
|
-
// String is referencing to a currency
|
|
7
5
|
interface PriceRoundSettings {
|
|
8
6
|
configurations: Record<string, number>;
|
|
9
7
|
defaultPrecision: number;
|
|
@@ -61,7 +59,7 @@ export const ProductPriceRound: IProductPricingAdapter & {
|
|
|
61
59
|
const [productPrice] = calculation;
|
|
62
60
|
pricingAdapter.resultSheet().resetCalculation(params.calculationSheet);
|
|
63
61
|
pricingAdapter.resultSheet().addItem({
|
|
64
|
-
amount: roundToNext(productPrice.amount, roundPrecision) * quantity,
|
|
62
|
+
amount: calcUtils.roundToNext(productPrice.amount, roundPrecision) * quantity,
|
|
65
63
|
isTaxable: productPrice.isTaxable,
|
|
66
64
|
isNetPrice: productPrice.isNetPrice,
|
|
67
65
|
meta: { adapter: ProductPriceRound.key },
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import Sheets from 'node-sheets';
|
|
7
7
|
import { log, LogLevel } from '@unchainedshop/logger';
|
|
8
8
|
import 'abort-controller/polyfill.js';
|
|
9
|
-
import
|
|
9
|
+
import { LRUCache } from 'lru-cache';
|
|
10
10
|
import { IWarehousingAdapter } from '@unchainedshop/types/warehousing.js';
|
|
11
11
|
|
|
12
12
|
const { NODE_ENV, GOOGLE_SHEETS_ID, GOOGLE_SHEETS_PRIVATE_KEY_DATA } = process.env;
|
|
@@ -48,7 +48,7 @@ const updateGoogleCache = async (cache) => {
|
|
|
48
48
|
return null;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
const googleCache = new
|
|
51
|
+
const googleCache = new LRUCache({
|
|
52
52
|
max: 500,
|
|
53
53
|
ttl, // 1 second in dev
|
|
54
54
|
allowStale: true,
|
package/src/worker/email.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core-worker';
|
|
|
2
2
|
import { createLogger } from '@unchainedshop/logger';
|
|
3
3
|
import { IWorkerAdapter } from '@unchainedshop/types/worker.js';
|
|
4
4
|
import fs from 'fs';
|
|
5
|
-
import { join } from 'path';
|
|
5
|
+
import { join, isAbsolute } from 'path';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import open from 'open';
|
|
8
8
|
import nodemailer from 'nodemailer';
|
|
@@ -15,7 +15,7 @@ export const checkEmailInterceptionEnabled = () => {
|
|
|
15
15
|
|
|
16
16
|
const buildLink = ({ filename, content, href, contentType, encoding, path }) => {
|
|
17
17
|
if (path) {
|
|
18
|
-
return `<a href="${join(process.cwd(), path)}">${filename}</a>`;
|
|
18
|
+
return `<a href="${isAbsolute(path) ? path : join(process.cwd(), path)}">${filename}</a>`;
|
|
19
19
|
}
|
|
20
20
|
if (href) {
|
|
21
21
|
return `<a href="${href}">${filename}</a>`;
|
|
@@ -154,7 +154,7 @@ export const configureGenerateOrderAutoscheduling = () => {
|
|
|
154
154
|
if (enrollmentsSettings.autoSchedulingSchedule) {
|
|
155
155
|
WorkerDirector.configureAutoscheduling(GenerateOrderWorker, {
|
|
156
156
|
schedule: enrollmentsSettings.autoSchedulingSchedule,
|
|
157
|
-
|
|
157
|
+
retries: 5,
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
};
|
package/src/worker/sms.ts
CHANGED
|
@@ -7,6 +7,14 @@ const { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_SMS_FROM } = process.env;
|
|
|
7
7
|
|
|
8
8
|
const logger = createLogger('unchained:plugins:worker:sms');
|
|
9
9
|
|
|
10
|
+
/* Potential: no need for twilio npm
|
|
11
|
+
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Messages.json" \
|
|
12
|
+
--data-urlencode "From=+15017122661" \
|
|
13
|
+
--data-urlencode "Body=Hi there" \
|
|
14
|
+
--data-urlencode "To=+15558675310" \
|
|
15
|
+
-u $TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN
|
|
16
|
+
*/
|
|
17
|
+
|
|
10
18
|
const SmsWorkerPlugin: IWorkerAdapter<
|
|
11
19
|
{
|
|
12
20
|
from?: string;
|