@unchainedshop/plugins 4.5.0 → 4.6.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/README.md CHANGED
@@ -123,6 +123,32 @@ const platform = await startPlatform({
123
123
  });
124
124
  ```
125
125
 
126
+ ## Security
127
+
128
+ ### Payment Plugin Security
129
+
130
+ All payment plugins implement secure tokenization patterns for PCI DSS SAQ-A eligibility:
131
+
132
+ | Plugin | Security Method |
133
+ |--------|-----------------|
134
+ | Stripe | PaymentIntent/SetupIntent tokenization |
135
+ | Datatrans | Secure Fields with HMAC-SHA-256 signatures |
136
+ | Saferpay | Redirect with SHA-256 transaction signatures |
137
+ | PayPal | Order ID references |
138
+ | Braintree | Client SDK tokenization |
139
+ | Cryptopay | BIP-32 HD wallet address derivation |
140
+
141
+ **Signature Algorithms:**
142
+ - HMAC-SHA-256: Datatrans, Payrexx, GridFS file uploads
143
+ - HMAC-SHA-512: PostFinance Checkout
144
+ - SHA-256: Saferpay
145
+
146
+ ### FIPS 140-3 Compatibility
147
+
148
+ All cryptographic operations use FIPS-approved algorithms. When deployed on FIPS-enabled Node.js (e.g., Chainguard node-fips), plugins operate in FIPS-compliant mode.
149
+
150
+ See [SECURITY.md](../../SECURITY.md) for complete security documentation.
151
+
126
152
  ## Notes
127
153
 
128
154
  ### Postfinance Checkout Plugin
@@ -8,7 +8,6 @@ import { filesSettings } from '@unchainedshop/core-files';
8
8
  import {} from '@unchainedshop/core';
9
9
  import {} from "./index.js";
10
10
  const { GRIDFS_PUT_SERVER_PATH = '/gridfs' } = process.env;
11
- const { ROOT_URL } = process.env;
12
11
  const bufferToStream = (buffer) => {
13
12
  const stream = new Readable();
14
13
  stream.push(buffer);
@@ -32,7 +31,7 @@ export const GridFSAdapter = {
32
31
  const expiryDate = resolveExpirationDate();
33
32
  const hashedFilename = await buildHashedFilename(directoryName, fileName, expiryDate);
34
33
  const signature = await sign(directoryName, hashedFilename, expiryDate.getTime());
35
- const putURL = new URL(`${GRIDFS_PUT_SERVER_PATH}/${directoryName}/${encodeURIComponent(fileName)}?e=${expiryDate.getTime()}&s=${signature}`, ROOT_URL).href;
34
+ const putURL = new URL(`${GRIDFS_PUT_SERVER_PATH}/${directoryName}/${encodeURIComponent(fileName)}?e=${expiryDate.getTime()}&s=${signature}`, process.env.ROOT_URL).href;
36
35
  const url = `${GRIDFS_PUT_SERVER_PATH}/${directoryName}/${hashedFilename}`;
37
36
  return {
38
37
  _id: hashedFilename,
@@ -7,6 +7,7 @@ import {} from "./module.js";
7
7
  import {} from '@unchainedshop/api';
8
8
  import { createLogger } from '@unchainedshop/logger';
9
9
  import { getFileAdapter } from '@unchainedshop/core-files';
10
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
10
11
  const { ROOT_URL, GRIDFS_PUT_SERVER_PATH = '/gridfs' } = process.env;
11
12
  const logger = createLogger('unchained:gridfs');
12
13
  const gridfsHandler = async (req, res) => {
@@ -27,7 +28,8 @@ const gridfsHandler = async (req, res) => {
27
28
  const { s: signature, e: expiryTimestamp } = req.query;
28
29
  const expiryDate = new Date(parseInt(expiryTimestamp, 10));
29
30
  const fileId = await buildHashedFilename(directoryName, fileName, expiryDate);
30
- if ((await sign(directoryName, fileId, expiryDate.getTime())) === signature) {
31
+ const expectedSignature = await sign(directoryName, fileId, expiryDate.getTime());
32
+ if (await timingSafeStringEqual(expectedSignature, signature)) {
31
33
  const file = await modules.files.findFile({ fileId });
32
34
  if (!file) {
33
35
  res.status(404).send('File not found');
@@ -67,7 +69,10 @@ const gridfsHandler = async (req, res) => {
67
69
  }
68
70
  const fileUploadAdapter = getFileAdapter();
69
71
  const signedUrl = await fileUploadAdapter.createDownloadURL(fileDocument, expiry);
70
- if (!signedUrl || new URL(signedUrl, 'file://').searchParams.get('s') !== signature) {
72
+ const expectedSignature = signedUrl ? new URL(signedUrl, 'file://').searchParams.get('s') : null;
73
+ if (!signedUrl ||
74
+ !expectedSignature ||
75
+ !(await timingSafeStringEqual(expectedSignature, signature))) {
71
76
  res.status(403).send('Access restricted: Invalid signature.');
72
77
  return;
73
78
  }
@@ -7,6 +7,7 @@ import {} from "./module.js";
7
7
  import {} from '@unchainedshop/api';
8
8
  import { createLogger } from '@unchainedshop/logger';
9
9
  import { getFileAdapter } from '@unchainedshop/core-files';
10
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
10
11
  const logger = createLogger('unchained:gridfs');
11
12
  const { GRIDFS_PUT_SERVER_PATH = '/gridfs' } = process.env;
12
13
  const gridfsHandler = async (req, reply) => {
@@ -24,7 +25,8 @@ const gridfsHandler = async (req, reply) => {
24
25
  const { s: signature, e: expiryTimestamp } = req.query;
25
26
  const expiryDate = new Date(parseInt(expiryTimestamp, 10));
26
27
  const fileId = await buildHashedFilename(directoryName, fileName, expiryDate);
27
- if ((await sign(directoryName, fileId, expiryDate.getTime())) === signature) {
28
+ const expectedSignature = await sign(directoryName, fileId, expiryDate.getTime());
29
+ if (await timingSafeStringEqual(expectedSignature, signature)) {
28
30
  const file = await modules.files.findFile({ fileId });
29
31
  if (!file) {
30
32
  reply.status(404);
@@ -89,7 +91,10 @@ const gridfsHandler = async (req, reply) => {
89
91
  }
90
92
  const fileUploadAdapter = getFileAdapter();
91
93
  const signedUrl = await fileUploadAdapter.createDownloadURL(fileDocument, expiry);
92
- if (!signedUrl || new URL(signedUrl, 'file://').searchParams.get('s') !== signature) {
94
+ const expectedSignature = signedUrl ? new URL(signedUrl, 'file://').searchParams.get('s') : null;
95
+ if (!signedUrl ||
96
+ !expectedSignature ||
97
+ !(await timingSafeStringEqual(expectedSignature, signature))) {
93
98
  reply.status(403);
94
99
  logger.error('Invalid signature', { fileName, expiry });
95
100
  return reply.send({
@@ -1,16 +1,21 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
2
3
  const logger = createLogger('unchained:minio');
3
4
  const { MINIO_WEBHOOK_AUTH_TOKEN } = process.env;
4
- const isAuthorized = ({ authorization = '' }) => {
5
+ const isAuthorized = async ({ authorization = '' }) => {
6
+ if (!MINIO_WEBHOOK_AUTH_TOKEN)
7
+ return false;
5
8
  const [type, token] = authorization.split(' ');
6
- return type === 'Bearer' && token === MINIO_WEBHOOK_AUTH_TOKEN;
9
+ if (type !== 'Bearer' || !token)
10
+ return false;
11
+ return timingSafeStringEqual(token, MINIO_WEBHOOK_AUTH_TOKEN);
7
12
  };
8
13
  const minioHandler = async (req, res) => {
9
14
  try {
10
15
  if (req.method === 'POST' && req.body) {
11
16
  const { headers } = req;
12
17
  const { Records = [], EventName } = req.body;
13
- if (EventName === 's3:ObjectCreated:Put' && isAuthorized(headers)) {
18
+ if (EventName === 's3:ObjectCreated:Put' && (await isAuthorized(headers))) {
14
19
  const [{ s3 }] = Records;
15
20
  const { object } = s3;
16
21
  const { size, contentType: type } = object;
@@ -1,18 +1,23 @@
1
1
  import {} from '@unchainedshop/api';
2
2
  import { createLogger } from '@unchainedshop/logger';
3
3
  import {} from 'fastify';
4
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
4
5
  const logger = createLogger('unchained:minio');
5
6
  const { MINIO_WEBHOOK_AUTH_TOKEN } = process.env;
6
- const isAuthorized = ({ authorization = '' }) => {
7
+ const isAuthorized = async ({ authorization = '' }) => {
8
+ if (!MINIO_WEBHOOK_AUTH_TOKEN)
9
+ return false;
7
10
  const [type, token] = authorization.split(' ');
8
- return type === 'Bearer' && token === MINIO_WEBHOOK_AUTH_TOKEN;
11
+ if (type !== 'Bearer' || !token)
12
+ return false;
13
+ return timingSafeStringEqual(token, MINIO_WEBHOOK_AUTH_TOKEN);
9
14
  };
10
15
  const minioHandler = async (req, res) => {
11
16
  try {
12
17
  if (req.body) {
13
18
  const { headers } = req;
14
19
  const { Records = [], EventName } = req.body;
15
- if (EventName === 's3:ObjectCreated:Put' && isAuthorized(headers)) {
20
+ if (EventName === 's3:ObjectCreated:Put' && (await isAuthorized(headers))) {
16
21
  const [{ s3 }] = Records;
17
22
  const { object } = s3;
18
23
  const { size, contentType: type } = object;
@@ -104,7 +104,7 @@ const AppleIAP = {
104
104
  orderId: order._id,
105
105
  });
106
106
  return {
107
- transactionIdentifier,
107
+ transactionId: transactionIdentifier,
108
108
  };
109
109
  },
110
110
  };
@@ -23,11 +23,7 @@ export const appleIAPHandler = async (req, res) => {
23
23
  const transactions = responseBody?.unified_receipt?.latest_receipt_info;
24
24
  const latestTransaction = transactions[0];
25
25
  if (responseBody.notification_type === AppleNotificationTypes.INITIAL_BUY) {
26
- const orderPayment = await modules.orders.payments.findOrderPaymentByContextData({
27
- context: {
28
- 'meta.transactionIdentifier': latestTransaction.transaction_id,
29
- },
30
- });
26
+ const orderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.transaction_id);
31
27
  if (!orderPayment)
32
28
  throw new Error('Could not find any matching order payment');
33
29
  const order = await services.orders.checkoutOrder(orderPayment.orderId, {
@@ -54,11 +50,7 @@ export const appleIAPHandler = async (req, res) => {
54
50
  });
55
51
  }
56
52
  else {
57
- const originalOrderPayment = await modules.orders.payments.findOrderPaymentByContextData({
58
- context: {
59
- 'meta.transactionIdentifier': latestTransaction.original_transaction_id,
60
- },
61
- });
53
+ const originalOrderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.original_transaction_id);
62
54
  if (!originalOrderPayment)
63
55
  throw new Error('Could not find any matching order payment');
64
56
  const originalOrder = await modules.orders.findOrder({
@@ -101,7 +93,7 @@ export const appleIAPHandler = async (req, res) => {
101
93
  return;
102
94
  }
103
95
  catch (e) {
104
- logger.warn(`Apple IAP Webhook: ${e.message}`, e);
96
+ logger.warn(`Apple IAP Webhook: ${e.name} - ${e.message}`);
105
97
  res.status(503).send({ name: e.name, code: e.code, message: e.message });
106
98
  return;
107
99
  }
@@ -24,11 +24,7 @@ export const appleIAPHandler = async (req, reply) => {
24
24
  const transactions = responseBody?.unified_receipt?.latest_receipt_info;
25
25
  const latestTransaction = transactions[0];
26
26
  if (responseBody.notification_type === AppleNotificationTypes.INITIAL_BUY) {
27
- const orderPayment = await modules.orders.payments.findOrderPaymentByContextData({
28
- context: {
29
- 'meta.transactionIdentifier': latestTransaction.transaction_id,
30
- },
31
- });
27
+ const orderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.transaction_id);
32
28
  if (!orderPayment)
33
29
  throw new Error('Could not find any matching order payment');
34
30
  const order = await services.orders.checkoutOrder(orderPayment.orderId, {
@@ -55,11 +51,7 @@ export const appleIAPHandler = async (req, reply) => {
55
51
  });
56
52
  }
57
53
  else {
58
- const originalOrderPayment = await modules.orders.payments.findOrderPaymentByContextData({
59
- context: {
60
- 'meta.transactionIdentifier': latestTransaction.original_transaction_id,
61
- },
62
- });
54
+ const originalOrderPayment = await modules.orders.payments.findOrderPaymentByTransactionId(latestTransaction.original_transaction_id);
63
55
  if (!originalOrderPayment)
64
56
  throw new Error('Could not find any matching order payment');
65
57
  const originalOrder = await modules.orders.findOrder({
@@ -28,13 +28,23 @@ export default async function handleWebhook({ secret, wallet, price, ping, }, co
28
28
  }));
29
29
  if (orderPayment) {
30
30
  const order = await modules.orders.findOrder({ orderId: orderPayment.orderId });
31
+ if (!order) {
32
+ logger.error('Order not found for payment', { orderPaymentId, orderId: orderPayment.orderId });
33
+ throw new Error('Order not found');
34
+ }
31
35
  if (order.status === null) {
36
+ logger.info('Initiating checkout for new order', { orderId: order._id });
32
37
  await services.orders.checkoutOrder(order._id, {});
33
38
  }
34
39
  else if (order.status === OrderStatus.PENDING) {
40
+ logger.info('Processing pending order after payment update', { orderId: order._id });
35
41
  await services.orders.processOrder(order, {});
36
42
  }
37
43
  else {
44
+ logger.warn('Order already processed, ignoring webhook', {
45
+ orderId: order._id,
46
+ status: order.status,
47
+ });
38
48
  throw new Error('Already processed');
39
49
  }
40
50
  }
@@ -20,7 +20,7 @@ export const datatransHandler = async (req, res) => {
20
20
  signKey: DATATRANS_SIGN2_KEY || DATATRANS_SIGN_KEY,
21
21
  })(timestamp, req.body);
22
22
  if (hash !== comparableSignature) {
23
- logger.error(`hash mismatch: ${signature} / ${comparableSignature}`, req.body);
23
+ logger.error(`hash mismatch: ${signature} / ${comparableSignature}`);
24
24
  res.status(403).send('Hash mismatch');
25
25
  return;
26
26
  }
@@ -61,7 +61,7 @@ export const datatransHandler = async (req, res) => {
61
61
  }
62
62
  }
63
63
  catch (e) {
64
- logger.error(`rejected to checkout with message`, e);
64
+ logger.error(`rejected to checkout: ${e.name} - ${e.message}`);
65
65
  res.status(500).send({ name: e.name, code: e.code, message: e.message });
66
66
  return;
67
67
  }
@@ -2,7 +2,6 @@ import { mongodb } from '@unchainedshop/mongodb';
2
2
  import { SaferpayClient } from "./api/index.js";
3
3
  import { buildSignature } from "./buildSignature.js";
4
4
  import { OrderPricingSheet, PaymentAdapter, PaymentDirector, PaymentError, } from '@unchainedshop/core';
5
- const { SAFERPAY_BASE_URL = 'https://test.saferpay.com/api', SAFERPAY_CUSTOMER_ID, SAFERPAY_WEBHOOK_PATH = '/payment/saferpay/webhook', SAFERPAY_RETURN_PATH = '/saferpay/return', ROOT_URL = 'http://localhost:4010', EMAIL_WEBSITE_URL, SAFERPAY_USER, SAFERPAY_PW, } = process.env;
6
5
  const newSaferpayError = ({ code, message }) => {
7
6
  const error = new Error(message || code);
8
7
  error.name = `SAFERPAY_${code}`;
@@ -23,6 +22,7 @@ export const WordlineSaferpay = {
23
22
  },
24
23
  actions: (config, context) => {
25
24
  const { modules } = context;
25
+ const { SAFERPAY_BASE_URL = 'https://test.saferpay.com/api', SAFERPAY_CUSTOMER_ID, SAFERPAY_WEBHOOK_PATH = '/payment/saferpay/webhook', SAFERPAY_RETURN_PATH = '/saferpay/return', ROOT_URL = 'http://localhost:4010', EMAIL_WEBSITE_URL, SAFERPAY_USER, SAFERPAY_PW, } = process.env;
26
26
  const createSaferPayClient = () => {
27
27
  if (!SAFERPAY_CUSTOMER_ID || !SAFERPAY_USER || !SAFERPAY_PW)
28
28
  throw new Error('Credentials not Set');
@@ -1,5 +1,6 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
2
  import { buildSignature } from "./buildSignature.js";
3
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
3
4
  const logger = createLogger('unchained:saferpay:handler');
4
5
  export const saferpayHandler = async (request, response) => {
5
6
  const resolvedContext = request.unchainedContext;
@@ -25,7 +26,7 @@ export const saferpayHandler = async (request, response) => {
25
26
  throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
26
27
  }
27
28
  const correctSignature = await buildSignature(transactionId, orderPaymentId);
28
- if (correctSignature !== signature) {
29
+ if (!(await timingSafeStringEqual(correctSignature, signature))) {
29
30
  throw new Error('Invalid signature');
30
31
  }
31
32
  const order = await services.orders.checkoutOrder(orderPayment.orderId, {
@@ -1,5 +1,6 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
2
  import { buildSignature } from "./buildSignature.js";
3
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
3
4
  const logger = createLogger('unchained:saferpay:handler');
4
5
  export const saferpayHandler = async (request, reply) => {
5
6
  const resolvedContext = request.unchainedContext;
@@ -25,7 +26,7 @@ export const saferpayHandler = async (request, reply) => {
25
26
  throw new Error(`order payment not found with orderPaymentId: ${orderPaymentId}`);
26
27
  }
27
28
  const correctSignature = await buildSignature(transactionId, orderPaymentId);
28
- if (correctSignature !== signature) {
29
+ if (!(await timingSafeStringEqual(correctSignature, signature))) {
29
30
  throw new Error('Invalid signature');
30
31
  }
31
32
  const order = await services.orders.checkoutOrder(orderPayment.orderId, {
@@ -1 +1,2 @@
1
- export {};
1
+ import { type OrderDiscountConfiguration, type IOrderPricingAdapter } from '@unchainedshop/core';
2
+ export declare const OrderItemsDiscount: IOrderPricingAdapter<OrderDiscountConfiguration>;
@@ -1,6 +1,6 @@
1
1
  import { ProductPricingSheet, OrderPricingDirector, OrderPricingAdapter, OrderPricingRowCategory, resolveRatioAndTaxDivisorForPricingSheet, } from '@unchainedshop/core';
2
2
  import { calculation as calcUtils } from '@unchainedshop/utils';
3
- const OrderItemsDiscount = {
3
+ export const OrderItemsDiscount = {
4
4
  ...OrderPricingAdapter,
5
5
  key: 'shop.unchained.pricing.order-items-discount',
6
6
  version: '1.0.0',
@@ -1 +1,2 @@
1
- export {};
1
+ import { type IOrderPricingAdapter } from '@unchainedshop/core';
2
+ export declare const OrderItems: IOrderPricingAdapter;
@@ -1,5 +1,5 @@
1
1
  import { OrderPricingDirector, OrderPricingAdapter, ProductPricingSheet, } from '@unchainedshop/core';
2
- const OrderItems = {
2
+ export const OrderItems = {
3
3
  ...OrderPricingAdapter,
4
4
  key: 'shop.unchained.pricing.order-items',
5
5
  version: '1.0.0',
@@ -1 +1,2 @@
1
- export {};
1
+ import { type IOrderPricingAdapter } from '@unchainedshop/core';
2
+ export declare const OrderPayment: IOrderPricingAdapter;
@@ -1,5 +1,5 @@
1
1
  import { OrderPricingDirector, OrderPricingAdapter, PaymentPricingSheet, } from '@unchainedshop/core';
2
- const OrderPayment = {
2
+ export const OrderPayment = {
3
3
  ...OrderPricingAdapter,
4
4
  key: 'shop.unchained.pricing.order-payment',
5
5
  version: '1.0.0',
@@ -1 +1,2 @@
1
- export {};
1
+ import { type IProductPricingAdapter } from '@unchainedshop/core';
2
+ export declare const ProductPriceOptions: IProductPricingAdapter;
@@ -1,5 +1,5 @@
1
1
  import { ProductPricingDirector, ProductPricingAdapter, } from '@unchainedshop/core';
2
- const ProductPrice = {
2
+ export const ProductPriceOptions = {
3
3
  ...ProductPricingAdapter,
4
4
  key: 'shop.unchained.pricing.product-price-options',
5
5
  version: '1.0',
@@ -25,7 +25,7 @@ const ProductPrice = {
25
25
  amount: itemTotal,
26
26
  isTaxable: Boolean(price.isTaxable),
27
27
  isNetPrice: Boolean(price.isNetPrice),
28
- meta: { adapter: ProductPrice.key },
28
+ meta: { adapter: ProductPriceOptions.key },
29
29
  });
30
30
  }
31
31
  },
@@ -48,4 +48,4 @@ const ProductPrice = {
48
48
  };
49
49
  },
50
50
  };
51
- ProductPricingDirector.registerAdapter(ProductPrice);
51
+ ProductPricingDirector.registerAdapter(ProductPriceOptions);
@@ -1 +1,2 @@
1
- export {};
1
+ import { type ProductDiscountConfiguration, type IProductPricingAdapter } from '@unchainedshop/core';
2
+ export declare const ProductDiscount: IProductPricingAdapter<ProductDiscountConfiguration>;
@@ -1,6 +1,6 @@
1
1
  import { calculation as calcUtils } from '@unchainedshop/utils';
2
2
  import { ProductPricingDirector, ProductPricingAdapter, ProductPricingRowCategory, } from '@unchainedshop/core';
3
- const ProductDiscount = {
3
+ export const ProductDiscount = {
4
4
  ...ProductPricingAdapter,
5
5
  key: 'shop.unchained.pricing.product-discount',
6
6
  version: '1.0.0',
@@ -5,7 +5,6 @@ import { systemLocale } from '@unchainedshop/utils';
5
5
  import { generateDbObjectId } from '@unchainedshop/mongodb';
6
6
  import { getFileAdapter } from '@unchainedshop/core-files';
7
7
  import { createLogger } from '@unchainedshop/logger';
8
- const { MINTER_TOKEN_OFFSET = '0', ROOT_URL = 'http://localhost:4010' } = process.env;
9
8
  const logger = createLogger('unchained:eth-minter');
10
9
  const ETHMinter = {
11
10
  ...WarehousingAdapter,
@@ -18,6 +17,7 @@ const ETHMinter = {
18
17
  return type === WarehousingProviderType.VIRTUAL;
19
18
  },
20
19
  actions: (configuration, context) => {
20
+ const { MINTER_TOKEN_OFFSET = '0', ROOT_URL = 'http://localhost:4010' } = process.env;
21
21
  const { product, orderPosition, token, modules, locale } = context;
22
22
  const { contractAddress, contractStandard, tokenId, supply, ercMetadataProperties } = product?.tokenization || {};
23
23
  const getTokensCreated = async () => {
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@unchainedshop/plugins",
3
- "version": "4.5.0",
3
+ "description": "Official plugin collection for the Unchained Engine with payment, delivery, and pricing adapters",
4
+ "version": "4.6.0",
4
5
  "main": "lib/plugins-index.js",
5
6
  "types": "lib/plugins-index.d.ts",
6
7
  "exports": {
@@ -36,18 +37,18 @@
36
37
  },
37
38
  "homepage": "https://github.com/unchainedshop/unchained#readme",
38
39
  "dependencies": {
39
- "@unchainedshop/api": "^4.5.0",
40
- "@unchainedshop/core-delivery": "^4.5.0",
41
- "@unchainedshop/core-enrollments": "^4.5.0",
42
- "@unchainedshop/core-orders": "^4.5.0",
43
- "@unchainedshop/core-payment": "^4.5.0",
44
- "@unchainedshop/core-products": "^4.5.0",
45
- "@unchainedshop/core-warehousing": "^4.5.0",
46
- "@unchainedshop/core-worker": "^4.5.0",
47
- "@unchainedshop/events": "^4.5.0",
48
- "@unchainedshop/file-upload": "^4.5.0",
49
- "@unchainedshop/logger": "^4.5.0",
50
- "@unchainedshop/utils": "^4.5.0"
40
+ "@unchainedshop/api": "^4.6.0",
41
+ "@unchainedshop/core-delivery": "^4.6.0",
42
+ "@unchainedshop/core-enrollments": "^4.6.0",
43
+ "@unchainedshop/core-orders": "^4.6.0",
44
+ "@unchainedshop/core-payment": "^4.6.0",
45
+ "@unchainedshop/core-products": "^4.6.0",
46
+ "@unchainedshop/core-warehousing": "^4.6.0",
47
+ "@unchainedshop/core-worker": "^4.6.0",
48
+ "@unchainedshop/events": "^4.6.0",
49
+ "@unchainedshop/file-upload": "^4.6.0",
50
+ "@unchainedshop/logger": "^4.6.0",
51
+ "@unchainedshop/utils": "^4.6.0"
51
52
  },
52
53
  "peerDependencies": {
53
54
  "@aws-sdk/client-eventbridge": ">= 3.714 < 4",