strapi-plugin-payone-provider 5.6.10 → 5.6.12

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.
@@ -1,139 +1,172 @@
1
1
  "use strict";
2
2
 
3
- const { getPluginStore } = require("./settingsService");
3
+ const { sanitizeSensitive } = require("../utils/sanitize");
4
4
 
5
- const logTransaction = async (strapi, transactionData) => {
6
- const pluginStore = getPluginStore(strapi);
7
- let transactionHistory =
8
- (await pluginStore.get({ key: "transactionHistory" })) || [];
9
-
10
- const logEntry = {
11
- id: Date.now().toString(),
12
- timestamp: new Date().toISOString(),
13
- txid: transactionData.txid || null,
14
- reference: transactionData.reference || null,
15
- invoiceid: transactionData.invoiceid || null,
16
- request_type:
17
- transactionData.request_type || transactionData.request || "unknown",
18
- amount: transactionData.amount || null,
19
- currency: transactionData.currency || "EUR",
20
- status: transactionData.status || transactionData.Status || "unknown",
21
- error_code:
22
- transactionData.error_code || transactionData.Error?.ErrorCode || null,
23
- error_message:
24
- transactionData.error_message ||
25
- transactionData.Error?.ErrorMessage ||
26
- null,
27
- customer_message:
28
- transactionData.customer_message ||
29
- transactionData.Error?.CustomerMessage ||
30
- null,
31
- body: transactionData || null,
32
- created_at: new Date().toISOString(),
33
- updated_at: new Date().toISOString()
34
- };
5
+ const TRANSACTION_UID = "plugin::strapi-plugin-payone-provider.transaction";
35
6
 
36
- transactionHistory.unshift(logEntry);
7
+ const logTransaction = async (strapi, transactionData) => {
8
+ try {
9
+ const data = {
10
+ txid: transactionData.txid || 'NO TXID',
11
+ reference: transactionData.reference || 'NO REFERENCE',
12
+ invoiceid: transactionData.raw_request.invoiceid || 'NO INVOICE ID',
13
+ request_type: transactionData.request_type || "unknown",
14
+ amount: transactionData.amount || "0",
15
+ currency: transactionData.currency || "EUR",
16
+ status: transactionData.status || transactionData.raw_response.Status || "unknown",
17
+ error_code: transactionData.error_code || "NO ERROR CODE",
18
+ error_message: transactionData.error_message || "NO ERROR MESSAGE",
19
+ customer_message: transactionData.customer_message || "NO CUSTOMER MESSAGE",
20
+ body: transactionData ? { ...transactionData, raw_request: sanitizeSensitive(transactionData.raw_request), raw_response: sanitizeSensitive(transactionData.raw_response) } : {},
21
+ raw_request: sanitizeSensitive(transactionData.raw_request || {}),
22
+ raw_response: sanitizeSensitive(transactionData.raw_response || {}),
23
+ };
24
+
25
+ const entry = await strapi.db.query(TRANSACTION_UID).create({ data });
26
+ console.info("Transaction logged to DB:", {
27
+ id: entry.id,
28
+ txid: entry.txid,
29
+ status: entry.status
30
+ });
37
31
 
38
- if (transactionHistory.length > 1000) {
39
- transactionHistory = transactionHistory.slice(0, 1000);
32
+ return entry;
33
+ } catch (error) {
34
+ console.error("Failed to log transaction:", error);
40
35
  }
36
+ };
41
37
 
42
- await pluginStore.set({
43
- key: "transactionHistory",
44
- value: transactionHistory
45
- });
46
38
 
47
- console.log(`Transaction logged: ${logEntry}`);
48
- };
39
+ const hasFilterValue = (v) =>
40
+ typeof v === "string" && v.trim() !== "" && v.trim().toLowerCase() !== "all";
49
41
 
42
+ const buildWhereFromFilters = (filters = {}) => {
43
+ const conditions = [];
50
44
 
51
- const applyFilters = (transactions, filters = {}) => {
52
- let result = [...transactions];
53
- if (filters.search && typeof filters.search === 'string' && filters.search.trim() !== '') {
54
- const search = filters.search.toLowerCase().trim();
55
- result = result.filter((t) => {
56
- const txid = (t.txid || "").toString().toLowerCase();
57
- const reference = (t.reference || "").toString().toLowerCase();
58
- return txid.includes(search) || reference.includes(search);
45
+ if (hasFilterValue(filters.search)) {
46
+ const search = String(filters.search).trim();
47
+ conditions.push({
48
+ $or: [
49
+ { txid: { $containsi: search } },
50
+ { reference: { $containsi: search } },
51
+ ],
59
52
  });
60
53
  }
61
54
 
62
- if (filters.status) {
63
- result = result.filter(
64
- (t) => (t.status || "").toUpperCase() === filters.status.toUpperCase()
65
- );
66
- }
67
-
68
- if (filters.request_type) {
69
- result = result.filter((t) => t.request_type === filters.request_type);
55
+ if (hasFilterValue(filters.status)) {
56
+ conditions.push({ status: { $eqi: String(filters.status).trim() } });
70
57
  }
71
58
 
72
- if (filters.payment_method) {
73
- result = result.filter((t) => {
74
- const clearingtype = t.raw_request?.clearingtype;
75
- const wallettype = t.raw_request?.wallettype;
76
-
77
- switch (filters.payment_method) {
78
- case "credit_card":
79
- return clearingtype === "cc";
80
- case "paypal":
81
- return clearingtype === "wlt" && wallettype === "PPE";
82
- case "google_pay":
83
- return clearingtype === "wlt" && ["GPY", "GOOGLEPAY"].includes(wallettype);
84
- case "apple_pay":
85
- return clearingtype === "wlt" && ["APL", "APPLEPAY"].includes(wallettype);
86
- case "sofort":
87
- return clearingtype === "sb";
88
- case "sepa":
89
- return clearingtype === "elv";
90
- default:
91
- return true;
92
- }
93
- });
59
+ if (hasFilterValue(filters.request_type)) {
60
+ conditions.push({ request_type: String(filters.request_type).trim() });
94
61
  }
95
62
 
96
- if (filters.date_from) {
63
+ if (hasFilterValue(filters.date_from)) {
97
64
  const dateFrom = new Date(filters.date_from);
98
65
  dateFrom.setHours(0, 0, 0, 0);
99
- result = result.filter(
100
- (t) => new Date(t.timestamp) >= dateFrom
101
- );
66
+ conditions.push({ createdAt: { $gte: dateFrom.toISOString() } });
102
67
  }
103
68
 
104
- if (filters.date_to) {
69
+ if (hasFilterValue(filters.date_to)) {
105
70
  const dateTo = new Date(filters.date_to);
106
71
  dateTo.setHours(23, 59, 59, 999);
107
- result = result.filter(
108
- (t) => new Date(t.timestamp) <= dateTo
109
- );
72
+ conditions.push({ createdAt: { $lte: dateTo.toISOString() } });
110
73
  }
111
74
 
112
- return result;
113
- };
75
+ if (hasFilterValue(filters.payment_method)) {
76
+ switch (filters.payment_method) {
77
+ case "credit_card":
78
+ conditions.push({ raw_request: { $containsi: '"clearingtype":"cc"' } });
79
+ break;
80
+ case "paypal":
81
+ conditions.push({
82
+ $and: [
83
+ { raw_request: { $containsi: '"clearingtype":"wlt"' } },
84
+ { raw_request: { $containsi: '"wallettype":"PPE"' } },
85
+ ],
86
+ });
87
+ break;
88
+ case "google_pay":
89
+ conditions.push({
90
+ $and: [
91
+ { raw_request: { $containsi: '"clearingtype":"wlt"' } },
92
+ {
93
+ $or: [
94
+ { raw_request: { $containsi: '"wallettype":"GPY"' } },
95
+ { raw_request: { $containsi: '"wallettype":"GOOGLEPAY"' } },
96
+ ],
97
+ },
98
+ ],
99
+ });
100
+ break;
101
+ case "apple_pay":
102
+ conditions.push({
103
+ $and: [
104
+ { raw_request: { $containsi: '"clearingtype":"wlt"' } },
105
+ {
106
+ $or: [
107
+ { raw_request: { $containsi: '"wallettype":"APL"' } },
108
+ { raw_request: { $containsi: '"wallettype":"APPLEPAY"' } },
109
+ ],
110
+ },
111
+ ],
112
+ });
113
+ break;
114
+ case "sofort":
115
+ conditions.push({ raw_request: { $containsi: '"clearingtype":"sb"' } });
116
+ break;
117
+ case "sepa":
118
+ conditions.push({ raw_request: { $containsi: '"clearingtype":"elv"' } });
119
+ break;
120
+ default:
121
+ break;
122
+ }
123
+ }
114
124
 
115
- const getTransactionHistory = async (strapi, { filters = {}, pagination = {} }) => {
116
- const pluginStore = getPluginStore(strapi);
125
+ if (conditions.length === 0) return undefined;
126
+ if (conditions.length === 1) return conditions[0];
127
+ return { $and: conditions };
128
+ };
117
129
 
118
- let transactions =
119
- (await pluginStore.get({ key: "transactionHistory" })) || [];
130
+ const ALLOWED_SORT_FIELDS = [
131
+ "txid",
132
+ "reference",
133
+ "amount",
134
+ "request_type",
135
+ "status",
136
+ "createdAt",
137
+ "updatedAt",
138
+ ];
139
+
140
+ const getTransactionHistory = async (
141
+ strapi,
142
+ { filters = {}, pagination = {}, sort_by, sort_order }
143
+ ) => {
144
+ const page = Math.max(1, Number(pagination.page) || 1);
145
+ const pageSize = Math.min(100, Math.max(1, Number(pagination.pageSize) || 10));
146
+ const offset = (page - 1) * pageSize;
147
+
148
+ const where = buildWhereFromFilters(filters);
149
+
150
+ const sortField =
151
+ sort_by && ALLOWED_SORT_FIELDS.includes(sort_by) ? sort_by : "createdAt";
152
+ const order = sort_order === "asc" ? "asc" : "desc";
153
+
154
+ const queryOptions = {
155
+ orderBy: { [sortField]: order },
156
+ limit: pageSize,
157
+ offset,
158
+ };
159
+ if (where !== undefined) queryOptions.where = where;
120
160
 
121
- transactions = applyFilters(transactions, filters);
122
- const page = Number(pagination.page) || 1;
123
- const pageSize = Number(pagination.pageSize) || 10;
161
+ const [data, total] = await strapi.db
162
+ .query(TRANSACTION_UID)
163
+ .findWithCount(queryOptions);
124
164
 
125
- const total = transactions.length;
126
165
  const pageCount = Math.max(1, Math.ceil(total / pageSize));
127
-
128
- const validPage = Math.min(Math.max(1, page), pageCount);
129
-
130
- const start = (validPage - 1) * pageSize;
131
- const end = Math.min(start + pageSize, total);
132
-
133
- const paginatedData = start < total ? transactions.slice(start, end) : [];
166
+ const validPage = Math.min(page, pageCount);
134
167
 
135
168
  return {
136
- data: paginatedData,
169
+ data,
137
170
  pagination: {
138
171
  page: validPage,
139
172
  pageSize,
@@ -1,87 +1,81 @@
1
1
  "use strict";
2
2
 
3
- const crypto = require("crypto");
4
- const { getPluginStore, getSettings } = require("./settingsService");
3
+ const { getSettings } = require("./settingsService");
4
+ const { sanitizeSensitive } = require("../utils/sanitize");
5
5
 
6
- const verifyHash = (notificationData, portalKey) => {
7
- const {
8
- portalid = "",
9
- aid = "",
10
- txid = "",
11
- sequencenumber = "",
12
- price = "",
13
- currency = "",
14
- mode = "",
15
- } = notificationData;
6
+ const TRANSACTION_UID = "plugin::strapi-plugin-payone-provider.transaction";
16
7
 
17
- const hashString = `${portalid}${aid}${txid}${sequencenumber}${price}${currency}${mode}${portalKey}`;
18
- const expectedHash = crypto.createHash("md5").update(hashString).digest("hex");
8
+ const genreateUpdateData = (notificationData, existing, safeNotification) => {
9
+ const amount = String(notificationData.clearing_amount) || String(Math.round(parseFloat(notificationData.price) * 100)) || existing.amount;
10
+ const raw_request = {
11
+ ...existing.raw_request,
12
+ ...notificationData,
13
+ mode: notificationData.mode,
14
+ amount,
15
+ clearingtype: notificationData.clearingtype || existing.clearingtype,
16
+ }
17
+
18
+ return {
19
+ status: notificationData.transaction_status || existing.status,
20
+ currency: notificationData.currency || existing.currency,
21
+ reference: notificationData.reference || existing.reference,
22
+ amount,
23
+ body: {
24
+ ...existing.body,
25
+ status: notificationData.transaction_status,
26
+ amount,
27
+ raw_request: sanitizeSensitive(raw_request),
28
+ payone_notification_data: safeNotification,
29
+ },
30
+ };
31
+ }
32
+
33
+ const validateData = (notificationData, settings) => {
34
+ const isExist = (settings.portalid && settings.aid && settings.key) && (notificationData.portalid && notificationData.aid && notificationData.key);
35
+ const isMatch = notificationData.portalid === settings.portalid && notificationData.aid === settings.aid && notificationData.key === settings.key;
36
+
37
+ if (!isExist) {
38
+ console.log("[Payone TransactionStatus] Settings not found or payone data missing");
39
+ return false;
40
+ }
19
41
 
20
- return expectedHash.toLowerCase() === (notificationData.key || "").toLowerCase();
42
+ if (!isMatch) {
43
+ console.log("[Payone TransactionStatus] Payone data mismatch with settings");
44
+ return false;
45
+ }
46
+
47
+ return true;
21
48
  };
22
49
 
50
+
23
51
  const processTransactionStatus = async (strapi, notificationData) => {
24
52
  try {
25
53
  const settings = await getSettings(strapi);
26
- const txid = notificationData.txid;
27
-
28
- if (!settings || !settings.key) {
29
- console.log("[Payone TransactionStatus] Settings not found or key missing");
54
+ if (!validateData(notificationData, settings)) {
30
55
  return;
31
56
  }
32
57
 
33
- const isValid = verifyHash(notificationData, settings.key);
34
- if (!isValid) {
35
- console.log(`[Payone TransactionStatus] Hash verification failed txid: ${txid}`);
36
- return;
37
- }
58
+ const txid = notificationData.txid;
59
+ const existing = await strapi.db.query(TRANSACTION_UID).findOne({ where: { txid } });
38
60
 
39
- if (notificationData.portalid !== settings.portalid || notificationData.aid !== settings.aid) {
40
- console.log(`[Payone TransactionStatus] Portal ID or AID mismatch txid: ${txid}`);
61
+ if (!existing) {
62
+ console.log(`[Payone TransactionStatus] Transaction ${txid} not found. Notification ignored.`);
41
63
  return;
42
64
  }
43
65
 
44
- const pluginStore = getPluginStore(strapi);
45
- let transactionHistory = (await pluginStore.get({ key: "transactionHistory" })) || [];
46
-
47
- const transaction = transactionHistory.find((t) => t.txid === txid || t.id === txid);
48
-
49
- if (transaction) {
50
- Object.assign(transaction, {
51
- ...notificationData,
52
- status: notificationData?.transaction_status,
53
- txaction: notificationData?.txaction,
54
- txtime: notificationData?.txtime,
55
- sequencenumber: notificationData?.sequencenumber,
56
- balance: notificationData?.balance,
57
- receivable: notificationData?.receivable,
58
- price: notificationData?.price,
59
- amount: notificationData?.price ? parseFloat(notificationData?.price) * 100 : transaction?.amount,
60
- userid: notificationData?.userid,
61
- updated_at: new Date().toISOString(),
62
- body: {
63
- ...transaction?.body,
64
- ...notificationData,
65
- status: notificationData?.transaction_status
66
- }
67
- });
68
-
69
- await pluginStore.set({
70
- key: "transactionHistory",
71
- value: transactionHistory,
72
- });
73
-
74
- console.log(`[Payone TransactionStatus] Successfully updated transaction txid: ${txid}`);
75
- } else {
76
- console.log(`[Payone TransactionStatus] Transaction ${txid} not found in history. Notification ignored.`);
77
- }
66
+ const safeNotification = sanitizeSensitive({ ...notificationData });
67
+ const data = genreateUpdateData(notificationData, existing, safeNotification);
68
+ await strapi.db.query(TRANSACTION_UID).update({
69
+ where: { id: existing.id },
70
+ data,
71
+ });
78
72
 
73
+ console.log(`[Payone TransactionStatus] Successfully updated transaction txid: ${txid}`);
79
74
  } catch (error) {
80
75
  console.log(`[Payone TransactionStatus] Error processing notification: ${error}`);
81
76
  }
82
77
  };
83
78
 
84
79
  module.exports = {
85
- verifyHash,
86
80
  processTransactionStatus,
87
81
  };
@@ -2,38 +2,7 @@
2
2
 
3
3
  const crypto = require("crypto");
4
4
  const { normalizeCustomerId } = require("./normalize");
5
- const calculateKeyHash = (settings, params) => {
6
- const portalKey = settings.portalKey || settings.key;
7
- const portalid = String(settings.portalid || "");
8
- const aid = String(settings.aid || "");
9
- const mode = String(settings.mode || "test");
10
-
11
- const requestType = params.request || "";
12
-
13
- // For Capture and Refund operations
14
- if (requestType === "capture" || requestType === "refund") {
15
- const txid = String(params.txid || "");
16
- const sequencenumber = String(params.sequencenumber || "");
17
- const amount = String(params.amount || "");
18
- const currency = String(params.currency || "EUR");
19
-
20
- const hashString = `${portalid}${aid}${txid}${sequencenumber}${amount}${currency}${mode}${portalKey}`;
21
- return crypto.createHash("md5").update(hashString).digest("hex");
22
- }
23
-
24
- // For Preauthorization and Authorization operations
25
- if (requestType === "preauthorization" || requestType === "authorization") {
26
- const amount = String(params.amount || "");
27
- const currency = String(params.currency || "EUR");
28
- const reference = String(params.reference || "");
29
5
 
30
- const hashString = `${portalid}${aid}${amount}${currency}${reference}${mode}${portalKey}`;
31
- return crypto.createHash("md5").update(hashString).digest("hex");
32
- }
33
-
34
- const hashString = `${portalid}${aid}${mode}${portalKey}`;
35
- return crypto.createHash("md5").update(hashString).digest("hex");
36
- };
37
6
 
38
7
  const buildClientRequestParams = (settings, params, logger = null) => {
39
8
  const requestParams = {
@@ -51,7 +20,10 @@ const buildClientRequestParams = (settings, params, logger = null) => {
51
20
  logger
52
21
  );
53
22
 
54
- requestParams.key = calculateKeyHash(settings, requestParams);
23
+ requestParams.key = crypto
24
+ .createHash("md5")
25
+ .update(settings.portalKey || settings.key)
26
+ .digest("hex");
55
27
 
56
28
  const isCreditCard = requestParams.clearingtype === "cc";
57
29
  const enable3DSecure = settings.enable3DSecure !== false;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ const SENSITIVE_KEYS = [
4
+ "cardpan",
5
+ "cardexpiredate",
6
+ "cardcvc2",
7
+ "iban",
8
+ "bic",
9
+ "bankaccount",
10
+ "bankcode",
11
+ "bankaccountholder",
12
+ "key",
13
+ "accesscode",
14
+ "accessname",
15
+ "token",
16
+ "redirecturl",
17
+ "Identifier"
18
+ ];
19
+
20
+ const maskValue = (val) => {
21
+ if (typeof val !== "string") return val;
22
+ return "*".repeat(Math.min(val.length, 20));
23
+ };
24
+
25
+ const sanitizeSensitive = (obj) => {
26
+ if (!obj || typeof obj !== "object") return obj;
27
+ if (Array.isArray(obj)) return obj.map(sanitizeSensitive);
28
+ const out = {};
29
+ for (const [k, v] of Object.entries(obj)) {
30
+ const keyLower = k.toLowerCase();
31
+ if (SENSITIVE_KEYS.includes(keyLower) && v != null) {
32
+ out[k] = maskValue(String(v));
33
+ } else if (v != null && typeof v === "object" && !Array.isArray(v)) {
34
+ out[k] = sanitizeSensitive(v);
35
+ } else {
36
+ out[k] = v;
37
+ }
38
+ }
39
+ return out;
40
+ };
41
+
42
+ module.exports = { sanitizeSensitive };