@unchainedshop/plugins 4.8.5 → 5.0.0-alpha.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.
Files changed (58) hide show
  1. package/README.md +0 -2
  2. package/lib/events/node-event-emitter.d.ts +3 -1
  3. package/lib/events/node-event-emitter.js +3 -3
  4. package/lib/files/gridfs/adapter.d.ts +2 -2
  5. package/lib/files/gridfs/adapter.js +2 -5
  6. package/lib/files/gridfs/index.d.ts +5 -4
  7. package/lib/files/gridfs/index.js +30 -3
  8. package/lib/files/minio/api.js +1 -2
  9. package/lib/files/minio/index.d.ts +4 -5
  10. package/lib/files/minio/index.js +18 -157
  11. package/lib/files/temp-upload/api.d.ts +2 -0
  12. package/lib/files/temp-upload/api.js +1 -2
  13. package/lib/payment/apple-iap/adapter.d.ts +2 -1
  14. package/lib/payment/apple-iap/adapter.js +2 -3
  15. package/lib/payment/apple-iap/api.js +1 -1
  16. package/lib/payment/apple-iap/index.d.ts +6 -4
  17. package/lib/payment/apple-iap/index.js +28 -3
  18. package/lib/payment/cryptopay/api.js +1 -1
  19. package/lib/payment/cryptopay/handle-webhook.js +2 -1
  20. package/lib/payment/cryptopay/index.d.ts +5 -4
  21. package/lib/payment/cryptopay/index.js +31 -3
  22. package/lib/payment/datatrans-v2/adapter.js +11 -3
  23. package/lib/payment/datatrans-v2/api/authorize.js +1 -1
  24. package/lib/payment/datatrans-v2/api/authorizeAuthenticated.js +1 -1
  25. package/lib/payment/datatrans-v2/api/cancel.js +1 -1
  26. package/lib/payment/datatrans-v2/api/init.js +1 -1
  27. package/lib/payment/datatrans-v2/api/secureFields.js +1 -1
  28. package/lib/payment/datatrans-v2/api/settle.js +1 -1
  29. package/lib/payment/datatrans-v2/api/status.js +1 -1
  30. package/lib/payment/datatrans-v2/api/validate.js +1 -1
  31. package/lib/payment/datatrans-v2/index.d.ts +4 -1
  32. package/lib/payment/datatrans-v2/index.js +17 -411
  33. package/lib/payment/payrexx/api/index.js +2 -2
  34. package/lib/payment/payrexx/api.js +1 -1
  35. package/lib/payment/payrexx/index.d.ts +4 -1
  36. package/lib/payment/payrexx/index.js +19 -158
  37. package/lib/payment/postfinance-checkout/index.d.ts +5 -0
  38. package/lib/payment/postfinance-checkout/index.js +25 -186
  39. package/lib/payment/saferpay/adapter.js +1 -2
  40. package/lib/payment/saferpay/index.d.ts +5 -4
  41. package/lib/payment/saferpay/index.js +29 -3
  42. package/lib/payment/stripe/index.d.ts +4 -1
  43. package/lib/payment/stripe/index.js +22 -116
  44. package/lib/payment/stripe/stripe.js +1 -4
  45. package/lib/presets/all.d.ts +1 -70
  46. package/lib/presets/all.js +41 -27
  47. package/lib/presets/base.d.ts +1 -25
  48. package/lib/presets/base.js +56 -25
  49. package/lib/presets/countries/ch.d.ts +1 -2
  50. package/lib/presets/countries/ch.js +7 -2
  51. package/lib/presets/crypto.d.ts +3 -7
  52. package/lib/presets/crypto.js +18 -7
  53. package/lib/worker/bulk-import/api.d.ts +2 -0
  54. package/lib/worker/bulk-import/api.js +1 -2
  55. package/lib/worker/bulkgate/adapter.js +1 -1
  56. package/lib/worker/twilio/adapter.js +1 -1
  57. package/lib/worker/update-coinbase-rates/adapter.js +3 -5
  58. package/package.json +25 -30
package/README.md CHANGED
@@ -22,7 +22,6 @@ npm install @unchainedshop/plugins
22
22
  | Stripe | `payment/stripe` | Stripe payment integration |
