strapi-plugin-payone-provider 4.6.11 → 4.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,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { Box, Button, Flex, Typography, Tr, Td } from "@strapi/design-system";
3
3
  import { Table } from "@strapi/helper-plugin";
4
- import { ChevronDown, ChevronUp } from "@strapi/icons";
4
+ import { ChevronDown, ChevronUp, ArrowUp, ArrowDown } from "@strapi/icons";
5
5
 
6
6
  import StatusBadge from "../StatusBadge";
7
7
  import FiltersPanel from "./FiltersPanel";
@@ -23,20 +23,50 @@ const TransactionTable = () => {
23
23
  handleFiltersChange,
24
24
  handleTransactionSelect,
25
25
  pagination,
26
+ sort,
27
+ handleSort,
26
28
  } = useTransactionHistory();
27
29
 
28
30
  const headers = [
29
- { name: "txid", label: "TxId" },
30
- { name: "reference", label: "Reference" },
31
- { name: "amount", label: "Amount" },
32
- { name: "paymentMethod", label: "Payment Method" },
33
- { name: "type", label: "Type" },
34
- { name: "status", label: "Status" },
35
- { name: "created_at", label: "Created At" },
36
- { name: "updated_at", label: "Updated At" },
37
- { name: "details", label: "Details" },
31
+ { name: "txid", label: "TxId", sortKey: "txid", sortable: true },
32
+ { name: "reference", label: "Reference", sortKey: "reference", sortable: true },
33
+ { name: "amount", label: "Amount", sortKey: "amount", sortable: true },
34
+ { name: "paymentMethod", label: "Payment Method", sortKey: null, sortable: false },
35
+ { name: "type", label: "Type", sortKey: "request_type", sortable: true },
36
+ { name: "status", label: "Status", sortKey: "status", sortable: true },
37
+ { name: "created_at", label: "Created At", sortKey: "createdAt", sortable: true },
38
+ { name: "updated_at", label: "Updated At", sortKey: "updatedAt", sortable: true },
39
+ { name: "details", label: "Details", sortKey: null, sortable: false },
38
40
  ];
39
41
 
