@unchainedshop/plugins 4.4.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 +26 -0
- package/lib/files/gridfs/adapter.js +1 -2
- package/lib/files/gridfs/handler-express.js +7 -2
- package/lib/files/gridfs/handler-fastify.js +7 -2
- package/lib/files/minio/handler-express.js +8 -3
- package/lib/files/minio/handler-fastify.js +8 -3
- package/lib/filters/local-search.js +8 -17
- package/lib/payment/apple-iap/adapter.js +1 -1
- package/lib/payment/apple-iap/handler-express.js +3 -11
- package/lib/payment/apple-iap/handler-fastify.js +2 -10
- package/lib/payment/cryptopay/handle-webhook.js +10 -0
- package/lib/payment/datatrans-v2/handler-express.js +2 -2
- package/lib/payment/saferpay/adapter.js +1 -1
- package/lib/payment/saferpay/handler-express.js +2 -1
- package/lib/payment/saferpay/handler-fastify.js +2 -1
- package/lib/payment/stripe/stripe.js +1 -1
- package/lib/pricing/order-items-discount.d.ts +2 -1
- package/lib/pricing/order-items-discount.js +1 -1
- package/lib/pricing/order-items.d.ts +2 -1
- package/lib/pricing/order-items.js +1 -1
- package/lib/pricing/order-payment.d.ts +2 -1
- package/lib/pricing/order-payment.js +1 -1
- package/lib/pricing/product-catalog-price-options.d.ts +2 -1
- package/lib/pricing/product-catalog-price-options.js +3 -3
- package/lib/pricing/product-discount.d.ts +2 -1
- package/lib/pricing/product-discount.js +1 -1
- package/lib/warehousing/eth-minter.js +1 -1
- package/lib/worker/error-notifications.js +2 -3
- package/lib/worker/update-coinbase-rates.js +2 -3
- package/lib/worker/update-ecb-rates.js +2 -3
- package/lib/worker/update-token-ownership.js +4 -5
- package/lib/worker/zombie-killer.js +2 -2
- package/package.json +14 -19
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { mongodb } from '@unchainedshop/mongodb';
|
|
2
|
-
import {} from '@unchainedshop/core-products';
|
|
3
|
-
import {} from '@unchainedshop/core-assortments';
|
|
4
1
|
import { FilterDirector, FilterAdapter } from '@unchainedshop/core';
|
|
5
2
|
import { createLogger } from '@unchainedshop/logger';
|
|
6
3
|
import { isDocumentDBCompatModeEnabled } from '@unchainedshop/mongodb';
|
|
@@ -18,13 +15,10 @@ const LocalSearch = {
|
|
|
18
15
|
const { queryString } = params.searchQuery;
|
|
19
16
|
if (!queryString)
|
|
20
17
|
return productIds;
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
selector.productId = { $in: [...new Set(productIds)] };
|
|
26
|
-
}
|
|
27
|
-
const products = await params.modules.products.texts.findTexts(selector, {
|
|
18
|
+
const products = await params.modules.products.texts.findTexts({
|
|
19
|
+
queryString,
|
|
20
|
+
productIds: productIds ? [...new Set(productIds)] : undefined,
|
|
21
|
+
}, {
|
|
28
22
|
projection: {
|
|
29
23
|
productId: 1,
|
|
30
24
|
},
|
|
@@ -36,13 +30,10 @@ const LocalSearch = {
|
|
|
36
30
|
if (!queryString) {
|
|
37
31
|
return assortmentIds;
|
|
38
32
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
selector.assortmentId = { $in: assortmentIds };
|
|
44
|
-
}
|
|
45
|
-
const assortments = await params.modules.assortments.texts.findTexts(selector, {
|
|
33
|
+
const assortments = await params.modules.assortments.texts.findTexts({
|
|
34
|
+
queryString,
|
|
35
|
+
assortmentIds,
|
|
36
|
+
}, {
|
|
46
37
|
projection: {
|
|
47
38
|
assortmentId: 1,
|
|
48
39
|
},
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
import { type IOrderPricingAdapter } from '@unchainedshop/core';
|
|
2
|
+
export declare const OrderItems: IOrderPricingAdapter;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { type IOrderPricingAdapter } from '@unchainedshop/core';
|
|
2
|
+
export declare const OrderPayment: IOrderPricingAdapter;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
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
|
|
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:
|
|
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(
|
|
51
|
+
ProductPricingDirector.registerAdapter(ProductPriceOptions);
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
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 () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { WorkStatus } from '@unchainedshop/core-worker';
|
|
2
|
-
import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core';
|
|
3
|
-
|
|
4
|
-
const everyDayAtFourInTheNight = later.parse.cron('0 3 * * *');
|
|
2
|
+
import { WorkerDirector, WorkerAdapter, schedule } from '@unchainedshop/core';
|
|
3
|
+
const everyDayAtFourInTheNight = schedule.parse.cron('0 3 * * *');
|
|
5
4
|
export const ErrorNotifications = {
|
|
6
5
|
...WorkerAdapter,
|
|
7
6
|
key: 'shop.unchained.worker.error-notifications',
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
|
|
2
|
-
import later from '@breejs/later';
|
|
1
|
+
import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
|
|
3
2
|
import { resolveBestCurrency } from '@unchainedshop/utils';
|
|
4
3
|
const getExchangeRates = async (base) => {
|
|
5
4
|
return fetch(`https://api.coinbase.com/v2/exchange-rates?currency=${base}`, {
|
|
@@ -8,7 +7,7 @@ const getExchangeRates = async (base) => {
|
|
|
8
7
|
.then((res) => res.json())
|
|
9
8
|
.then((r) => r?.data);
|
|
10
9
|
};
|
|
11
|
-
const everyMinute =
|
|
10
|
+
const everyMinute = schedule.parse.cron('* * * * *');
|
|
12
11
|
const UpdateCoinbaseRates = {
|
|
13
12
|
...WorkerAdapter,
|
|
14
13
|
key: 'shop.unchained.worker.update-coinbase-rates',
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
|
|
2
|
-
import later from '@breejs/later';
|
|
1
|
+
import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
|
|
3
2
|
import { createLogger } from '@unchainedshop/logger';
|
|
4
3
|
const logger = createLogger('unchained:worker:update-ecb-rates');
|
|
5
4
|
let xml2json;
|
|
@@ -20,7 +19,7 @@ const getExchangeRates = async () => {
|
|
|
20
19
|
.filter((e) => e.name.toLowerCase() === 'cube')[0]
|
|
21
20
|
?.elements[0]?.elements.map((element) => element.attributes));
|
|
22
21
|
};
|
|
23
|
-
const everyDayAtFour =
|
|
22
|
+
const everyDayAtFour = schedule.parse.cron('0 15 * * *');
|
|
24
23
|
const baseCurrency = 'EUR';
|
|
25
24
|
const UpdateECBRates = {
|
|
26
25
|
...WorkerAdapter,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
|
|
2
|
-
|
|
3
|
-
const everyMinute = later.parse.cron('* * * * *');
|
|
1
|
+
import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
|
|
2
|
+
const everyMinute = schedule.parse.cron('* * * * *');
|
|
4
3
|
export const UpdateTokenOwnership = {
|
|
5
4
|
...WorkerAdapter,
|
|
6
5
|
key: 'shop.unchained.worker-plugin.update-token-ownership',
|
|
@@ -21,8 +20,8 @@ export const RefreshTokens = {
|
|
|
21
20
|
external: false,
|
|
22
21
|
async doWork(input, unchainedAPI) {
|
|
23
22
|
const { modules } = unchainedAPI;
|
|
24
|
-
const tokens = await modules.warehousing.findTokens({
|
|
25
|
-
const users = await modules.users.findUsers({ includeGuests: true,
|
|
23
|
+
const tokens = await modules.warehousing.findTokens({ walletAddressExists: true });
|
|
24
|
+
const users = await modules.users.findUsers({ includeGuests: true, web3Verified: true });
|
|
26
25
|
const accounts = users.flatMap((user) => {
|
|
27
26
|
return user?.services?.web3?.flatMap((service) => {
|
|
28
27
|
if (service.verified)
|
|
@@ -38,13 +38,13 @@ export const ZombieKillerWorker = {
|
|
|
38
38
|
const productMedia = await modules.products.media.findProductMedias({}, { projection: { mediaId: 1 } });
|
|
39
39
|
const assortmentMedia = await modules.assortments.media.findAssortmentMedias({}, { projection: { mediaId: 1 } });
|
|
40
40
|
const allFileIdsLinked = [...productMedia, ...assortmentMedia].map((l) => l?.mediaId);
|
|
41
|
-
const allFileIdsRelevant = (await modules.files.findFiles({
|
|
41
|
+
const allFileIdsRelevant = (await modules.files.findFiles({ paths: ['product-media', 'assortment-media'] }, { projection: { _id: 1 } })).map((a) => a._id);
|
|
42
42
|
const fileIdsToRemove = allFileIdsRelevant.filter((fileId) => {
|
|
43
43
|
return !allFileIdsLinked.includes(fileId);
|
|
44
44
|
}) || [];
|
|
45
45
|
const bulkImportMedia = await await modules.files.findFiles({
|
|
46
46
|
path: 'bulk-import-streams',
|
|
47
|
-
|
|
47
|
+
createdBefore: new Date(Date.now() - 1000 * 60 * 60 * 24 * bulkImportMaxAgeInDays),
|
|
48
48
|
});
|
|
49
49
|
bulkImportMedia.forEach(async (media) => {
|
|
50
50
|
fileIdsToRemove.push(media._id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/plugins",
|
|
3
|
-
"
|
|
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,22 +37,21 @@
|
|
|
36
37
|
},
|
|
37
38
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@unchainedshop/api": "^4.
|
|
40
|
-
"@unchainedshop/core-delivery": "^4.
|
|
41
|
-
"@unchainedshop/core-enrollments": "^4.
|
|
42
|
-
"@unchainedshop/core-orders": "^4.
|
|
43
|
-
"@unchainedshop/core-payment": "^4.
|
|
44
|
-
"@unchainedshop/core-products": "^4.
|
|
45
|
-
"@unchainedshop/core-warehousing": "^4.
|
|
46
|
-
"@unchainedshop/core-worker": "^4.
|
|
47
|
-
"@unchainedshop/events": "^4.
|
|
48
|
-
"@unchainedshop/file-upload": "^4.
|
|
49
|
-
"@unchainedshop/logger": "^4.
|
|
50
|
-
"@unchainedshop/utils": "^4.
|
|
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",
|
|
54
|
-
"@breejs/later": "^4.2.0",
|
|
55
55
|
"@paypal/checkout-server-sdk": "1.x",
|
|
56
56
|
"@redis/client": ">= 1 < 6",
|
|
57
57
|
"@scure/bip32": ">= 2",
|
|
@@ -68,9 +68,6 @@
|
|
|
68
68
|
"xml-js": ">= 1.6 < 2"
|
|
69
69
|
},
|
|
70
70
|
"peerDependenciesMeta": {
|
|
71
|
-
"@breejs/later": {
|
|
72
|
-
"optional": true
|
|
73
|
-
},
|
|
74
71
|
"@aws-sdk/client-eventbridge": {
|
|
75
72
|
"optional": true
|
|
76
73
|
},
|
|
@@ -115,12 +112,10 @@
|
|
|
115
112
|
}
|
|
116
113
|
},
|
|
117
114
|
"devDependencies": {
|
|
118
|
-
"@breejs/later": "^4.2.0",
|
|
119
115
|
"@paypal/checkout-server-sdk": "^1.0.3",
|
|
120
116
|
"@redis/client": "^5.6.0",
|
|
121
117
|
"@scure/bip32": "^2.0.0",
|
|
122
118
|
"@scure/btc-signer": "^2.0.0",
|
|
123
|
-
"@types/breejs__later": "^4.1.5",
|
|
124
119
|
"@types/express": "^5.0.3",
|
|
125
120
|
"@types/node": "^25.0.0",
|
|
126
121
|
"expiry-map": "^2.0.0",
|