23
23
  | Datatrans V2 | `payment/datatrans-v2` | Datatrans payment gateway |
24
24
  | Saferpay | `payment/saferpay` | Saferpay payment gateway |
25
- | PayPal Checkout | `payment/paypal-checkout` | PayPal Checkout integration |
26
25
  | Braintree | `payment/braintree` | Braintree payments |
27
26
  | Payrexx | `payment/payrexx` | Payrexx payment gateway |
28
27
  | Apple IAP | `payment/apple-iap` | Apple In-App Purchase |
@@ -134,7 +133,6 @@ All payment plugins implement secure tokenization patterns for PCI DSS SAQ-A eli
134
133
  | Stripe | PaymentIntent/SetupIntent tokenization |
135
134
  | Datatrans | Secure Fields with HMAC-SHA-256 signatures |
136
135
  | Saferpay | Redirect with SHA-256 transaction signatures |
137
- | PayPal | Order ID references |
138
136
  | Braintree | Client SDK tokenization |
139
137
  | Cryptopay | BIP-32 HD wallet address derivation |
140
138
 
@@ -1 +1,3 @@
1
- export {};
1
+ import { type EmitAdapter } from '@unchainedshop/events';
2
+ export declare const NodeEventEmitter: () => EmitAdapter;
3
+ export default NodeEventEmitter;
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import { setEmitAdapter } from '@unchainedshop/events';
3
- const NodeEventEmitter = () => {
2
+ import {} from '@unchainedshop/events';
3
+ export const NodeEventEmitter = () => {
4
4
  const eventEmitter = new EventEmitter();
5
5
  return {
6
6
  publish: (eventName, payload) => {
@@ -11,4 +11,4 @@ const NodeEventEmitter = () => {
11
11
  },
12
12
  };
13
13
  };
14
- setEmitAdapter(NodeEventEmitter());
14
+ export default NodeEventEmitter;
@@ -1,6 +1,6 @@
1
- import { type IFileAdapter } from '@unchainedshop/file-upload';
1
+ import { type IFileAdapter } from '@unchainedshop/core-files';
2
2
  import { type UnchainedCore } from '@unchainedshop/core';
3
- import { type GridFSFileUploadsModule } from './index.ts';
3
+ import { type GridFSFileUploadsModule } from './module.ts';
4
4
  export declare const GridFSAdapter: IFileAdapter<UnchainedCore & {
5
5
  modules: GridFSFileUploadsModule;
6
6
  }>;
@@ -1,12 +1,10 @@
1
1
  import { Readable, PassThrough } from 'node:stream';
2
2
  import { finished, pipeline } from 'node:stream/promises';
3
3
  import mime from 'mime/lite';
4
- import { FileAdapter, FileDirector, buildHashedFilename, resolveExpirationDate, } from '@unchainedshop/file-upload';
5
- import {} from '@unchainedshop/file-upload';
4
+ import { FileAdapter, buildHashedFilename, resolveExpirationDate, filesSettings, } from '@unchainedshop/core-files';
6
5
  import sign from "./sign.js";
7
- import { filesSettings } from '@unchainedshop/core-files';
8
6
  import {} from '@unchainedshop/core';
9
- import {} from "./index.js";
7
+ import {} from "./module.js";
10
8
  const { GRIDFS_PUT_SERVER_PATH = '/gridfs' } = process.env;
11
9
  const bufferToStream = (buffer) => {
12
10
  const stream = new Readable();
@@ -113,4 +111,3 @@ export const GridFSAdapter = {
113
111
  await Promise.all(files.map(async ({ _id, path }) => modules.gridfsFileUploads.removeFileFromBucket(path, _id)));
114
112
  },
115
113
  };
116
- FileDirector.registerAdapter(GridFSAdapter);
@@ -1,4 +1,5 @@
1
- import './adapter.ts';
2
- import gridfsModules, { type GridFSFileUploadsModule } from './module.ts';
3
- export type { GridFSFileUploadsModule };
4
- export default gridfsModules;
1
+ import type { IPlugin } from '@unchainedshop/core';
2
+ export declare const GridFSPlugin: IPlugin;
3
+ export default GridFSPlugin;
4
+ export { GridFSAdapter } from './adapter.ts';
5
+ export { type GridFSFileUploadsModule } from './module.ts';
@@ -1,3 +1,30 @@
1
- import "./adapter.js";
2
- import gridfsModules, {} from "./module.js";
3
- export default gridfsModules;
1
+ import { createLogger } from '@unchainedshop/logger';
2
+ import { GridFSAdapter } from "./adapter.js";
3
+ import { configureGridFSFileUploadModule } from "./module.js";
4
+ import { gridfsRouteHandler } from "./api.js";
5
+ const { GRIDFS_PUT_SERVER_PATH = '/gridfs', UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET } = process.env;
6
+ const logger = createLogger('unchained:gridfs');
7
+ export const GridFSPlugin = {
8
+ key: 'shop.unchained.file-upload-plugin.gridfs',
9
+ label: 'GridFS File Upload Plugin',
10
+ version: '1.0.0',
11
+ adapters: [GridFSAdapter],
12
+ module: ({ db }) => ({
13
+ gridfsFileUploads: configureGridFSFileUploadModule({ db }),
14
+ }),
15
+ routes: [
16
+ {
17
+ path: `${GRIDFS_PUT_SERVER_PATH}/:directoryName/:fileName`,
18
+ method: 'ALL',
19
+ handler: gridfsRouteHandler,
20
+ },
21
+ ],
22
+ onRegister: () => {
23
+ if (!UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET) {
24
+ logger.warn('UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET not set - PUT uploads and signed downloads will fail');
25
+ }
26
+ },
27
+ };
28
+ export default GridFSPlugin;
29
+ export { GridFSAdapter } from "./adapter.js";
30
+ export {} from "./module.js";
@@ -19,8 +19,7 @@ export async function minioWebhookHandler(request, context) {
19
19
  headers: { 'Content-Type': 'application/json' },
20
20
  });
21
21
  }
