@unchainedshop/plugins 4.5.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +12 -10
- package/lib/presets/base.d.ts +1 -0
- package/lib/presets/base.js +1 -0
- 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/bulk-export.d.ts +2 -0
- package/lib/worker/bulk-export.js +64 -0
- package/package.json +14 -13
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;
|
|
@@ -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, {
|
|
@@ -3,17 +3,19 @@ const logger = createLogger('unchained:stripe');
|
|
|
3
3
|
const { STRIPE_SECRET, STRIPE_WEBHOOK_ENVIRONMENT, EMAIL_WEBSITE_NAME } = process.env;
|
|
4
4
|
export let stripe;
|
|
5
5
|
const environment = STRIPE_WEBHOOK_ENVIRONMENT ?? null;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
logger.warn('STRIPE_SECRET is not set, skipping initialization');
|
|
9
|
-
}
|
|
10
|
-
const { default: Stripe } = await import('stripe');
|
|
11
|
-
stripe = new Stripe(STRIPE_SECRET, {
|
|
12
|
-
apiVersion: '2025-12-15.clover',
|
|
13
|
-
});
|
|
6
|
+
if (!STRIPE_SECRET) {
|
|
7
|
+
logger.warn('STRIPE_SECRET is not set, skipping initialization');
|
|
14
8
|
}
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
else {
|
|
10
|
+
try {
|
|
11
|
+
const { default: Stripe } = await import('stripe');
|
|
12
|
+
stripe = new Stripe(STRIPE_SECRET, {
|
|
13
|
+
apiVersion: '2025-12-15.clover',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
logger.warn(`optional peer npm package 'stripe' not installed, stripe adapter will not work`);
|
|
18
|
+
}
|
|
17
19
|
}
|
|
18
20
|
export const upsertCustomer = async ({ userId, name, email }) => {
|
|
19
21
|
try {
|
package/lib/presets/base.d.ts
CHANGED
|
@@ -20,5 +20,6 @@ import '../worker/http-request.ts';
|
|
|
20
20
|
import '../worker/heartbeat.ts';
|
|
21
21
|
import '../worker/email.ts';
|
|
22
22
|
import '../worker/error-notifications.ts';
|
|
23
|
+
import '../worker/bulk-export.ts';
|
|
23
24
|
import gridfsModules from '../files/gridfs/index.ts';
|
|
24
25
|
export default gridfsModules;
|
package/lib/presets/base.js
CHANGED
|
@@ -20,5 +20,6 @@ import "../worker/http-request.js";
|
|
|
20
20
|
import "../worker/heartbeat.js";
|
|
21
21
|
import "../worker/email.js";
|
|
22
22
|
import "../worker/error-notifications.js";
|
|
23
|
+
import "../worker/bulk-export.js";
|
|
23
24
|
import gridfsModules from "../files/gridfs/index.js";
|
|
24
25
|
export default gridfsModules;
|
|
@@ -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 () => {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { WorkerDirector, WorkerAdapter } from '@unchainedshop/core';
|
|
2
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
3
|
+
const isSupportedLocale = (locale) => {
|
|
4
|
+
try {
|
|
5
|
+
const dtf = new Intl.DateTimeFormat(locale);
|
|
6
|
+
const resolved = dtf.resolvedOptions().locale;
|
|
7
|
+
return resolved.toLowerCase() === locale.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
console.error(e);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const createLanguageDialectList = (languages, countries) => {
|
|
15
|
+
const result = new Set();
|
|
16
|
+
(languages || []).forEach(({ isoCode: baseIsoCode }) => {
|
|
17
|
+
result.add(baseIsoCode);
|
|
18
|
+
(countries || []).forEach(({ isoCode: countryIsoCode }) => {
|
|
19
|
+
const dialectIsoCode = [baseIsoCode, countryIsoCode.toUpperCase()].join('-');
|
|
20
|
+
if (isSupportedLocale(dialectIsoCode))
|
|
21
|
+
result.add(dialectIsoCode);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
return Array.from(result);
|
|
25
|
+
};
|
|
26
|
+
const logger = createLogger('unchained:bulk-export');
|
|
27
|
+
export const BulkExportWorker = {
|
|
28
|
+
...WorkerAdapter,
|
|
29
|
+
key: 'shop.unchained.worker-plugin.bulk-export',
|
|
30
|
+
label: 'Bulk Export',
|
|
31
|
+
version: '2.1.0',
|
|
32
|
+
type: 'BULK_EXPORT',
|
|
33
|
+
maxParallelAllocations: 1,
|
|
34
|
+
doWork: async (payload, unchainedAPI) => {
|
|
35
|
+
try {
|
|
36
|
+
const { modules } = unchainedAPI;
|
|
37
|
+
const languages = await modules.languages.findLanguages({ includeInactive: false });
|
|
38
|
+
const countries = await modules.countries.findCountries({ includeInactive: false });
|
|
39
|
+
const bulkExporter = unchainedAPI.bulkExporter.createBulkExporter({ entity: payload.type });
|
|
40
|
+
await bulkExporter.validate(payload);
|
|
41
|
+
const locales = createLanguageDialectList(languages, countries);
|
|
42
|
+
const [result, error] = await bulkExporter.execute(payload, locales, unchainedAPI);
|
|
43
|
+
if (error) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
result: result,
|
|
47
|
+
error,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
result,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
logger.error(err);
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
error: { name: err.name, message: err.message, stack: err.stack },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
WorkerDirector.registerAdapter(BulkExportWorker);
|
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.1",
|
|
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.
|
|
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",
|