42
+ const renderHeaderLabel = (header) => {
43
+ const isSorted = header.sortKey && sort.sort_by === header.sortKey;
44
+ const SortIcon = isSorted && sort.sort_order === "asc" ? ArrowUp : ArrowDown;
45
+
46
+ if (!header.sortable || !header.sortKey) {
47
+ return header.label;
48
+ }
49
+
50
+ return (
51
+ <Flex
52
+ alignItems="center"
53
+ gap={1}
54
+ onClick={() => handleSort(header.sortKey)}
55
+ style={{ cursor: "pointer", userSelect: "none" }}
56
+ title={`Sort by ${header.label}`}
57
+ >
58
+ <Typography variant="sigma" textColor="neutral600">
59
+ {header.label}
60
+ </Typography>
61
+ {isSorted ? (
62
+ <SortIcon width={12} height={12} />
63
+ ) : (
64
+ <Box width={12} height={12} aria-hidden />
65
+ )}
66
+ </Flex>
67
+ );
68
+ };
69
+
40
70
  return (
41
71
  <Flex direction="column" alignItems="stretch" gap={4} minHeight={"800px"}>
42
72
  <FiltersPanel
@@ -56,9 +86,9 @@ const TransactionTable = () => {
56
86
  {headers.map((header) => (
57
87
  <Table.HeaderCell
58
88
  fieldSchemaType="custom"
59
- isSortable={true}
89
+ isSortable={header.sortable}
60
90
  key={header.name}
61
- label={header.label}
91
+ label={renderHeaderLabel(header)}
62
92
  name={header.name}
63
93
  />
64
94
  ))}
@@ -122,12 +152,12 @@ const TransactionTable = () => {
122
152
  </Td>
123
153
  <Td>
124
154
  <Typography variant="pi" textColor="neutral600">
125
- {formatDate(transaction.created_at)}
155
+ {formatDate(transaction.createdAt ?? transaction.created_at)}
126
156
  </Typography>
127
157
  </Td>
128
158
  <Td>
129
159
  <Typography variant="pi" textColor="neutral600">
130
- {formatDate(transaction.updated_at)}
160
+ {formatDate(transaction.updatedAt ?? transaction.updated_at)}
131
161
  </Typography>
132
162
  </Td>
133
163
  <Td>
@@ -139,7 +139,7 @@ const TransactionDetails = ({ transaction }) => {
139
139
  </Typography>
140
140
  <Box marginTop={4}>
141
141
  <JsonView
142
- value={transaction?.body}
142
+ value={transaction?.body || transaction?.raw_request || {}}
143
143
  style={githubDarkTheme}
144
144
  displayDataTypes={false}
145
145
  enableClipboard
@@ -24,10 +24,12 @@ const useTransactionHistory = () => {
24
24
 
25
25
  const getQueryParams = () => {
26
26
  const searchParams = new URLSearchParams(location.search);
27
- const page = parseInt(searchParams.get('page') || '1', 10);
28
- const pageSize = parseInt(searchParams.get('pageSize') || String(PAGE_SIZE), 10);
27
+ const page = parseInt(searchParams.get("page") || "1", 10);
28
+ const pageSize = parseInt(searchParams.get("pageSize") || String(PAGE_SIZE), 10);
29
+ const sort_by = searchParams.get("sort_by") || "createdAt";
30
+ const sort_order = searchParams.get("sort_order") || "desc";
29
31
 
30
- return { page, pageSize };
32
+ return { page, pageSize, sort_by, sort_order };
31
33
  };
32
34
 
33
35
  const [filters, setFilters] = useState({
@@ -47,6 +49,11 @@ const useTransactionHistory = () => {
47
49
  total: 0,
48
50
  });
49
51
 
52
+ const [sort, setSort] = useState({
53
+ sort_by: initialQueryParams.sort_by,
54
+ sort_order: initialQueryParams.sort_order,
55
+ });
56
+
50
57
  const [transactionHistory, setTransactionHistory] = useState([]);
51
58
  const [isLoadingHistory, setIsLoadingHistory] = useState(false);
52
59
  const [selectedTransaction, setSelectedTransaction] = useState(null);
@@ -56,7 +63,9 @@ const useTransactionHistory = () => {
56
63
  try {
57
64
  const response = await payoneRequests.getTransactionHistory({
58
65
  filters,
59
- pagination
66
+ pagination,
67
+ sort_by: sort.sort_by,
68
+ sort_order: sort.sort_order,
60
69
  });
61
70
 
62
71
  if (response && response.data && response.pagination) {
@@ -94,6 +103,7 @@ const useTransactionHistory = () => {
94
103
  page: params.page,
95
104
  pageSize: params.pageSize,
96
105
  }));
106
+ setSort({ sort_by: params.sort_by, sort_order: params.sort_order });
97
107
  }, [location.search]);
98
108
 
99
109
  useEffect(() => {
@@ -107,6 +117,8 @@ const useTransactionHistory = () => {
107
117
  filters.date_to,
108
118
  pagination.page,
109
119
  pagination.pageSize,
120
+ sort.sort_by,
121
+ sort.sort_order,
110
122
  ]);
111
123
 
112
124
  const handleTransactionSelect = (transaction) => {
@@ -135,13 +147,23 @@ const useTransactionHistory = () => {
135
147
  ...prev,
136
148
  ...newFilters,
137
149
  }));
138
- // Reset to first page when filters change
139
150
  const updatedQuery = new URLSearchParams(location.search);
140
- updatedQuery.set('page', '1');
151
+ updatedQuery.set("page", "1");
141
152
  history.push({ search: updatedQuery.toString() });
142
153
  }
143
154
  };
144
155
 
156
+ const handleSort = (sortBy) => {
157
+ const nextOrder =
158
+ sort.sort_by === sortBy && sort.sort_order === "asc" ? "desc" : "asc";
159
+ setSort({ sort_by: sortBy, sort_order: nextOrder });
160
+ const updatedQuery = new URLSearchParams(location.search);
161
+ updatedQuery.set("sort_by", sortBy);
162
+ updatedQuery.set("sort_order", nextOrder);
163
+ updatedQuery.set("page", "1");
164
+ history.push({ search: updatedQuery.toString() });
165
+ };
166
+
145
167
  useEffect(() => {
146
168
  setSelectedTransaction(null);
147
169
  }, [filters, pagination.page]);
@@ -156,6 +178,8 @@ const useTransactionHistory = () => {
156
178
  handleFiltersChange,
157
179
  pagination,
158
180
  handlePaginationChange,
181
+ sort,
182
+ handleSort,
159
183
  };
160
184
  };
161
185
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-payone-provider",
3
- "version": "4.6.11",
3
+ "version": "4.6.12",
4
4
  "description": "Strapi plugin for Payone payment gateway integration",
5
5
  "license": "MIT",
6
6
  "maintainers": [
@@ -48,4 +48,4 @@
48
48
  "kind": "plugin",
49
49
  "displayName": "Strapi Payone Provider"
50
50
  }
51
- }
51
+ }
@@ -0,0 +1,5 @@
1
+ const transactionsContentType = require('./transactions/index.js')
2
+
3
+ module.exports = {
4
+ 'transaction': transactionsContentType
5
+ }
@@ -0,0 +1,5 @@
1
+ const schema = require('./schema.json');
2
+
3
+ module.exports = {
4
+ schema
5
+ }
@@ -0,0 +1,87 @@
1
+ {
2
+ "kind": "collectionType",
3
+ "collectionName": "transactions",
4
+ "info": {
5
+ "singularName": "transaction",
6
+ "pluralName": "transactions",
7
+ "displayName": "Transaction"
8
+ },
9
+ "options": {
10
+ "draftAndPublish": false
11
+ },
12
+ "pluginOptions": {
13
+ "content-manager": {
14
+ "visible": false
15
+ },
16
+ "content-type-builder": {
17
+ "visible": false
18
+ }
19
+ },
20
+ "attributes": {
21
+ "txid": {
22
+ "type": "string",
23
+ "required": false,
24
+ "default": "NO TXID"
25
+ },
26
+ "reference": {
27
+ "type": "string",
28
+ "required": false,
29
+ "default": "NO REFERENCE"
30
+ },
31
+ "invoiceid": {
32
+ "type": "string",
33
+ "required": false,
34
+ "default": "NO INVOICE ID"
35
+ },
36
+ "amount": {
37
+ "type": "string",
38
+ "required": false,
39
+ "default": "0"
40
+ },
41
+ "currency": {
42
+ "type": "string",
43
+ "required": false,
44
+ "default": "EUR"
45
+ },
46
+ "status": {
47
+ "type": "string",
48
+ "required": false,
49
+ "default": "unknown"
50
+ },
51
+ "error_code": {
52
+ "type": "string",
53
+ "required": false,
54
+ "default": "NO ERROR CODE"
55
+ },
56
+ "request_type": {
57
+ "type": "string",
58
+ "required": false,
59
+ "default": "unknown"
60
+ },
61
+ "error_message": {
62
+ "type": "string",
63
+ "required": false,
64
+ "default": "NO ERROR MESSAGE"
65
+ },
66
+ "customer_message": {
67
+ "type": "string",
68
+ "required": false,
69
+ "default": "NO CUSTOMER MESSAGE"
70
+ },
71
+ "body": {
72
+ "type": "json",
73
+ "required": false,
74
+ "default": {}
75
+ },
76
+ "raw_request": {
77
+ "type": "json",
78
+ "required": false,
79
+ "default": {}
80
+ },
81
+ "raw_response": {
82
+ "type": "json",
83
+ "required": false,
84
+ "default": {}
85
+ }
86
+ }
87
+ }
@@ -118,13 +118,15 @@ module.exports = ({ strapi }) => ({
118
118
 
119
119
  async getTransactionHistory(ctx) {
120
120
  try {
121
- const { filters = {}, pagination = {} } = ctx.query || {};
121
+ const { filters = {}, pagination = {}, sort_by, sort_order } = ctx.query || {};
122
122
  const page = parseInt(pagination.page || "1", 10);
123
123
  const pageSize = parseInt(pagination.pageSize || "10", 10);
124
124
 
125
125
  const result = await getPayoneService(strapi).getTransactionHistory({
126
126
  filters: filters || {},
127
- pagination: { page, pageSize }
127
+ pagination: { page, pageSize },
128
+ sort_by: sort_by || undefined,
129
+ sort_order: sort_order || undefined,
128
130
  });
129
131
  ctx.body = result
130
132
  } catch (error) {
package/server/index.js CHANGED
@@ -8,13 +8,14 @@ const controllers = require("./controllers");
8
8
  const routes = require("./routes");
9
9
  const services = require("./services");
10
10
  const policies = require("./policies");
11
-
11
+ const contentTypes = require("./content-types");
12
12
  module.exports = {
13
13
  register,
14
14
  bootstrap,
15
15
  destroy,
16
16
  config,
17
17
  controllers,
18
+ contentTypes,
18
19
  routes,
19
20
  services,
20
21
  policies
@@ -174,8 +174,6 @@ const validateApplePayMerchant = async (strapi, params) => {
174
174
 
175
175
  const sessionResponse = await initializeApplePaySession(strapi, params);
176
176
 
177
- // Extract add_paydata[applepay_payment_session] from response
178
- // Payone returns this in URL-encoded format: add_paydata[applepay_payment_session]=BASE64_STRING
179
177
  const applePaySessionBase64 =
180
178
  sessionResponse["add_paydata[applepay_payment_session]"] ||
181
179
  sessionResponse["add_paydata_applepay_payment_session"] ||
@@ -39,8 +39,13 @@ module.exports = ({ strapi }) => ({
39
39
  },
40
40
 
41
41
 
42
- async getTransactionHistory({ filters = {}, pagination = {} }) {
43
- return await transactionService.getTransactionHistory(strapi, { filters, pagination });
42
+ async getTransactionHistory({ filters = {}, pagination = {}, sort_by = "createdAt", sort_order = "desc" } = {}) {
43
+ return await transactionService.getTransactionHistory(strapi, {
44
+ filters,
45
+ pagination,
46
+ sort_by,
47
+ sort_order,
48
+ });
44
49
  },
45
50
 
46
51
  // Test connection
@@ -1,156 +1,169 @@
1
1
  "use strict";
2
2
 
3
- const { getPluginStore } = require("./settingsService");
3
+ const { sanitizeSensitive } = require("../utils/sanitize");
4
4
 
5
- const sanitizeRawRequest = (rawRequest) => {
6
- if (!rawRequest || typeof rawRequest !== "object") return rawRequest
7
- const sanitized = { ...rawRequest };
8
- const sensitiveFields = ["cardpan", "cardexpiredate", "cardcvc2"];
9
-
10
- sensitiveFields.forEach((field) => {
11
- if (sanitized[field] && typeof sanitized[field] === "string") {
12
- sanitized[field] = "*".repeat(sanitized[field].length);
13
- }
14
- });
15
-
16
- return sanitized;
17
- };
5
+ const TRANSACTION_UID = "plugin::strapi-plugin-payone-provider.transaction";
18
6
 
19
7
  const logTransaction = async (strapi, transactionData) => {
20
- const pluginStore = getPluginStore(strapi);
21
- let transactionHistory =
22
- (await pluginStore.get({ key: "transactionHistory" })) || [];
23
-
24
- const logEntry = {
25
- id: Date.now().toString(),
26
- timestamp: new Date().toISOString(),
27
- txid: transactionData.txid || null,
28
- reference: transactionData.reference || null,
29
- invoiceid: transactionData.invoiceid || null,
30
- request_type:
31
- transactionData.request_type || transactionData.request || "unknown",
32
- amount: transactionData.amount || null,
33
- currency: transactionData.currency || "EUR",
34
- status: transactionData.status || transactionData.Status || "unknown",
35
- error_code:
36
- transactionData.error_code || transactionData.Error?.ErrorCode || null,
37
- error_message:
38
- transactionData.error_message ||
39
- transactionData.Error?.ErrorMessage ||
40
- null,
41
- customer_message:
42
- transactionData.customer_message ||
43
- transactionData.Error?.CustomerMessage ||
44
- null,
45
- body: transactionData ? { ...transactionData, raw_request: sanitizeRawRequest(transactionData.raw_request) } : null,
46
- raw_request: transactionData.raw_request
47
- ? sanitizeRawRequest(transactionData.raw_request)
48
- : null,
49
- raw_response: sanitizeRawRequest(transactionData.raw_response) || transactionData,
50
- created_at: new Date().toISOString(),
51
- updated_at: new Date().toISOString()
52
- };
53
-
54
- transactionHistory.unshift(logEntry);
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
+ });
55
31
 
56
- if (transactionHistory.length > 5000) {
57
- transactionHistory = transactionHistory.slice(0, 5000);
32
+ return entry;
33
+ } catch (error) {
34
+ console.error("Failed to log transaction:", error);
58
35
  }
59
-
60
- await pluginStore.set({
61
- key: "transactionHistory",
62
- value: transactionHistory
63
- });
64
-
65
- strapi.log.info("Transaction logged:", logEntry);
66
36
  };
67
37
 
68
- const applyFilters = (transactions, filters = {}) => {
69
- let result = [...transactions];
70
38
 
71
- if (filters.search && typeof filters.search === 'string' && filters.search.trim() !== '') {
72
- const search = filters.search.toLowerCase().trim();
73
- result = result.filter((t) => {
74
- const txid = (t.txid || "").toString().toLowerCase();
75
- const reference = (t.reference || "").toString().toLowerCase();
76
- return txid.includes(search) || reference.includes(search);
39
+ const buildWhereFromFilters = (filters = {}) => {
40
+ const conditions = [];
41
+
42
+ if (filters.search && typeof filters.search === "string" && filters.search.trim() !== "") {
43
+ const search = filters.search.trim();
44
+ conditions.push({
45
+ $or: [
46
+ { txid: { $containsi: search } },
47
+ { reference: { $containsi: search } },
48
+ ],
77
49
  });
78
50
  }
79
51
 
80
52
  if (filters.status) {
81
- result = result.filter(
82
- (t) => (t.status || "").toUpperCase() === filters.status.toUpperCase()
83
- );
53
+ conditions.push({ status: { $eqi: filters.status } });
84
54
  }
85
55
 
86
56
  if (filters.request_type) {
87
- result = result.filter((t) => t.request_type === filters.request_type);
88
- }
89
-
90
- if (filters.payment_method) {
91
- result = result.filter((t) => {
92
- const clearingtype = t.raw_request?.clearingtype;
93
- const wallettype = t.raw_request?.wallettype;
94
-
95
- switch (filters.payment_method) {
96
- case "credit_card":
97
- return clearingtype === "cc";
98
- case "paypal":
99
- return clearingtype === "wlt" && wallettype === "PPE";
100
- case "google_pay":
101
- return clearingtype === "wlt" && ["GPY", "GOOGLEPAY"].includes(wallettype);
102
- case "apple_pay":
103
- return clearingtype === "wlt" && ["APL", "APPLEPAY"].includes(wallettype);
104
- case "sofort":
105
- return clearingtype === "sb";
106
- case "sepa":
107
- return clearingtype === "elv";
108
- default:
109
- return true;
110
- }
111
- });
57
+ conditions.push({ request_type: filters.request_type });
112
58
  }
113
59
 
114
60
  if (filters.date_from) {
115
61
  const dateFrom = new Date(filters.date_from);
116
62
  dateFrom.setHours(0, 0, 0, 0);
117
- result = result.filter(
118
- (t) => new Date(t.timestamp || t.created_at) >= dateFrom
119
- );
63
+ conditions.push({ createdAt: { $gte: dateFrom.toISOString() } });
120
64
  }
121
65
 
122
66
  if (filters.date_to) {
123
67
  const dateTo = new Date(filters.date_to);
124
68
  dateTo.setHours(23, 59, 59, 999);
125
- result = result.filter(
126
- (t) => new Date(t.timestamp || t.created_at) <= dateTo
127
- );
69
+ conditions.push({ createdAt: { $lte: dateTo.toISOString() } });
128
70
  }
129
71
 
130
- return result;
131
- };
72
+ if (filters.payment_method) {
73
+ switch (filters.payment_method) {
74
+ case "credit_card":
75
+ conditions.push({ raw_request: { $containsi: '"clearingtype":"cc"' } });
76
+ break;
77
+ case "paypal":
78
+ conditions.push({
79
+ $and: [
80
+ { raw_request: { $containsi: '"clearingtype":"wlt"' } },
81
+ { raw_request: { $containsi: '"wallettype":"PPE"' } },
82
+ ],
83
+ });
84
+ break;
85
+ case "google_pay":
86
+ conditions.push({
87
+ $and: [
88
+ { raw_request: { $containsi: '"clearingtype":"wlt"' } },
89
+ {
90
+ $or: [
91
+ { raw_request: { $containsi: '"wallettype":"GPY"' } },
92
+ { raw_request: { $containsi: '"wallettype":"GOOGLEPAY"' } },
93
+ ],
94
+ },
95
+ ],
96
+ });
97
+ break;
98
+ case "apple_pay":
99
+ conditions.push({
100
+ $and: [
101
+ { raw_request: { $containsi: '"clearingtype":"wlt"' } },
102
+ {
103
+ $or: [
104
+ { raw_request: { $containsi: '"wallettype":"APL"' } },
105
+ { raw_request: { $containsi: '"wallettype":"APPLEPAY"' } },
106
+ ],
107
+ },
108
+ ],
109
+ });
110
+ break;
111
+ case "sofort":
112
+ conditions.push({ raw_request: { $containsi: '"clearingtype":"sb"' } });
113
+ break;
114
+ case "sepa":
115
+ conditions.push({ raw_request: { $containsi: '"clearingtype":"elv"' } });
116
+ break;
117
+ default:
118
+ break;
119
+ }
120
+ }
132
121
 
133
- const getTransactionHistory = async (strapi, { filters = {}, pagination = {} }) => {
134
- const pluginStore = getPluginStore(strapi);
122
+ if (conditions.length === 0) return undefined;
123
+ if (conditions.length === 1) return conditions[0];
124
+ return { $and: conditions };
125
+ };
135
126
 
136
- let transactions =
137
- (await pluginStore.get({ key: "transactionHistory" })) || [];
127
+ const ALLOWED_SORT_FIELDS = [
128
+ "txid",
129
+ "reference",
130
+ "amount",
131
+ "request_type",
132
+ "status",
133
+ "createdAt",
134
+ "updatedAt",
135
+ ];
136
+
137
+ const getTransactionHistory = async (
138
+ strapi,
139
+ { filters = {}, pagination = {}, sort_by, sort_order }
140
+ ) => {
141
+ const page = Math.max(1, Number(pagination.page) || 1);
142
+ const pageSize = Math.min(100, Math.max(1, Number(pagination.pageSize) || 10));
143
+ const offset = (page - 1) * pageSize;
144
+
145
+ const where = buildWhereFromFilters(filters);
146
+
147
+ const sortField =
148
+ sort_by && ALLOWED_SORT_FIELDS.includes(sort_by) ? sort_by : "createdAt";
149
+ const order = sort_order === "asc" ? "asc" : "desc";
150
+
151
+ const queryOptions = {
152
+ orderBy: { [sortField]: order },
153
+ limit: pageSize,
154
+ offset,
155
+ };
156
+ if (where !== undefined) queryOptions.where = where;
138
157
 
139
- transactions = applyFilters(transactions, filters);
140
- const page = Number(pagination.page) || 1;
141
- const pageSize = Number(pagination.pageSize) || 10;
158
+ const [data, total] = await strapi.db
159
+ .query(TRANSACTION_UID)
160
+ .findWithCount(queryOptions);
142
161
 
143
- const total = transactions.length;
144
162
  const pageCount = Math.max(1, Math.ceil(total / pageSize));
163
+ const validPage = Math.min(page, pageCount);
145
164
 
146
- const validPage = Math.min(Math.max(1, page), pageCount);
147
-
148
- const start = (validPage - 1) * pageSize;
149
- const end = Math.min(start + pageSize, total);
150
-
151
- const paginatedData = start < total ? transactions.slice(start, end) : [];
152
165
  return {
153
- data: paginatedData,
166
+ data,
154
167
  pagination: {
155
168
  page: validPage,
156
169
  pageSize,
@@ -1,24 +1,9 @@
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;
16
-
17
- const hashString = `${portalid}${aid}${txid}${sequencenumber}${price}${currency}${mode}${portalKey}`;
18
- const expectedHash = crypto.createHash("md5").update(hashString).digest("hex");
19
-
20
- return expectedHash.toLowerCase() === (notificationData.key || "").toLowerCase();
21
- };
6
+ const TRANSACTION_UID = "plugin::strapi-plugin-payone-provider.transaction";
22
7
 
23
8
  const processTransactionStatus = async (strapi, notificationData) => {
24
9
  try {
@@ -30,58 +15,49 @@ const processTransactionStatus = async (strapi, notificationData) => {
30
15
  return;
31
16
  }
32
17
 
33
- const isValid = verifyHash(notificationData, settings.key);
34
- if (!isValid) {
35
- console.log(`[Payone TransactionStatus] Hash verification failed txid: ${txid}`);
36
- return;
37
- }
38
-
39
18
  if (notificationData.portalid !== settings.portalid || notificationData.aid !== settings.aid) {
40
19
  console.log(`[Payone TransactionStatus] Portal ID or AID mismatch txid: ${txid}`);
41
20
  return;
42
21
  }
43
22
 
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.`);
23
+ const existing = await strapi.db.query(TRANSACTION_UID).findOne({ where: { txid } });
24
+ if (!existing) {
25
+ console.log(`[Payone TransactionStatus] Transaction ${txid} not found. Notification ignored.`);
26
+ return;
77
27
  }
78
28
 
29
+ const amount = notificationData.clearing_amount
30
+ ? String(notificationData.clearing_amount)
31
+ : notificationData.price
32
+ ? String(Math.round(parseFloat(notificationData.price) * 100))
33
+ : existing.amount;
34
+
35
+ const safeNotification = sanitizeSensitive({ ...notificationData });
36
+
37
+ const data = {
38
+ status: notificationData.transaction_status || existing.status,
39
+ currency: notificationData.currency || existing.currency,
40
+ reference: notificationData.reference || existing.reference,
41
+ amount,
42
+ body: {
43
+ ...existing.body,
44
+ status: notificationData.transaction_status,
45
+ amount,
46
+ payone_notification_data: safeNotification,
47
+ },
48
+ };
49
+
50
+ await strapi.db.query(TRANSACTION_UID).update({
51
+ where: { id: existing.id },
52
+ data,
53
+ });
54
+
55
+ console.log(`[Payone TransactionStatus] Successfully updated transaction txid: ${txid}`);
79
56
  } catch (error) {
80
57
  console.log(`[Payone TransactionStatus] Error processing notification: ${error}`);
81
58
  }
82
59
  };
83
60
 
84
61
  module.exports = {
85
- verifyHash,
86
62
  processTransactionStatus,
87
63
  };
@@ -0,0 +1,41 @@
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
+ ];
18
+
19
+ const maskValue = (val) => {
20
+ if (typeof val !== "string") return val;
21
+ return "*".repeat(Math.min(val.length, 20));
22
+ };
23
+
24
+ const sanitizeSensitive = (obj) => {
25
+ if (!obj || typeof obj !== "object") return obj;
26
+ if (Array.isArray(obj)) return obj.map(sanitizeSensitive);
27
+ const out = {};
28
+ for (const [k, v] of Object.entries(obj)) {
29
+ const keyLower = k.toLowerCase();
30
+ if (SENSITIVE_KEYS.includes(keyLower) && v != null) {
31
+ out[k] = maskValue(String(v));
32
+ } else if (v != null && typeof v === "object" && !Array.isArray(v)) {
33
+ out[k] = sanitizeSensitive(v);
34
+ } else {
35
+ out[k] = v;
36
+ }
37
+ }
38
+ return out;
39
+ };
40
+
41
+ module.exports = { sanitizeSensitive };