22
- const body = await request.json();
23
- const { Records = [], EventName } = body;
22
+ const { Records = [], EventName } = (await request.json());
24
23
  if (EventName === 's3:ObjectCreated:Put') {
25
24
  const [{ s3 }] = Records;
26
25
  const { object } = s3;
@@ -1,5 +1,4 @@
1
- import { type IFileAdapter } from '@unchainedshop/file-upload';
2
- import { Client } from 'minio';
3
- export declare function connectToMinio(): Promise<Client | null>;
4
- export declare const MinioAdapter: IFileAdapter;
5
- export default MinioAdapter;
1
+ import type { IPlugin } from '@unchainedshop/core';
2
+ export declare const MinioPlugin: IPlugin;
3
+ export default MinioPlugin;
4
+ export { MinioAdapter, connectToMinio } from './adapter.ts';
@@ -1,164 +1,25 @@
1
- import { Readable } from 'node:stream';
2
- import {} from '@unchainedshop/file-upload';
3
- import { FileAdapter, FileDirector, buildHashedFilename, resolveExpirationDate, } from '@unchainedshop/file-upload';
4
- import mime from 'mime/lite';
5
- import { Client } from 'minio';
6
- import { AssumeRoleProvider } from 'minio/dist/esm/AssumeRoleProvider.mjs';
7
- import { expiryOffsetInMs } from '@unchainedshop/file-upload/lib/put-expiration.js';
8
1
  import { createLogger } from '@unchainedshop/logger';
2
+ import { MinioAdapter } from "./adapter.js";
3
+ import { minioWebhookHandler } from "./api.js";
9
4
  const logger = createLogger('unchained:minio');
10
- const { MINIO_ACCESS_KEY, MINIO_REGION, MINIO_STS_ENDPOINT, MINIO_SECRET_KEY, MINIO_ENDPOINT, MINIO_BUCKET_NAME, MINIO_UPLOAD_PREFIX, NODE_ENV, AMAZON_S3_SESSION_TOKEN, } = process.env;
11
- let client = null;
12
- export async function connectToMinio() {
13
- if (!MINIO_ENDPOINT || !MINIO_BUCKET_NAME) {
14
- logger.error('Please configure Minio/S3 by providing MINIO_ENDPOINT & MINIO_BUCKET_NAME to use upload features');
15
- return null;
16
- }
17
- try {
18
- const resolvedUrl = new URL(MINIO_ENDPOINT);
19
- const minioClient = new Client({
20
- endPoint: resolvedUrl.hostname,
21
- useSSL: resolvedUrl.protocol === 'https:',
22
- port: parseInt(resolvedUrl.port, 10) || undefined,
23
- region: MINIO_REGION,
24
- sessionToken: AMAZON_S3_SESSION_TOKEN,
25
- accessKey: MINIO_ACCESS_KEY,
26
- secretKey: MINIO_SECRET_KEY,
27
- });
28
- if (NODE_ENV === 'development')
29
- minioClient?.traceOn(process.stdout);
30
- if (MINIO_STS_ENDPOINT) {
31
- const ap = new AssumeRoleProvider({
32
- stsEndpoint: MINIO_STS_ENDPOINT,
33
- accessKey: MINIO_ACCESS_KEY,
34
- secretKey: MINIO_SECRET_KEY,
35
- });
36
- await minioClient.setCredentialsProvider(ap);
37
- }
38
- return minioClient;
39
- }
40
- catch (error) {
41
- logger.error(`Exception while creating Minio client: ${error.message}`);
42
- }
43
- return null;
44
- }
45
- const generateMinioPath = (directoryName, fileName) => {
46
- const prefix = MINIO_UPLOAD_PREFIX || '';
47
- return [prefix, directoryName, fileName].filter(Boolean).join('/');
48
- };
49
- const generateMinioUrl = (directoryName, hashedFilename) => {
50
- return `${MINIO_ENDPOINT}/${MINIO_BUCKET_NAME}/${generateMinioPath(directoryName, hashedFilename)}`;
51
- };
52
- connectToMinio().then(function setClient(c) {
53
- client = c || client;
54
- });
55
- const getObjectStats = async (fileName) => {
56
- if (!client)
57
- throw new Error('Minio not connected, check env variables');
58
- return client.statObject(MINIO_BUCKET_NAME, fileName);
59
- };
60
- const bufferToStream = (buffer) => {
61
- const stream = new Readable();
62
- stream.push(buffer);
63
- stream.push(null);
64
- return stream;
65
- };
66
- export const MinioAdapter = {
5
+ const { MINIO_WEBHOOK_AUTH_TOKEN, MINIO_WEBHOOK_PATH = '/minio/webhook' } = process.env;
6
+ export const MinioPlugin = {
67
7
  key: 'shop.unchained.file-upload-plugin.minio',
68
- label: 'Uploads files into an S3 bucket using minio',
8
+ label: 'Minio/S3 File Upload Plugin with Webhook',
69
9
  version: '1.1.0',
70
- ...FileAdapter,
71
- async createDownloadURL(file) {
72
- if (file.meta?.isPrivate)
73
- throw new Error("Minio Plugin doesn't support private files yet");
74
- return generateMinioUrl(file.path, file._id);
75
- },
76
- async createSignedURL(directoryName, fileName) {
77
- if (!client)
78
- throw new Error('Minio not connected, check env variables');
79
- const expiryDate = resolveExpirationDate();
80
- const _id = await buildHashedFilename(directoryName, fileName, expiryDate);
81
- const url = await client.presignedPutObject(MINIO_BUCKET_NAME, generateMinioPath(directoryName, _id), expiryOffsetInMs() / 1000);
82
- return {
83
- _id,
84
- directoryName,
85
- expiryDate,
86
- fileName,
87
- type: mime.getType(fileName),
88
- putURL: url,
89
- url: generateMinioUrl(directoryName, _id),
90
- };
91
- },
92
- async removeFiles(files) {
93
- if (!client)
94
- throw new Error('Minio not connected, check env variables');
95
- const fileIds = files.map(({ path, _id }) => {
96
- return `${path}/${_id}`;
97
- });
98
- await client.removeObjects(MINIO_BUCKET_NAME, fileIds);
99
- },
100
- async uploadFileFromStream(directoryName, rawFile) {
101
- if (!client)
102
- throw new Error('Minio not connected, check env variables');
103
- let stream;
104
- let fileName;
105
- if (rawFile instanceof Promise) {
106
- const { filename: fname, createReadStream } = await rawFile;
107
- fileName = fname;
108
- stream = createReadStream();
109
- }
110
- else {
111
- fileName = rawFile.filename;
112
- stream = bufferToStream(Buffer.from(rawFile.buffer, 'base64'));
10
+ adapters: [MinioAdapter],
11
+ routes: [
12
+ {
13
+ path: MINIO_WEBHOOK_PATH,
14
+ method: 'POST',
15
+ handler: minioWebhookHandler,
16
+ },
17
+ ],
18
+ onRegister: () => {
19
+ if (!MINIO_WEBHOOK_AUTH_TOKEN) {
20
+ logger.warn('MINIO_WEBHOOK_AUTH_TOKEN not set - webhooks will be disabled');
113
21
  }
114
- const _id = await buildHashedFilename(directoryName, fileName, new Date());
115
- const type = mime.getType(fileName) || (await Promise.resolve(rawFile)).mimetype;
116
- const metaData = {
117
- 'Content-Type': type,
118
- };
119
- await client.putObject(MINIO_BUCKET_NAME, generateMinioPath(directoryName, _id), stream, undefined, metaData);
120
- const { size } = await getObjectStats(generateMinioPath(directoryName, _id));
121
- return {
122
- _id,
123
- directoryName,
124
- expiryDate: null,
125
- fileName,
126
- size,
127
- type,
128
- url: generateMinioUrl(directoryName, _id),
129
- };
130
- },
131
- async uploadFileFromURL(directoryName, { fileLink, fileName: fname, fileId, headers }) {
132
- if (!client)
133
- throw new Error('Minio not connected, check env variables');
134
- const { href } = new URL(fileLink);
135
- const fileName = fname || href.split('/').pop();
136
- const hashedFilename = await buildHashedFilename(directoryName, fileName, new Date());
137
- const url = new URL(fileLink);
138
- const response = await fetch(url, { headers });
139
- const type = mime.getType(fileName) || response.headers['content-type'];
140
- const readable = Readable.fromWeb(response.body);
141
- const metaData = {
142
- 'Content-Type': type,
143
- };
144
- await client.putObject(MINIO_BUCKET_NAME, generateMinioPath(directoryName, hashedFilename), readable, undefined, metaData);
145
- const { size } = await getObjectStats(generateMinioPath(directoryName, hashedFilename));
146
- return {
147
- _id: fileId || hashedFilename,
148
- directoryName,
149
- expiryDate: null,
150
- fileName,
151
- size,
152
- type,
153
- url: generateMinioUrl(directoryName, hashedFilename),
154
- };
155
- },
156
- async createDownloadStream(file) {
157
- if (!client)
158
- throw new Error('Minio not connected, check env variables');
159
- const stream = await client.getObject(MINIO_BUCKET_NAME, generateMinioPath(file.path, file._id));
160
- return stream;
161
22
  },
162
23
  };
163
- FileDirector.registerAdapter(MinioAdapter);
164
- export default MinioAdapter;
24
+ export default MinioPlugin;
25
+ export { MinioAdapter, connectToMinio } from "./adapter.js";
@@ -1,6 +1,8 @@
1
1
  import { type UnchainedCore } from '@unchainedshop/core';
2
+ import type { RolesInterface } from '@unchainedshop/roles';
2
3
  export declare function tempUploadHandler(request: Request, context: UnchainedCore & {
3
4
  params: Record<string, string>;
5
+ roles?: RolesInterface;
4
6
  userId?: string;
5
7
  user?: any;
6
8
  }): Promise<Response>;
@@ -1,10 +1,9 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
2
  import { getFileAdapter } from '@unchainedshop/core';
3
- import { Roles } from '@unchainedshop/roles';
4
3
  const logger = createLogger('unchained:temp-upload');
5
4
  export async function tempUploadHandler(request, context) {
6
5
  try {
7
- const hasPermission = await Roles.userHasPermission(context, 'uploadTempFile', []);
6
+ const hasPermission = await context.roles?.userHasPermission(context, 'uploadTempFile', []);
8
7
  if (!hasPermission) {
9
8
  return Response.json({ error: 'Permission denied', message: 'User does not have uploadTempFile permission' }, { status: 403 });
10
9
  }
@@ -1 +1,2 @@
1
- export {};
1
+ import { type IPaymentAdapter } from '@unchainedshop/core';
2
+ export declare const AppleIAP: IPaymentAdapter;
@@ -1,10 +1,10 @@
1
- import { PaymentAdapter, PaymentDirector, PaymentError, } from '@unchainedshop/core';
1
+ import { PaymentAdapter, PaymentError } from '@unchainedshop/core';
2
2
  import { createLogger } from '@unchainedshop/logger';
3
3
  import {} from "./module.js";
4
4
  import { verifyReceipt } from "./verify-receipt.js";
5
5
  const logger = createLogger('unchained:apple-iap');
6
6
  const { APPLE_IAP_SHARED_SECRET } = process.env;
7
- const AppleIAP = {
7
+ export const AppleIAP = {
8
8
  ...PaymentAdapter,
9
9
  key: 'shop.unchained.apple-iap',
10
10
  label: 'Apple In-App-Purchase',
@@ -111,4 +111,3 @@ const AppleIAP = {
111
111
  return adapterActions;
112
112
  },
113
113
  };
114
- PaymentDirector.registerAdapter(AppleIAP);
@@ -13,7 +13,7 @@ const AppleNotificationTypes = {
13
13
  export async function appleIAPWebhookHandler(request, context) {
14
14
  try {
15
15
  const { modules, services } = context;
16
- const responseBody = await request.json();
16
+ const responseBody = (await request.json());
17
17
  if (responseBody.password !== APPLE_IAP_SHARED_SECRET) {
18
18
  logger.warn('Apple IAP Webhook: Invalid shared secret');
19
19
  return new Response(JSON.stringify({
@@ -1,4 +1,6 @@
1
- export * from './adapter.ts';
2
- import appleTransactionsModule, { type AppleTransactionsModule } from './module.ts';
3
- export type { AppleTransactionsModule };
4
- export default appleTransactionsModule;
1
+ import type { IPlugin } from '@unchainedshop/core';
2
+ import { type AppleTransactionsModule } from './module.ts';
3
+ export declare const AppleIAPPlugin: IPlugin;
4
+ export default AppleIAPPlugin;
5
+ export { AppleIAP } from './adapter.ts';
6
+ export { type AppleTransactionsModule };
@@ -1,3 +1,28 @@
1
- export * from "./adapter.js";
2
- import appleTransactionsModule, {} from "./module.js";
3
- export default appleTransactionsModule;
1
+ import { AppleIAP } from "./adapter.js";
2
+ import { configureAppleTransactionsModule } from "./module.js";
3
+ import { appleIAPWebhookHandler } from "./api.js";
4
+ const { APPLE_IAP_WEBHOOK_PATH = '/payment/apple-iap', APPLE_IAP_SHARED_SECRET } = process.env;
5
+ export const AppleIAPPlugin = {
6
+ key: 'shop.unchained.payment.apple-iap',
7
+ label: 'Apple In-App-Purchase Payment Plugin',
8
+ version: '1.0.0',
9
+ adapters: [AppleIAP],
10
+ module: ({ db }) => ({
11
+ appleTransactions: configureAppleTransactionsModule({ db }),
12
+ }),
13
+ routes: [
14
+ {
15
+ path: APPLE_IAP_WEBHOOK_PATH,
16
+ method: 'POST',
17
+ handler: appleIAPWebhookHandler,
18
+ },
19
+ ],
20
+ onRegister: () => {
21
+ if (!APPLE_IAP_SHARED_SECRET) {
22
+ throw new Error('APPLE_IAP_SHARED_SECRET not set');
23
+ }
24
+ },
25
+ };
26
+ export default AppleIAPPlugin;
27
+ export { AppleIAP } from "./adapter.js";
28
+ export {};
@@ -3,7 +3,7 @@ import handleWebhook from "./handle-webhook.js";
3
3
  const logger = createLogger('unchained:cryptopay');
4
4
  export async function cryptopayWebhookHandler(request, context) {
5
5
  try {
6
- const body = await request.json();
6
+ const body = (await request.json());
7
7
  await handleWebhook(body, context);
8
8
  return new Response(JSON.stringify({ success: true }), {
9
9
  status: 200,
@@ -1,4 +1,5 @@
1
1
  import { createLogger } from '@unchainedshop/logger';
2
+ import { timingSafeStringEqual } from '@unchainedshop/utils';
2
3
  import {} from '@unchainedshop/api';
3
4
  import { OrderStatus } from '@unchainedshop/core-orders';
4
5
  import {} from "./module.js";
@@ -7,7 +8,7 @@ const { CRYPTOPAY_SECRET, CRYPTOPAY_MAX_RATE_AGE = '360' } = process.env;
7
8
  const logger = createLogger('unchained:cryptopay:handler');
8
9
  export default async function handleWebhook({ secret, wallet, price, ping, }, context) {
9
10
  const { modules, services } = context;
10
- if (secret !== CRYPTOPAY_SECRET) {
11
+ if (!CRYPTOPAY_SECRET || !(await timingSafeStringEqual(secret, CRYPTOPAY_SECRET))) {
11
12
  logger.warn(`webhook called with invalid secret`);
12
13
  throw new Error('Secret invalid');
13
14
  }
@@ -1,4 +1,5 @@
1
- import './plugin.ts';
2
- import cryptopayModules, { type CryptopayModule } from './module.ts';
3
- export type { CryptopayModule };
4
- export default cryptopayModules;
1
+ import type { IPlugin } from '@unchainedshop/core';
2
+ export declare const CryptopayPlugin: IPlugin;
3
+ export default CryptopayPlugin;
4
+ export { Cryptopay } from './adapter.ts';
5
+ export { type CryptopayModule } from './module.ts';
@@ -1,3 +1,31 @@
1
- import "./plugin.js";
2
- import cryptopayModules, {} from "./module.js";
3
- export default cryptopayModules;
1
+ import cryptopayModules from "./module.js";
2
+ import { Cryptopay } from "./adapter.js";
3
+ import { cryptopayWebhookHandler } from "./api.js";
4
+ const { CRYPTOPAY_SECRET, CRYPTOPAY_BTC_XPUB, CRYPTOPAY_ETH_XPUB, CRYPTOPAY_WEBHOOK_PATH = '/payment/cryptopay', } = process.env;
5
+ export const CryptopayPlugin = {
6
+ key: 'shop.unchained.payment.cryptopay',
7
+ label: 'Cryptopay Payment Plugin',
8
+ version: '1.0.0',
9
+ adapters: [Cryptopay],
10
+ module: ({ db }) => ({
11
+ cryptopay: cryptopayModules.cryptopay.configure({ db }),
12
+ }),
13
+ routes: [
14
+ {
15
+ path: CRYPTOPAY_WEBHOOK_PATH,
16
+ method: 'POST',
17
+ handler: cryptopayWebhookHandler,
18
+ },
19
+ ],
20
+ onRegister: () => {
21
+ if (!CRYPTOPAY_SECRET) {
22
+ throw new Error('CRYPTOPAY_SECRET not set');
23
+ }
24
+ if (!CRYPTOPAY_BTC_XPUB && !CRYPTOPAY_ETH_XPUB) {
25
+ throw new Error('No CRYPTOPAY_BTC_XPUB or CRYPTOPAY_ETH_XPUB set');
26
+ }
27
+ },
28
+ };
29
+ export default CryptopayPlugin;
30
+ export { Cryptopay } from "./adapter.js";
31
+ export {} from "./module.js";
@@ -26,10 +26,13 @@ const getMarketplaceSplits = async ({ order, orderPayment, config, }) => {
26
26
  currencyCode: order.currencyCode,
27
27
  });
28
28
  const { amount: total } = pricing.total({ useNetPrice: false });
29
- return Promise.all(config
29
+ const roundedTotal = Math.round(total);
30
+ const splits = config
30
31
  .filter((item) => item.key === 'marketplaceSplit')
31
32
  .map((item) => {
32
- const [subMerchantId, staticDiscountId, sharePercentage] = item.value || ''.split(';').map((f) => f.trim());
33
+ const [subMerchantId, staticDiscountId, sharePercentage] = (item.value || '')
34
+ .split(';')
35
+ .map((f) => f.trim());
33
36
  const { amount: discountSum } = pricingForOrderPayment.total({
34
37
  category: PaymentPricingRowCategory.Discount,
35
38
  discountId: staticDiscountId,
@@ -42,7 +45,12 @@ const getMarketplaceSplits = async ({ order, orderPayment, config, }) => {
42
45
  amount,
43
46
  commission,
44
47
  };
45
- }));
48
+ });
49
+ if (splits.length > 0) {
50
+ const splitsSum = splits.reduce((sum, s) => sum + s.amount, 0);
51
+ splits[splits.length - 1].amount += roundedTotal - splitsSum;
52
+ }
53
+ return splits;
46
54
  };
47
55
  export const Datatrans = {
48
56
  ...PaymentAdapter,
@@ -5,6 +5,6 @@ export default async function authorize({ ...payload }) {
5
5
  };
6
6
  const { fetchDatatrans } = this;
7
7
  const result = await fetchDatatrans('/v1/transactions/authorize', reqBody);
8
- const json = await result.json();
8
+ const json = (await result.json());
9
9
  return json;
10
10
  }
@@ -5,6 +5,6 @@ export default async function authorizeAuthenticated({ transactionId, ...payload
5
5
  };
6
6
  const { fetchDatatrans } = this;
7
7
  const result = await fetchDatatrans(`/v1/transactions/${transactionId}/authorize`, reqBody);
8
- const json = await result.json();
8
+ const json = (await result.json());
9
9
  return json;
10
10
  }
@@ -4,6 +4,6 @@ export default async function cancel({ transactionId, ...payload }) {
4
4
  const result = await fetchDatatrans(`/v1/transactions/${transactionId}/cancel`, reqBody);
5
5
  if (result.status === 204)
6
6
  return true;
7
- const json = await result.json();
7
+ const json = (await result.json());
8
8
  return json;
9
9
  }
@@ -23,7 +23,7 @@ export default async function init({ option, redirect: userRedirect, ...payload
23
23
  };
24
24
  const { fetchDatatrans } = this;
25
25
  const result = await fetchDatatrans('/v1/transactions', reqBody);
26
- const json = await result.json();
26
+ const json = (await result.json());
27
27
  const location = result.headers?.get('location');
28
28
  return {
29
29
  location,
@@ -7,6 +7,6 @@ export default async function secureFields(payload) {
7
7
  };
8
8
  const { fetchDatatrans } = this;
9
9
  const result = await fetchDatatrans('/v1/transactions/secureFields', reqBody);
10
- const json = await result.json();
10
+ const json = (await result.json());
11
11
  return json;
12
12
  }
@@ -4,6 +4,6 @@ export default async function settle({ transactionId, ...payload }) {
4
4
  const result = await fetchDatatrans(`/v1/transactions/${transactionId}/settle`, reqBody);
5
5
  if (result.status === 204)
6
6
  return true;
7
- const json = await result.json();
7
+ const json = (await result.json());
8
8
  return json;
9
9
  }
@@ -1,6 +1,6 @@
1
1
  export default async function status({ transactionId }) {
2
2
  const { fetchDatatrans } = this;
3
3
  const result = await fetchDatatrans(`/v1/transactions/${transactionId}`);
4
- const json = await result.json();
4
+ const json = (await result.json());
5
5
  return json;
6
6
  }
@@ -4,6 +4,6 @@ export default async function validate(payload) {
4
4
  ...payload,
5
5
  };
6
6
  const result = await fetchDatatrans(`/v1/transactions/validate`, reqBody);
7
- const json = await result.json();
7
+ const json = (await result.json());
8
8
  return json;
9
9
  }
@@ -1 +1,4 @@
1
- export {};
1
+ import type { IPlugin } from '@unchainedshop/core';
2
+ export declare const DatatransPlugin: IPlugin;
3
+ export default DatatransPlugin;
4
+ export { Datatrans } from './adapter.ts';