@unchainedshop/plugins 4.8.19 → 4.8.21
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/datatrans-v2/adapter.js +12 -9
- package/lib/presets/countries/eu.d.ts +2 -0
- package/lib/presets/countries/eu.js +2 -0
- package/lib/presets/countries/uk.d.ts +2 -0
- package/lib/presets/countries/uk.js +2 -0
- package/lib/presets/countries/us.d.ts +2 -0
- package/lib/presets/countries/us.js +2 -0
- package/lib/pricing/delivery-eu-tax.d.ts +2 -0
- package/lib/pricing/delivery-eu-tax.js +55 -0
- package/lib/pricing/delivery-swiss-tax.js +7 -26
- package/lib/pricing/delivery-uk-tax.d.ts +2 -0
- package/lib/pricing/delivery-uk-tax.js +43 -0
- package/lib/pricing/delivery-us-sales-tax.d.ts +2 -0
- package/lib/pricing/delivery-us-sales-tax.js +58 -0
- package/lib/pricing/product-eu-tax.d.ts +2 -0
- package/lib/pricing/product-eu-tax.js +56 -0
- package/lib/pricing/product-swiss-tax.js +7 -26
- package/lib/pricing/product-uk-tax.d.ts +2 -0
- package/lib/pricing/product-uk-tax.js +57 -0
- package/lib/pricing/product-us-sales-tax.d.ts +2 -0
- package/lib/pricing/product-us-sales-tax.js +52 -0
- package/lib/pricing/tax/applyTaxRateToTaxableRows.d.ts +23 -0
- package/lib/pricing/tax/applyTaxRateToTaxableRows.js +30 -0
- package/lib/pricing/tax/ch-tax-rates.json +24 -0
- package/lib/pricing/tax/ch.js +6 -30
- package/lib/pricing/tax/eraRates.d.ts +10 -0
- package/lib/pricing/tax/eraRates.js +15 -0
- package/lib/pricing/tax/eu-tax-rates.json +797 -0
- package/lib/pricing/tax/eu.d.ts +12 -0
- package/lib/pricing/tax/eu.js +38 -0
- package/lib/pricing/tax/uk-tax-rates.json +18 -0
- package/lib/pricing/tax/uk.d.ts +10 -0
- package/lib/pricing/tax/uk.js +23 -0
- package/lib/pricing/tax/us-tax-rates.json +334 -0
- package/lib/pricing/tax/us.d.ts +11 -0
- package/lib/pricing/tax/us.js +20 -0
- package/lib/pricing/utils/isDeliveryAddressInCountry.d.ts +1 -1
- package/lib/pricing/utils/isDeliveryAddressInCountry.js +3 -8
- package/lib/pricing/utils/resolveDeliveryLocation.d.ts +10 -0
- package/lib/pricing/utils/resolveDeliveryLocation.js +9 -0
- package/lib/quotations/manual/adapter.js +6 -1
- package/lib/worker/gc-guests/adapter.d.ts +9 -0
- package/lib/worker/gc-guests/adapter.js +56 -0
- package/lib/worker/gc-guests/index.d.ts +4 -0
- package/lib/worker/gc-guests/index.js +13 -0
- package/lib/worker/invalidate-carts/adapter.d.ts +9 -0
- package/lib/worker/invalidate-carts/adapter.js +52 -0
- package/lib/worker/invalidate-carts/index.d.ts +4 -0
- package/lib/worker/invalidate-carts/index.js +13 -0
- package/lib/worker/zombie-killer/adapter.d.ts +2 -0
- package/lib/worker/zombie-killer/adapter.js +26 -3
- package/lib/worker/zombie-killer/index.js +4 -1
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@ import { createLogger } from '@unchainedshop/logger';
|
|
|
2
2
|
import createDatatransAPI from "./api/index.js";
|
|
3
3
|
import parseRegistrationData from "./parseRegistrationData.js";
|
|
4
4
|
import roundedAmountFromOrder from "./roundedAmountFromOrder.js";
|
|
5
|
+
import build3DSCardholder, { withCardCardholder, withSecureFieldsCardholder, } from "./build3DSCardholder.js";
|
|
5
6
|
import { PaymentAdapter, PaymentError, PaymentPricingRowCategory, PaymentPricingSheet, OrderPricingSheet, } from '@unchainedshop/core';
|
|
6
7
|
const logger = createLogger('unchained:datatrans');
|
|
7
8
|
const { DATATRANS_SECRET, DATATRANS_SIGN_KEY, DATATRANS_API_ENDPOINT = 'https://api.sandbox.datatrans.com', DATATRANS_MERCHANT_ID, } = process.env;
|
|
@@ -221,22 +222,18 @@ export const Datatrans = {
|
|
|
221
222
|
const price = order
|
|
222
223
|
? roundedAmountFromOrder(order)
|
|
223
224
|
: {};
|
|
225
|
+
const cardholder = build3DSCardholder(order);
|
|
224
226
|
if (useSecureFields) {
|
|
225
227
|
const result = await api().secureFields({
|
|
226
|
-
...arbitraryFields,
|
|
228
|
+
...withSecureFieldsCardholder(arbitraryFields, cardholder),
|
|
227
229
|
currency: price.currencyCode || 'CHF',
|
|
228
|
-
refno,
|
|
229
|
-
refno2,
|
|
230
|
-
customer: {
|
|
231
|
-
id: userId,
|
|
232
|
-
},
|
|
233
230
|
amount: price.amount,
|
|
234
231
|
});
|
|
235
232
|
throwIfResponseError(result);
|
|
236
233
|
return JSON.stringify(result);
|
|
237
234
|
}
|
|
238
235
|
const result = await api().init({
|
|
239
|
-
...arbitraryFields,
|
|
236
|
+
...withCardCardholder(arbitraryFields, cardholder),
|
|
240
237
|
currency: price.currencyCode || 'CHF',
|
|
241
238
|
refno,
|
|
242
239
|
refno2,
|
|
@@ -349,11 +346,17 @@ export const Datatrans = {
|
|
|
349
346
|
if (status === 'authenticated') {
|
|
350
347
|
if (authorizeAuthenticated) {
|
|
351
348
|
checkIfTransactionAmountValid(transactionId, transaction);
|
|
349
|
+
const { orderPayment, order } = context;
|
|
350
|
+
const userId = order?.userId || context?.userId;
|
|
351
|
+
const refno = orderPayment
|
|
352
|
+
? Buffer.from(orderPayment._id, 'hex').toString('base64')
|
|
353
|
+
: transaction.refno;
|
|
354
|
+
const refno2 = userId || transaction.refno2;
|
|
352
355
|
await authorizeAuth({
|
|
353
356
|
...(authorizeAuthenticated || {}),
|
|
354
357
|
transactionId,
|
|
355
|
-
refno
|
|
356
|
-
refno2
|
|
358
|
+
refno,
|
|
359
|
+
refno2,
|
|
357
360
|
});
|
|
358
361
|
status = 'authorized';
|
|
359
362
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DeliveryPricingRowCategory, DeliveryPricingAdapter, DeliveryPricingDirector, } from '@unchainedshop/core';
|
|
2
|
+
import { isEuMemberCountry, resolveEuTaxCategoryFromDeliveryProvider, resolveEuTaxRate, } from "./tax/eu.js";
|
|
3
|
+
import resolveDeliveryLocation from "./utils/resolveDeliveryLocation.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
5
|
+
export const DeliveryEuTax = {
|
|
6
|
+
...DeliveryPricingAdapter,
|
|
7
|
+
key: 'shop.unchained.pricing.delivery-eu-tax',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
label: 'Apply EU VAT on Delivery Fees',
|
|
10
|
+
orderIndex: 80,
|
|
11
|
+
isActivatedFor: (context) => {
|
|
12
|
+
if (!context.order)
|
|
13
|
+
return false;
|
|
14
|
+
if (!context.orderDelivery)
|
|
15
|
+
return false;
|
|
16
|
+
const { countryCode } = resolveDeliveryLocation({
|
|
17
|
+
order: context.order,
|
|
18
|
+
orderDelivery: context.orderDelivery,
|
|
19
|
+
countryCode: context.countryCode,
|
|
20
|
+
});
|
|
21
|
+
return isEuMemberCountry(countryCode);
|
|
22
|
+
},
|
|
23
|
+
actions: (params) => {
|
|
24
|
+
const pricingAdapter = DeliveryPricingAdapter.actions(params);
|
|
25
|
+
const { context } = params;
|
|
26
|
+
return {
|
|
27
|
+
...pricingAdapter,
|
|
28
|
+
calculate: async () => {
|
|
29
|
+
const { countryCode } = resolveDeliveryLocation({
|
|
30
|
+
order: context.order,
|
|
31
|
+
orderDelivery: context.orderDelivery,
|
|
32
|
+
countryCode: context.countryCode,
|
|
33
|
+
});
|
|
34
|
+
const category = resolveEuTaxCategoryFromDeliveryProvider(context.provider);
|
|
35
|
+
const taxRate = resolveEuTaxRate({
|
|
36
|
+
countryCode,
|
|
37
|
+
category,
|
|
38
|
+
referenceDate: context.order?.ordered,
|
|
39
|
+
});
|
|
40
|
+
if (taxRate === null)
|
|
41
|
+
return pricingAdapter.calculate();
|
|
42
|
+
DeliveryPricingAdapter.log(`DeliveryEuTax -> Tax Multiplicator: ${taxRate}`);
|
|
43
|
+
applyTaxRateToTaxableRows({
|
|
44
|
+
calculationSheet: params.calculationSheet,
|
|
45
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
46
|
+
taxRate,
|
|
47
|
+
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
48
|
+
adapterKey: DeliveryEuTax.key,
|
|
49
|
+
});
|
|
50
|
+
return pricingAdapter.calculate();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
DeliveryPricingDirector.registerAdapter(DeliveryEuTax);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DeliveryPricingRowCategory, DeliveryPricingAdapter, DeliveryPricingDirector, } from '@unchainedshop/core';
|
|
2
2
|
import { resolveTaxCategoryFromDeliveryProvider, SwissTaxCategories } from "./tax/ch.js";
|
|
3
3
|
import isDeliveryAddressInCountry from "./utils/isDeliveryAddressInCountry.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
4
5
|
export const DeliverySwissTax = {
|
|
5
6
|
...DeliveryPricingAdapter,
|
|
6
7
|
key: 'shop.unchained.pricing.delivery-swiss-tax',
|
|
@@ -27,32 +28,12 @@ export const DeliverySwissTax = {
|
|
|
27
28
|
const taxCategory = resolveTaxCategoryFromDeliveryProvider(context.provider) || SwissTaxCategories.DEFAULT;
|
|
28
29
|
const taxRate = taxCategory.rate(context.order?.ordered);
|
|
29
30
|
DeliveryPricingAdapter.log(`DeliverySwissTax -> Tax Multiplicator: ${taxRate}`);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
isTaxable: false,
|
|
37
|
-
isNetPrice: false,
|
|
38
|
-
meta: { adapter: DeliverySwissTax.key },
|
|
39
|
-
});
|
|
40
|
-
pricingAdapter.resultSheet().addTax({
|
|
41
|
-
amount: taxAmount,
|
|
42
|
-
rate: taxRate,
|
|
43
|
-
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
44
|
-
meta: { adapter: DeliverySwissTax.key },
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
const taxAmount = row.amount * taxRate;
|
|
49
|
-
pricingAdapter.resultSheet().addTax({
|
|
50
|
-
amount: taxAmount,
|
|
51
|
-
rate: taxRate,
|
|
52
|
-
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
53
|
-
meta: { adapter: DeliverySwissTax.key },
|
|
54
|
-
});
|
|
55
|
-
}
|
|
31
|
+
applyTaxRateToTaxableRows({
|
|
32
|
+
calculationSheet: params.calculationSheet,
|
|
33
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
34
|
+
taxRate,
|
|
35
|
+
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
36
|
+
adapterKey: DeliverySwissTax.key,
|
|
56
37
|
});
|
|
57
38
|
return pricingAdapter.calculate();
|
|
58
39
|
},
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DeliveryPricingRowCategory, DeliveryPricingAdapter, DeliveryPricingDirector, } from '@unchainedshop/core';
|
|
2
|
+
import { resolveUkTaxCategoryFromDeliveryProvider, UkTaxCategories, UK_VAT_COUNTRY_CODES, } from "./tax/uk.js";
|
|
3
|
+
import isDeliveryAddressInCountry from "./utils/isDeliveryAddressInCountry.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
5
|
+
export const DeliveryUkTax = {
|
|
6
|
+
...DeliveryPricingAdapter,
|
|
7
|
+
key: 'shop.unchained.pricing.delivery-uk-tax',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
label: 'Apply UK VAT on Delivery Fees',
|
|
10
|
+
orderIndex: 80,
|
|
11
|
+
isActivatedFor: (context) => {
|
|
12
|
+
if (!context.order)
|
|
13
|
+
return false;
|
|
14
|
+
if (!context.orderDelivery)
|
|
15
|
+
return false;
|
|
16
|
+
return isDeliveryAddressInCountry({
|
|
17
|
+
order: context.order,
|
|
18
|
+
orderDelivery: context.orderDelivery,
|
|
19
|
+
countryCode: context.countryCode,
|
|
20
|
+
}, UK_VAT_COUNTRY_CODES);
|
|
21
|
+
},
|
|
22
|
+
actions: (params) => {
|
|
23
|
+
const pricingAdapter = DeliveryPricingAdapter.actions(params);
|
|
24
|
+
const { context } = params;
|
|
25
|
+
return {
|
|
26
|
+
...pricingAdapter,
|
|
27
|
+
calculate: async () => {
|
|
28
|
+
const taxCategory = resolveUkTaxCategoryFromDeliveryProvider(context.provider) || UkTaxCategories.STANDARD;
|
|
29
|
+
const taxRate = taxCategory.rate(context.order?.ordered);
|
|
30
|
+
DeliveryPricingAdapter.log(`DeliveryUkTax -> Tax Multiplicator: ${taxRate}`);
|
|
31
|
+
applyTaxRateToTaxableRows({
|
|
32
|
+
calculationSheet: params.calculationSheet,
|
|
33
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
34
|
+
taxRate,
|
|
35
|
+
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
36
|
+
adapterKey: DeliveryUkTax.key,
|
|
37
|
+
});
|
|
38
|
+
return pricingAdapter.calculate();
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
DeliveryPricingDirector.registerAdapter(DeliveryUkTax);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DeliveryPricingRowCategory, DeliveryPricingAdapter, DeliveryPricingDirector, } from '@unchainedshop/core';
|
|
2
|
+
import { US_COUNTRY_CODE, isDeliveryExemptFromUsSalesTax, resolveUsSalesTaxRate } from "./tax/us.js";
|
|
3
|
+
import resolveDeliveryLocation from "./utils/resolveDeliveryLocation.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
5
|
+
export const DeliveryUsSalesTax = {
|
|
6
|
+
...DeliveryPricingAdapter,
|
|
7
|
+
key: 'shop.unchained.pricing.delivery-us-sales-tax',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
label: 'Apply US State Sales Tax on Delivery Fees',
|
|
10
|
+
orderIndex: 80,
|
|
11
|
+
isActivatedFor: (context) => {
|
|
12
|
+
if (!context.order)
|
|
13
|
+
return false;
|
|
14
|
+
if (!context.orderDelivery)
|
|
15
|
+
return false;
|
|
16
|
+
const { countryCode } = resolveDeliveryLocation({
|
|
17
|
+
order: context.order,
|
|
18
|
+
orderDelivery: context.orderDelivery,
|
|
19
|
+
countryCode: context.countryCode,
|
|
20
|
+
});
|
|
21
|
+
return countryCode === US_COUNTRY_CODE;
|
|
22
|
+
},
|
|
23
|
+
actions: (params) => {
|
|
24
|
+
const pricingAdapter = DeliveryPricingAdapter.actions(params);
|
|
25
|
+
const { context } = params;
|
|
26
|
+
return {
|
|
27
|
+
...pricingAdapter,
|
|
28
|
+
calculate: async () => {
|
|
29
|
+
if (isDeliveryExemptFromUsSalesTax(context.provider)) {
|
|
30
|
+
return pricingAdapter.calculate();
|
|
31
|
+
}
|
|
32
|
+
const { regionCode } = resolveDeliveryLocation({
|
|
33
|
+
order: context.order,
|
|
34
|
+
orderDelivery: context.orderDelivery,
|
|
35
|
+
countryCode: context.countryCode,
|
|
36
|
+
});
|
|
37
|
+
const taxRate = resolveUsSalesTaxRate({
|
|
38
|
+
regionCode,
|
|
39
|
+
referenceDate: context.order?.ordered,
|
|
40
|
+
});
|
|
41
|
+
if (taxRate === null) {
|
|
42
|
+
DeliveryPricingAdapter.log(`DeliveryUsSalesTax -> unknown state '${regionCode}', no sales tax applied`);
|
|
43
|
+
return pricingAdapter.calculate();
|
|
44
|
+
}
|
|
45
|
+
DeliveryPricingAdapter.log(`DeliveryUsSalesTax -> Tax Multiplicator: ${taxRate}`);
|
|
46
|
+
applyTaxRateToTaxableRows({
|
|
47
|
+
calculationSheet: params.calculationSheet,
|
|
48
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
49
|
+
taxRate,
|
|
50
|
+
baseCategory: DeliveryPricingRowCategory.Delivery,
|
|
51
|
+
adapterKey: DeliveryUsSalesTax.key,
|
|
52
|
+
});
|
|
53
|
+
return pricingAdapter.calculate();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
DeliveryPricingDirector.registerAdapter(DeliveryUsSalesTax);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ProductPricingDirector, ProductPricingAdapter, ProductPricingRowCategory, } from '@unchainedshop/core';
|
|
2
|
+
import { resolveEuTaxCategoryFromDeliveryProvider, resolveEuTaxCategoryFromProduct, resolveEuTaxRate, } from "./tax/eu.js";
|
|
3
|
+
import resolveDeliveryLocation from "./utils/resolveDeliveryLocation.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
5
|
+
export const ProductEuTax = {
|
|
6
|
+
...ProductPricingAdapter,
|
|
7
|
+
key: 'shop.unchained.pricing.product-eu-tax',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
label: 'Apply EU VAT on Product',
|
|
10
|
+
orderIndex: 80,
|
|
11
|
+
isActivatedFor: () => {
|
|
12
|
+
return true;
|
|
13
|
+
},
|
|
14
|
+
actions: (params) => {
|
|
15
|
+
const pricingAdapter = ProductPricingAdapter.actions(params);
|
|
16
|
+
const { context } = params;
|
|
17
|
+
return {
|
|
18
|
+
...pricingAdapter,
|
|
19
|
+
calculate: async () => {
|
|
20
|
+
const orderDelivery = context.order?.deliveryId
|
|
21
|
+
? await context.modules.orders.deliveries.findDelivery({
|
|
22
|
+
orderDeliveryId: context.order?.deliveryId,
|
|
23
|
+
})
|
|
24
|
+
: null;
|
|
25
|
+
const { countryCode } = resolveDeliveryLocation({ ...context, orderDelivery });
|
|
26
|
+
let category = resolveEuTaxCategoryFromProduct(context.product);
|
|
27
|
+
if (!category) {
|
|
28
|
+
const provider = orderDelivery?.deliveryProviderId
|
|
29
|
+
? await context.modules.delivery.findProvider({
|
|
30
|
+
deliveryProviderId: orderDelivery?.deliveryProviderId,
|
|
31
|
+
})
|
|
32
|
+
: null;
|
|
33
|
+
if (provider)
|
|
34
|
+
category = resolveEuTaxCategoryFromDeliveryProvider(provider);
|
|
35
|
+
}
|
|
36
|
+
const taxRate = resolveEuTaxRate({
|
|
37
|
+
countryCode,
|
|
38
|
+
category,
|
|
39
|
+
referenceDate: context.order?.ordered,
|
|
40
|
+
});
|
|
41
|
+
if (taxRate === null)
|
|
42
|
+
return pricingAdapter.calculate();
|
|
43
|
+
ProductPricingAdapter.log(`ProductEuTax -> Tax Multiplicator: ${taxRate}`);
|
|
44
|
+
applyTaxRateToTaxableRows({
|
|
45
|
+
calculationSheet: params.calculationSheet,
|
|
46
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
47
|
+
taxRate,
|
|
48
|
+
baseCategory: ProductPricingRowCategory.Item,
|
|
49
|
+
adapterKey: ProductEuTax.key,
|
|
50
|
+
});
|
|
51
|
+
return pricingAdapter.calculate();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
ProductPricingDirector.registerAdapter(ProductEuTax);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ProductPricingDirector, ProductPricingAdapter, ProductPricingRowCategory, } from '@unchainedshop/core';
|
|
2
2
|
import { resolveTaxCategoryFromDeliveryProvider, resolveTaxCategoryFromProduct, SwissTaxCategories, } from "./tax/ch.js";
|
|
3
3
|
import isDeliveryAddressInCountry from "./utils/isDeliveryAddressInCountry.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
4
5
|
export const ProductSwissTax = {
|
|
5
6
|
...ProductPricingAdapter,
|
|
6
7
|
key: 'shop.unchained.pricing.product-swiss-tax',
|
|
@@ -41,32 +42,12 @@ export const ProductSwissTax = {
|
|
|
41
42
|
taxCategory = SwissTaxCategories.DEFAULT;
|
|
42
43
|
const taxRate = taxCategory.rate(context.order?.ordered);
|
|
43
44
|
ProductPricingAdapter.log(`ProductSwissTax -> Tax Multiplicator: ${taxRate}`);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
isTaxable: false,
|
|
51
|
-
isNetPrice: false,
|
|
52
|
-
meta: { adapter: ProductSwissTax.key },
|
|
53
|
-
});
|
|
54
|
-
pricingAdapter.resultSheet().addTax({
|
|
55
|
-
amount: taxAmount,
|
|
56
|
-
rate: taxRate,
|
|
57
|
-
baseCategory: ProductPricingRowCategory.Item,
|
|
58
|
-
meta: { adapter: ProductSwissTax.key },
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
const taxAmount = row.amount * taxRate;
|
|
63
|
-
pricingAdapter.resultSheet().addTax({
|
|
64
|
-
amount: taxAmount,
|
|
65
|
-
rate: taxRate,
|
|
66
|
-
baseCategory: ProductPricingRowCategory.Item,
|
|
67
|
-
meta: { adapter: ProductSwissTax.key },
|
|
68
|
-
});
|
|
69
|
-
}
|
|
45
|
+
applyTaxRateToTaxableRows({
|
|
46
|
+
calculationSheet: params.calculationSheet,
|
|
47
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
48
|
+
taxRate,
|
|
49
|
+
baseCategory: ProductPricingRowCategory.Item,
|
|
50
|
+
adapterKey: ProductSwissTax.key,
|
|
70
51
|
});
|
|
71
52
|
return pricingAdapter.calculate();
|
|
72
53
|
},
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ProductPricingDirector, ProductPricingAdapter, ProductPricingRowCategory, } from '@unchainedshop/core';
|
|
2
|
+
import { resolveUkTaxCategoryFromDeliveryProvider, resolveUkTaxCategoryFromProduct, UkTaxCategories, UK_VAT_COUNTRY_CODES, } from "./tax/uk.js";
|
|
3
|
+
import isDeliveryAddressInCountry from "./utils/isDeliveryAddressInCountry.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
5
|
+
export const ProductUkTax = {
|
|
6
|
+
...ProductPricingAdapter,
|
|
7
|
+
key: 'shop.unchained.pricing.product-uk-tax',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
label: 'Apply UK VAT on Product',
|
|
10
|
+
orderIndex: 80,
|
|
11
|
+
isActivatedFor: () => {
|
|
12
|
+
return true;
|
|
13
|
+
},
|
|
14
|
+
actions: (params) => {
|
|
15
|
+
const pricingAdapter = ProductPricingAdapter.actions(params);
|
|
16
|
+
const { context } = params;
|
|
17
|
+
return {
|
|
18
|
+
...pricingAdapter,
|
|
19
|
+
calculate: async () => {
|
|
20
|
+
const orderDelivery = context.order?.deliveryId
|
|
21
|
+
? await context.modules.orders.deliveries.findDelivery({
|
|
22
|
+
orderDeliveryId: context.order?.deliveryId,
|
|
23
|
+
})
|
|
24
|
+
: null;
|
|
25
|
+
if (!isDeliveryAddressInCountry({
|
|
26
|
+
...context,
|
|
27
|
+
orderDelivery,
|
|
28
|
+
}, UK_VAT_COUNTRY_CODES)) {
|
|
29
|
+
return pricingAdapter.calculate();
|
|
30
|
+
}
|
|
31
|
+
let taxCategory = resolveUkTaxCategoryFromProduct(context.product);
|
|
32
|
+
if (!taxCategory) {
|
|
33
|
+
const provider = orderDelivery?.deliveryProviderId
|
|
34
|
+
? await context.modules.delivery.findProvider({
|
|
35
|
+
deliveryProviderId: orderDelivery?.deliveryProviderId,
|
|
36
|
+
})
|
|
37
|
+
: null;
|
|
38
|
+
if (provider)
|
|
39
|
+
taxCategory = resolveUkTaxCategoryFromDeliveryProvider(provider);
|
|
40
|
+
}
|
|
41
|
+
if (!taxCategory)
|
|
42
|
+
taxCategory = UkTaxCategories.STANDARD;
|
|
43
|
+
const taxRate = taxCategory.rate(context.order?.ordered);
|
|
44
|
+
ProductPricingAdapter.log(`ProductUkTax -> Tax Multiplicator: ${taxRate}`);
|
|
45
|
+
applyTaxRateToTaxableRows({
|
|
46
|
+
calculationSheet: params.calculationSheet,
|
|
47
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
48
|
+
taxRate,
|
|
49
|
+
baseCategory: ProductPricingRowCategory.Item,
|
|
50
|
+
adapterKey: ProductUkTax.key,
|
|
51
|
+
});
|
|
52
|
+
return pricingAdapter.calculate();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
ProductPricingDirector.registerAdapter(ProductUkTax);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ProductPricingDirector, ProductPricingAdapter, ProductPricingRowCategory, } from '@unchainedshop/core';
|
|
2
|
+
import { US_COUNTRY_CODE, isProductExemptFromUsSalesTax, resolveUsSalesTaxRate } from "./tax/us.js";
|
|
3
|
+
import resolveDeliveryLocation from "./utils/resolveDeliveryLocation.js";
|
|
4
|
+
import { applyTaxRateToTaxableRows } from "./tax/applyTaxRateToTaxableRows.js";
|
|
5
|
+
export const ProductUsSalesTax = {
|
|
6
|
+
...ProductPricingAdapter,
|
|
7
|
+
key: 'shop.unchained.pricing.product-us-sales-tax',
|
|
8
|
+
version: '1.0.0',
|
|
9
|
+
label: 'Apply US State Sales Tax on Product',
|
|
10
|
+
orderIndex: 80,
|
|
11
|
+
isActivatedFor: () => {
|
|
12
|
+
return true;
|
|
13
|
+
},
|
|
14
|
+
actions: (params) => {
|
|
15
|
+
const pricingAdapter = ProductPricingAdapter.actions(params);
|
|
16
|
+
const { context } = params;
|
|
17
|
+
return {
|
|
18
|
+
...pricingAdapter,
|
|
19
|
+
calculate: async () => {
|
|
20
|
+
if (isProductExemptFromUsSalesTax(context.product)) {
|
|
21
|
+
return pricingAdapter.calculate();
|
|
22
|
+
}
|
|
23
|
+
const orderDelivery = context.order?.deliveryId
|
|
24
|
+
? await context.modules.orders.deliveries.findDelivery({
|
|
25
|
+
orderDeliveryId: context.order?.deliveryId,
|
|
26
|
+
})
|
|
27
|
+
: null;
|
|
28
|
+
const { countryCode, regionCode } = resolveDeliveryLocation({ ...context, orderDelivery });
|
|
29
|
+
if (countryCode !== US_COUNTRY_CODE)
|
|
30
|
+
return pricingAdapter.calculate();
|
|
31
|
+
const taxRate = resolveUsSalesTaxRate({
|
|
32
|
+
regionCode,
|
|
33
|
+
referenceDate: context.order?.ordered,
|
|
34
|
+
});
|
|
35
|
+
if (taxRate === null) {
|
|
36
|
+
ProductPricingAdapter.log(`ProductUsSalesTax -> unknown state '${regionCode}', no sales tax applied`);
|
|
37
|
+
return pricingAdapter.calculate();
|
|
38
|
+
}
|
|
39
|
+
ProductPricingAdapter.log(`ProductUsSalesTax -> Tax Multiplicator: ${taxRate}`);
|
|
40
|
+
applyTaxRateToTaxableRows({
|
|
41
|
+
calculationSheet: params.calculationSheet,
|
|
42
|
+
resultSheet: pricingAdapter.resultSheet(),
|
|
43
|
+
taxRate,
|
|
44
|
+
baseCategory: ProductPricingRowCategory.Item,
|
|
45
|
+
adapterKey: ProductUsSalesTax.key,
|
|
46
|
+
});
|
|
47
|
+
return pricingAdapter.calculate();
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
ProductPricingDirector.registerAdapter(ProductUsSalesTax);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PricingCalculation } from '@unchainedshop/utils';
|
|
2
|
+
export interface TaxableCalculation extends PricingCalculation {
|
|
3
|
+
isTaxable?: boolean;
|
|
4
|
+
isNetPrice?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface TaxResultSheet {
|
|
7
|
+
calculation: PricingCalculation[];
|
|
8
|
+
addTax: (row: {
|
|
9
|
+
amount: number;
|
|
10
|
+
rate: number;
|
|
11
|
+
baseCategory: string;
|
|
12
|
+
meta?: unknown;
|
|
13
|
+
}) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare const applyTaxRateToTaxableRows: <Calculation extends TaxableCalculation>({ calculationSheet, resultSheet, taxRate, baseCategory, adapterKey, }: {
|
|
16
|
+
calculationSheet: {
|
|
17
|
+
filterBy: (filter?: Partial<Calculation>) => Calculation[];
|
|
18
|
+
};
|
|
19
|
+
resultSheet: TaxResultSheet;
|
|
20
|
+
taxRate: number;
|
|
21
|
+
baseCategory: string;
|
|
22
|
+
adapterKey: string;
|
|
23
|
+
}) => void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const applyTaxRateToTaxableRows = ({ calculationSheet, resultSheet, taxRate, baseCategory, adapterKey, }) => {
|
|
2
|
+
calculationSheet
|
|
3
|
+
.filterBy({ isTaxable: true })
|
|
4
|
+
.forEach(({ isNetPrice, ...row }) => {
|
|
5
|
+
if (!isNetPrice) {
|
|
6
|
+
const taxAmount = row.amount - row.amount / (1 + taxRate);
|
|
7
|
+
resultSheet.calculation.push({
|
|
8
|
+
...row,
|
|
9
|
+
amount: -taxAmount,
|
|
10
|
+
isTaxable: false,
|
|
11
|
+
isNetPrice: false,
|
|
12
|
+
meta: { adapter: adapterKey },
|
|
13
|
+
});
|
|
14
|
+
resultSheet.addTax({
|
|
15
|
+
amount: taxAmount,
|
|
16
|
+
rate: taxRate,
|
|
17
|
+
baseCategory,
|
|
18
|
+
meta: { adapter: adapterKey },
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
resultSheet.addTax({
|
|
23
|
+
amount: row.amount * taxRate,
|
|
24
|
+
rate: taxRate,
|
|
25
|
+
baseCategory,
|
|
26
|
+
meta: { adapter: adapterKey },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "https://www.estv.admin.ch/estv/en/home/value-added-tax/vat-rates-switzerland.html",
|
|
3
|
+
"verifiedAt": "2026-08-20",
|
|
4
|
+
"timezone": "+01:00",
|
|
5
|
+
"categories": {
|
|
6
|
+
"default": [
|
|
7
|
+
{ "validFrom": "2001-01-01", "rate": 0.076 },
|
|
8
|
+
{ "validFrom": "2011-01-01", "rate": 0.08 },
|
|
9
|
+
{ "validFrom": "2018-01-01", "rate": 0.077 },
|
|
10
|
+
{ "validFrom": "2024-01-01", "rate": 0.081 }
|
|
11
|
+
],
|
|
12
|
+
"reduced": [
|
|
13
|
+
{ "validFrom": "2001-01-01", "rate": 0.024 },
|
|
14
|
+
{ "validFrom": "2011-01-01", "rate": 0.025 },
|
|
15
|
+
{ "validFrom": "2024-01-01", "rate": 0.026 }
|
|
16
|
+
],
|
|
17
|
+
"special": [
|
|
18
|
+
{ "validFrom": "2001-01-01", "rate": 0.036 },
|
|
19
|
+
{ "validFrom": "2011-01-01", "rate": 0.038 },
|
|
20
|
+
{ "validFrom": "2018-01-01", "rate": 0.037 },
|
|
21
|
+
{ "validFrom": "2024-01-01", "rate": 0.038 }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
package/lib/pricing/tax/ch.js
CHANGED
|
@@ -1,33 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return 0.077;
|
|
8
|
-
}
|
|
9
|
-
return 0.081;
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
REDUCED: {
|
|
13
|
-
value: 'reduced',
|
|
14
|
-
rate: (referenceDate = new Date()) => {
|
|
15
|
-
if (referenceDate.getTime() < startOf2024.getTime()) {
|
|
16
|
-
return 0.025;
|
|
17
|
-
}
|
|
18
|
-
return 0.026;
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
SPECIAL: {
|
|
22
|
-
value: 'special',
|
|
23
|
-
rate: (referenceDate = new Date()) => {
|
|
24
|
-
if (referenceDate.getTime() < startOf2024.getTime()) {
|
|
25
|
-
return 0.037;
|
|
26
|
-
}
|
|
27
|
-
return 0.038;
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
};
|
|
1
|
+
import swissTaxRates from './ch-tax-rates.json' with { type: 'json' };
|
|
2
|
+
import { compileEraRates, rateForDate } from "./eraRates.js";
|
|
3
|
+
export const SwissTaxCategories = Object.fromEntries(Object.entries(swissTaxRates.categories).map(([value, eras]) => [
|
|
4
|
+
value.toUpperCase(),
|
|
5
|
+
{ value, rate: rateForDate(compileEraRates(eras, swissTaxRates.timezone)) },
|
|
6
|
+
]));
|
|
31
7
|
export const resolveTaxCategoryFromDeliveryProvider = (provider) => {
|
|
32
8
|
const taxCategoryFromProvider = provider?.configuration
|
|
33
9
|
?.find(({ key }) => {
|