strapi-plugin-payone-provider 4.6.11 → 4.6.13
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/admin/src/pages/App/components/transaction-history/TransactionTable.jsx +44 -14
- package/admin/src/pages/App/components/transaction-history/details/TransactionDetails.jsx +1 -1
- package/admin/src/pages/hooks/useTransactionHistory.js +30 -6
- package/package.json +2 -2
- package/server/config/index.js +7 -1
- package/server/content-types/index.js +5 -0
- package/server/content-types/transactions/index.js +5 -0
- package/server/content-types/transactions/schema.json +87 -0
- package/server/controllers/payone.js +16 -13
- package/server/index.js +2 -1
- package/server/policies/is-payone-notification.js +11 -22
- package/server/services/applePayService.js +0 -2
- package/server/services/payone.js +7 -2
- package/server/services/transactionService.js +130 -117
- package/server/services/transactionStatusService.js +55 -61
- package/server/utils/sanitize.js +41 -0
|
@@ -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={
|
|
89
|
+
isSortable={header.sortable}
|
|
60
90
|
key={header.name}
|
|
61
|
-
label={header
|
|
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(
|
|
28
|
-
const pageSize = parseInt(searchParams.get(
|
|
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(
|
|
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.
|
|
3
|
+
"version": "4.6.13",
|
|
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
|
+
}
|
package/server/config/index.js
CHANGED
|
@@ -14,7 +14,13 @@ module.exports = {
|
|
|
14
14
|
merchantName: "",
|
|
15
15
|
displayName: "",
|
|
16
16
|
domainName: "",
|
|
17
|
-
merchantIdentifier: ""
|
|
17
|
+
merchantIdentifier: "",
|
|
18
|
+
enable3DSecure: false,
|
|
19
|
+
enableCreditCard: false,
|
|
20
|
+
enablePayPal: false,
|
|
21
|
+
enableGooglePay: false,
|
|
22
|
+
enableApplePay: false,
|
|
23
|
+
enableSepaDirectDebit: false
|
|
18
24
|
}
|
|
19
25
|
},
|
|
20
26
|
validator(config) {
|
|
@@ -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) {
|
|
@@ -256,19 +258,20 @@ module.exports = ({ strapi }) => ({
|
|
|
256
258
|
|
|
257
259
|
async handleTransactionStatus(ctx) {
|
|
258
260
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
console.log(`[Payone TransactionStatus] Responded TSOK`);
|
|
261
|
+
if (!ctx.state.payoneAllowed) {
|
|
262
|
+
console.log("[Payone] Notification ignored (policy failed)");
|
|
263
|
+
} else {
|
|
264
|
+
const notificationData = ctx.request.body || {};
|
|
265
|
+
await getPayoneService(strapi).processTransactionStatus(notificationData);
|
|
266
|
+
}
|
|
266
267
|
} catch (error) {
|
|
267
|
-
console.log("[Payone TransactionStatus] Error
|
|
268
|
-
ctx.status = 200;
|
|
269
|
-
ctx.body = "TSOK";
|
|
270
|
-
ctx.type = "text/plain";
|
|
268
|
+
console.log("[Payone TransactionStatus] Error:", error);
|
|
271
269
|
}
|
|
270
|
+
|
|
271
|
+
ctx.status = 200;
|
|
272
|
+
ctx.body = "TSOK";
|
|
273
|
+
ctx.type = "text/plain";
|
|
272
274
|
}
|
|
273
275
|
|
|
276
|
+
|
|
274
277
|
});
|
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
|
|
@@ -1,30 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = async (ctx, config, { strapi }) => {
|
|
1
|
+
module.exports = async (ctx) => {
|
|
4
2
|
const { request } = ctx;
|
|
5
|
-
const userAgent = request.header["user-agent"] || request.header["User-Agent"] || "";
|
|
6
|
-
const clientIp = request.ip || request.connection?.remoteAddress || "";
|
|
7
|
-
|
|
8
|
-
if (userAgent !== "PAYONE FinanceGate") {
|
|
9
|
-
console.log(`[Payone TransactionStatus] Invalid User-Agent: ${userAgent}, IP: ${clientIp}`);
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
3
|
|
|
4
|
+
const userAgent = request.headers["user-agent"] || "";
|
|
5
|
+
const clientIp =
|
|
6
|
+
request.headers["x-payone-client-ip"]?.trim() ||
|
|
7
|
+
request.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
|
|
8
|
+
request.ip ||
|
|
9
|
+
"";
|
|
13
10
|
|
|
14
|
-
const
|
|
15
|
-
if (ip.startsWith("185.60.20.")) {
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
11
|
+
const isValid = userAgent === "PAYONE FinanceGate" && (clientIp.startsWith("185.60.20.") || clientIp === "54.246.203.105");
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
23
|
-
};
|
|
13
|
+
ctx.state.payoneAllowed = isValid;
|
|
24
14
|
|
|
25
|
-
if (!
|
|
26
|
-
console.log(
|
|
27
|
-
return false;
|
|
15
|
+
if (!isValid) {
|
|
16
|
+
console.log("[Payone] Policy failed", { userAgent, clientIp });
|
|
28
17
|
}
|
|
29
18
|
|
|
30
19
|
return true;
|
|
@@ -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, {
|
|
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 {
|
|
3
|
+
const { sanitizeSensitive } = require("../utils/sanitize");
|
|
4
4
|
|
|
5
|
-
const
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
transactionData.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
(t) => new Date(t.timestamp || t.created_at) <= dateTo
|
|
127
|
-
);
|
|
69
|
+
conditions.push({ createdAt: { $lte: dateTo.toISOString() } });
|
|
128
70
|
}
|
|
129
71
|
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
122
|
+
if (conditions.length === 0) return undefined;
|
|
123
|
+
if (conditions.length === 1) return conditions[0];
|
|
124
|
+
return { $and: conditions };
|
|
125
|
+
};
|
|
135
126
|
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
166
|
+
data,
|
|
154
167
|
pagination: {
|
|
155
168
|
page: validPage,
|
|
156
169
|
pageSize,
|
|
@@ -1,87 +1,81 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
3
|
+
const { getSettings } = require("./settingsService");
|
|
4
|
+
const { sanitizeSensitive } = require("../utils/sanitize");
|
|
5
5
|
|
|
6
|
-
const
|
|
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
|
-
|
|
18
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
34
|
-
|
|
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 (
|
|
40
|
-
console.log(`[Payone TransactionStatus]
|
|
61
|
+
if (!existing) {
|
|
62
|
+
console.log(`[Payone TransactionStatus] Transaction ${txid} not found. Notification ignored.`);
|
|
41
63
|
return;
|
|
42
64
|
}
|
|
43
65
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
};
|
|
@@ -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 };
|