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.
- 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/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 +4 -2
- package/server/index.js +2 -1
- 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 +34 -58
- 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.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,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, {
|
|
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,24 +1,9 @@
|
|
|
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;
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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 };
|