@unchainedshop/plugins 4.8.13 → 4.8.15

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.
@@ -58,19 +58,31 @@ const PaypalCheckout = {
58
58
  throw new Error('You have to provide orderID in paymentContext');
59
59
  }
60
60
  try {
61
- const request = new checkoutNodeJssdk.orders.OrdersGetRequest(orderID);
62
61
  const client = new checkoutNodeJssdk.core.PayPalHttpClient(environment());
63
- const paypalOrder = await client.execute(request);
62
+ const getRequest = new checkoutNodeJssdk.orders.OrdersGetRequest(orderID);
63
+ let paypalResult = (await client.execute(getRequest)).result;
64
+ if (paypalResult.status === 'APPROVED') {
65
+ const captureRequest = new checkoutNodeJssdk.orders.OrdersCaptureRequest(orderID);
66
+ captureRequest.requestBody({});
67
+ paypalResult = (await client.execute(captureRequest)).result;
68
+ }
69
+ if (paypalResult.status !== 'COMPLETED') {
70
+ throw new Error(`PayPal order not completed (status: ${paypalResult.status})`);
71
+ }
72
+ const capture = paypalResult.purchase_units?.[0]?.payments?.captures?.find((c) => c.status === 'COMPLETED');
73
+ if (!capture) {
74
+ throw new Error('PayPal order has no completed capture');
75
+ }
64
76
  const pricing = OrderPricingSheet({
65
77
  calculation: order.calculation,
66
78
  currencyCode: order.currencyCode,
67
79
  });
68
80
  const ourTotal = roundToDecimals(pricing.total({ useNetPrice: false }).amount / 100, 2);
69
- const paypalTotal = roundToDecimals(paypalOrder.result.purchase_units[0].amount.value, 2);
70
- if (ourTotal === paypalTotal) {
81
+ const paidTotal = roundToDecimals(capture.amount.value, 2);
82
+ if (ourTotal === paidTotal && capture.amount.currency_code === order.currencyCode) {
71
83
  return order;
72
84
  }
73
- logger.warn('Missmatch PAYPAL ORDER', JSON.stringify(paypalOrder.result, null, 2));
85
+ logger.warn('Missmatch PAYPAL ORDER', JSON.stringify(paypalResult, null, 2));
74
86
  logger.debug('OUR ORDER', order);
75
87
  logger.debug('OUR PRICE', pricing);
76
88
  throw new Error(`Payment mismatch`);
@@ -10,7 +10,7 @@ else {
10
10
  try {
11
11
  const { default: Stripe } = await import('stripe');
12
12
  stripe = new Stripe(STRIPE_SECRET, {
13
- apiVersion: '2026-04-22.dahlia',
13
+ apiVersion: '2026-05-27.dahlia',
14
14
  });
15
15
  }
16
16
  catch {
@@ -14,6 +14,7 @@ import '../enrollments/licensed.ts';
14
14
  import '../events/node-event-emitter.ts';
15
15
  import '../worker/bulk-import.ts';
16
16
  import '../worker/zombie-killer.ts';
17
+ import '../worker/gc-guests.ts';
17
18
  import '../worker/message.ts';
18
19
  import '../worker/external.ts';
19
20
  import '../worker/http-request.ts';
@@ -14,6 +14,7 @@ import "../enrollments/licensed.js";
14
14
  import "../events/node-event-emitter.js";
15
15
  import "../worker/bulk-import.js";
16
16
  import "../worker/zombie-killer.js";
17
+ import "../worker/gc-guests.js";
17
18
  import "../worker/message.js";
18
19
  import "../worker/external.js";
19
20
  import "../worker/http-request.js";
@@ -0,0 +1,8 @@
1
+ import { type IWorkerAdapter } from '@unchainedshop/core';
2
+ export declare const GCGuestsWorker: IWorkerAdapter<{
3
+ guestUserMaxAgeInDays?: number;
4
+ }, {
5
+ scannedGuestCount: number;
6
+ deletedGuestCount: number;
7
+ }>;
8
+ export default GCGuestsWorker;
@@ -0,0 +1,60 @@
1
+ import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
2
+ import { userSettings } from '@unchainedshop/core-users';
3
+ import { createLogger } from '@unchainedshop/logger';
4
+ const logger = createLogger('unchained:worker:gc-guests');
5
+ const ONE_DAY_IN_MILLISECONDS = 86400000;
6
+ const everyDayAtHalfPastTwo = schedule.parse.cron('30 2 * * *');
7
+ export const GCGuestsWorker = {
8
+ ...WorkerAdapter,
9
+ key: 'shop.unchained.worker-plugin.gc-guests',
10
+ label: 'Garbage Collect Guests',
11
+ version: '1.0.0',
12
+ type: 'GC_GUESTS',
13
+ doWork: async ({ guestUserMaxAgeInDays } = {}, unchainedAPI) => {
14
+ const { modules, services } = unchainedAPI;
15
+ try {
16
+ const maxAgeInDays = guestUserMaxAgeInDays ?? userSettings.guestUserMaxAgeInDays;
17
+ const before = new Date(Date.now() - maxAgeInDays * ONE_DAY_IN_MILLISECONDS);
18
+ const candidateIds = await modules.users.findGuestUserIds({ before });
19
+ const recentCarts = candidateIds.length
20
+ ? await modules.orders.findCarts({ userIds: candidateIds }, { projection: { userId: 1, updated: 1 } })
21
+ : [];
22
+ const userIdsWithRecentCart = new Set(recentCarts.filter((cart) => cart.updated && cart.updated >= before).map((c) => c.userId));
23
+ const userIdsToDelete = candidateIds.filter((id) => !userIdsWithRecentCart.has(id));
24
+ let deletedGuestCount = 0;
25
+ await Array.fromAsync(userIdsToDelete, async (userId) => {
26
+ try {
27
+ await services.users.deleteUser({ userId });
28
+ deletedGuestCount += 1;
29
+ }
30
+ catch (err) {
31
+ logger.warn(`Failed to garbage-collect guest ${userId}: ${err.message}`);
32
+ }
33
+ });
34
+ return {
35
+ success: true,
36
+ result: {
37
+ scannedGuestCount: candidateIds.length,
38
+ deletedGuestCount,
39
+ },
40
+ };
41
+ }
42
+ catch (err) {
43
+ return {
44
+ success: false,
45
+ error: {
46
+ name: err.name,
47
+ message: err.message,
48
+ stack: err.stack,
49
+ },
50
+ };
51
+ }
52
+ },
53
+ };
54
+ export default GCGuestsWorker;
55
+ WorkerDirector.registerAdapter(GCGuestsWorker);
56
+ WorkerDirector.configureAutoscheduling({
57
+ type: GCGuestsWorker.type,
58
+ schedule: everyDayAtHalfPastTwo,
59
+ retries: 2,
60
+ });
@@ -9,5 +9,6 @@ export declare const ZombieKillerWorker: IWorkerAdapter<{
9
9
  deletedProductTextsCount: number;
10
10
  deletedProductVariationsCount: number;
11
11
  deletedAssortmentTextsCount: number;
12
+ deletedCartsCount: number;
12
13
  }>;
13
14
  export default ZombieKillerWorker;
@@ -1,12 +1,14 @@
1
- import { WorkerAdapter, WorkerDirector } from '@unchainedshop/core';
1
+ import { WorkerAdapter, WorkerDirector, schedule } from '@unchainedshop/core';
2
+ const everyDayAtTwo = schedule.parse.cron('0 2 * * *');
2
3
  export const ZombieKillerWorker = {
3
4
  ...WorkerAdapter,
4
5
  key: 'shop.unchained.worker-plugin.zombie-killer',
5
6
  label: 'Zombie Killer',
6
7
  version: '1.0.0',
7
8
  type: 'ZOMBIE_KILLER',
8
- doWork: async ({ bulkImportMaxAgeInDays } = { bulkImportMaxAgeInDays: 5 }, unchainedAPI) => {
9
+ doWork: async (input, unchainedAPI) => {
9
10
  const { modules, services } = unchainedAPI;
11
+ const { bulkImportMaxAgeInDays = 5 } = input || {};
10
12
  try {
11
13
  const error = false;
12
14
  const filters = await modules.filters.findFilters({ includeInactive: true }, { projection: { _id: 1 } });
@@ -38,7 +40,9 @@ export const ZombieKillerWorker = {
38
40
  const productMedia = await modules.products.media.findProductMedias({}, { projection: { mediaId: 1 } });
39
41
  const assortmentMedia = await modules.assortments.media.findAssortmentMedias({}, { projection: { mediaId: 1 } });
40
42
  const allFileIdsLinked = [...productMedia, ...assortmentMedia].map((l) => l?.mediaId);
41
- const allFileIdsRelevant = (await modules.files.findFiles({ paths: ['product-media', 'assortment-media'] }, { projection: { _id: 1 } })).map((a) => a._id);
43
+ const allFileIdsRelevant = (await modules.files.findFiles({ paths: ['product-media', 'assortment-media'] }, { projection: { _id: 1, expires: 1 } }))
44
+ .filter((file) => !file.expires)
45
+ .map((a) => a._id);
42
46
  const fileIdsToRemove = allFileIdsRelevant.filter((fileId) => {
43
47
  return !allFileIdsLinked.includes(fileId);
44
48
  }) || [];
@@ -54,6 +58,17 @@ export const ZombieKillerWorker = {
54
58
  fileIds: fileIdsToRemove,
55
59
  })
56
60
  : 0;
61
+ const cartUserIds = (await modules.orders.findCartUserIds()).filter(Boolean);
62
+ const existingUserIds = new Set(cartUserIds.length ? await modules.users.findExistingUserIds({ userIds: cartUserIds }) : []);
63
+ const deadUserIds = cartUserIds.filter((userId) => !existingUserIds.has(userId));
64
+ const deadCarts = deadUserIds.length
65
+ ? await modules.orders.findCarts({ userIds: deadUserIds }, { projection: { _id: 1 } })
66
+ : [];
67
+ let deletedCartsCount = 0;
68
+ await Array.fromAsync(deadCarts, async (cart) => {
69
+ await services.orders.deleteCart(cart._id);
70
+ deletedCartsCount += 1;
71
+ });
57
72
  const result = {
58
73
  deletedFilterTextsCount,
59
74
  deletedProductMediaCount,
@@ -62,6 +77,7 @@ export const ZombieKillerWorker = {
62
77
  deletedAssortmentMediaCount,
63
78
  deletedAssortmentTextsCount,
64
79
  deletedFilesCount,
80
+ deletedCartsCount,
65
81
  };
66
82
  if (error) {
67
83
  return {
@@ -89,3 +105,8 @@ export const ZombieKillerWorker = {
89
105
  };
90
106
  export default ZombieKillerWorker;
91
107
  WorkerDirector.registerAdapter(ZombieKillerWorker);
108
+ WorkerDirector.configureAutoscheduling({
109
+ type: ZombieKillerWorker.type,
110
+ schedule: everyDayAtTwo,
111
+ retries: 2,
112
+ });
package/package.json CHANGED
@@ -1,7 +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.8.13",
4
+ "version": "4.8.15",
5
5
  "main": "lib/plugins-index.js",
6
6
  "types": "lib/plugins-index.d.ts",
7
7
  "exports": {
@@ -43,6 +43,7 @@
43
43
  "@unchainedshop/core-orders": "^4.8.12",
44
44
  "@unchainedshop/core-payment": "^4.8.12",
45
45
  "@unchainedshop/core-products": "^4.8.12",
46
+ "@unchainedshop/core-users": "^4.8.12",
46
47
  "@unchainedshop/core-warehousing": "^4.8.12",
47
48
  "@unchainedshop/core-worker": "^4.8.12",
48
49
  "@unchainedshop/events": "^4.8.12",
@@ -53,7 +54,7 @@
53
54
  "peerDependencies": {
54
55
  "@aws-sdk/client-eventbridge": ">= 3.714 < 4",
55
56
  "@paypal/checkout-server-sdk": "1.x",
56
- "@redis/client": ">= 1 < 6",
57
+ "@redis/client": ">= 1 < 7",
57
58
  "@scure/bip32": ">= 2",
58
59
  "@scure/btc-signer": ">= 2",
59
60
  "mime": ">= 4 < 5",
@@ -61,7 +62,7 @@
61
62
  "express": ">= 5 < 6",
62
63
  "fastify": ">= 5.2 < 6",
63
64
  "minio": "8.x",
64
- "nodemailer": ">= 6.9 < 9",
65
+ "nodemailer": ">= 6.9 < 10",
65
66
  "p-memoize": "8.x",
66
67
  "stripe": ">= 19 < 23",
67
68
  "web-push": ">= 3.6 <4",
@@ -113,7 +114,7 @@
113
114
  },
114
115
  "devDependencies": {
115
116
  "@paypal/checkout-server-sdk": "^1.0.3",
116
- "@redis/client": "^5.6.0",
117
+ "@redis/client": "^6.0.0",
117
118
  "@scure/bip32": "^2.0.0",
118
119
  "@scure/btc-signer": "^2.0.0",
119
120
  "@types/express": "^5.0.3",
@@ -122,7 +123,7 @@
122
123
  "express": "^5.1.0",
123
124
  "fastify": "^5.4.0",
124
125
  "minio": "^8.0.5",
125
- "nodemailer": "^8.0.2",
126
+ "nodemailer": "^9.0.0",
126
127
  "p-memoize": "^8.0.0",
127
128
  "stripe": "^22.0.2",
128
129
  "typescript": "^5.8.3",