strapi-plugin-payone-provider 1.6.6 → 4.6.9

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.
Files changed (24) hide show
  1. package/.well-known/.gitkeep +3 -0
  2. package/.well-known/apple-developer-merchant-id-domain-association.txt +1 -0
  3. package/admin/src/pages/App/components/AppTabs.jsx +37 -16
  4. package/admin/src/pages/App/components/ApplePayBtn.jsx +63 -28
  5. package/admin/src/pages/App/components/ConfigurationPanel.jsx +163 -10
  6. package/admin/src/pages/App/components/CustomerInfoPopover.jsx +147 -0
  7. package/admin/src/pages/App/components/DocsPanel.jsx +864 -194
  8. package/admin/src/pages/App/components/HistoryPanel.jsx +24 -237
  9. package/admin/src/pages/App/components/PaymentActionsPanel.jsx +8 -1
  10. package/admin/src/pages/App/components/RawDataPopover.jsx +113 -0
  11. package/admin/src/pages/App/components/StatusBadge.jsx +72 -14
  12. package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTableFilters.jsx +113 -0
  13. package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTablePagination.jsx +180 -0
  14. package/admin/src/pages/App/components/TransactionHistoryTable/index.jsx +225 -0
  15. package/admin/src/pages/App/components/paymentActions/ApplePayPanel.jsx +45 -1
  16. package/admin/src/pages/App/index.jsx +4 -0
  17. package/admin/src/pages/hooks/useSettings.js +26 -0
  18. package/admin/src/pages/hooks/useTransactionHistory.js +75 -11
  19. package/package.json +3 -2
  20. package/server/bootstrap.js +9 -3
  21. package/server/controllers/payone.js +8 -0
  22. package/server/services/applePayService.js +103 -93
  23. package/server/services/transactionService.js +104 -19
  24. package/server/utils/paymentMethodParams.js +67 -18
@@ -8,15 +8,20 @@ import {
8
8
  Stack,
9
9
  Typography,
10
10
  TextInput,
11
+ Select,
12
+ Option,
11
13
  Divider,
12
14
  } from "@strapi/design-system";
13
15
  import { Search } from "@strapi/icons";
14
16
  import TransactionHistoryItem from "./TransactionHistoryItem";
17
+ import TransactionHistoryTable from "./TransactionHistoryTable";
15
18
 
16
19
  const HistoryPanel = ({
17
20
  filters,
18
21
  onFilterChange,
19
22
  onFilterApply,
23
+ sorting,
24
+ onSort,
20
25
  isLoadingHistory,
21
26
  transactionHistory,
22
27
  paginatedTransactions,
@@ -25,7 +30,10 @@ const HistoryPanel = ({
25
30
  pageSize,
26
31
  onRefresh,
27
32
  onPageChange,
33
+ onPageSizeChange,
28
34
  }) => {
35
+ console.log(transactionHistory);
36
+
29
37
  return (
30
38
  <Box
31
39
  className="payment-container"
@@ -53,244 +61,23 @@ const HistoryPanel = ({
53
61
  View and filter all payment transactions processed through Payone
54
62
  </Typography>
55
63
  </Box>
56
- {/* Filters */}
57
- <Box>
58
- <Box marginBottom={4}>
59
- <Typography variant="delta" as="h3" fontWeight="bold">
60
- Transaction Filters
61
- </Typography>
62
- <Typography variant="pi" textColor="neutral600" marginTop={2}>
63
- Filter transactions by status, type, date range, and more
64
- </Typography>
65
- </Box>
66
- <Card className="payment-card">
67
- <CardBody padding={6}>
68
- <Stack spacing={4}>
69
- <Flex gap={4} wrap="wrap" alignItems="center">
70
- <TextInput
71
- label="Status"
72
- name="status"
73
- value={filters.status}
74
- onChange={(e) => onFilterChange("status", e.target.value)}
75
- placeholder="APPROVED, ERROR, etc."
76
- className="payment-input"
77
- style={{ flex: 1, minWidth: "200px" }}
78
- />
79
- <TextInput
80
- label="Request Type"
81
- name="request_type"
82
- value={filters.request_type}
83
- onChange={(e) =>
84
- onFilterChange("request_type", e.target.value)
85
- }
86
- placeholder="preauthorization, authorization, etc."
87
- className="payment-input"
88
- style={{ flex: 1, minWidth: "200px" }}
89
- />
90
- <TextInput
91
- label="Transaction ID"
92
- name="txid"
93
- value={filters.txid}
94
- onChange={(e) => onFilterChange("txid", e.target.value)}
95
- placeholder="Enter TxId"
96
- className="payment-input"
97
- style={{ flex: 1, minWidth: "200px" }}
98
- />
99
- <TextInput
100
- label="Reference"
101
- name="reference"
102
- value={filters.reference}
103
- onChange={(e) =>
104
- onFilterChange("reference", e.target.value)
105
- }
106
- placeholder="Enter reference"
107
- className="payment-input"
108
- style={{ flex: 1, minWidth: "200px" }}
109
- />
110
- <TextInput
111
- label="Date From"
112
- name="date_from"
113
- value={filters.date_from}
114
- onChange={(e) =>
115
- onFilterChange("date_from", e.target.value)
116
- }
117
- placeholder="YYYY-MM-DD"
118
- type="date"
119
- className="payment-input"
120
- style={{ flex: 1, minWidth: "200px" }}
121
- />
122
- <TextInput
123
- label="Date To"
124
- name="date_to"
125
- value={filters.date_to}
126
- onChange={(e) => onFilterChange("date_to", e.target.value)}
127
- placeholder="YYYY-MM-DD"
128
- type="date"
129
- className="payment-input"
130
- style={{ flex: 1, minWidth: "200px" }}
131
- />
132
- <Button
133
- variant="default"
134
- onClick={onFilterApply}
135
- loading={isLoadingHistory}
136
- startIcon={<Search />}
137
- className="payment-button payment-button-primary"
138
- >
139
- Apply Filters
140
- </Button>
141
- </Flex>
142
- </Stack>
143
- </CardBody>
144
- </Card>
145
- </Box>
146
-
147
- <Divider />
148
-
149
- {/* Transaction History */}
150
- <Box>
151
- <Box marginBottom={6}>
152
- <Flex
153
- justifyContent="space-between"
154
- alignItems="center"
155
- marginBottom={4}
156
- >
157
- <Box>
158
- <Typography variant="delta" as="h3" fontWeight="bold">
159
- Transaction History
160
- </Typography>
161
- <Typography variant="pi" textColor="neutral600" marginTop={2}>
162
- {transactionHistory.length} total transactions •{" "}
163
- {paginatedTransactions.length} on page {currentPage} of{" "}
164
- {totalPages}
165
- </Typography>
166
- </Box>
167
- <Button
168
- variant="default"
169
- onClick={onRefresh}
170
- loading={isLoadingHistory}
171
- startIcon={<Search />}
172
- size="S"
173
- className="payment-button payment-button-success"
174
- >
175
- Refresh
176
- </Button>
177
- </Flex>
178
- </Box>
179
-
180
- {isLoadingHistory ? (
181
- <Box padding={4} textAlign="center">
182
- <Typography>Loading transactions...</Typography>
183
- </Box>
184
- ) : transactionHistory.length === 0 ? (
185
- <Box padding={4} textAlign="center">
186
- <Typography textColor="neutral600">
187
- No transactions found
188
- </Typography>
189
- </Box>
190
- ) : (
191
- <Box>
192
- {paginatedTransactions.map((transaction) => (
193
- <TransactionHistoryItem
194
- key={transaction.id}
195
- transaction={transaction}
196
- />
197
- ))}
198
-
199
- {/* Pagination */}
200
- <Box paddingTop={6} paddingBottom={4}>
201
- <Card className="payment-card">
202
- <CardBody padding={4}>
203
- <Flex justifyContent="space-between" alignItems="center">
204
- {transactionHistory.length > pageSize &&
205
- totalPages > 1 ? (
206
- <Flex gap={3} alignItems="center">
207
- <Button
208
- variant="default"
209
- size="S"
210
- onClick={() =>
211
- onPageChange(Math.max(1, currentPage - 1))
212
- }
213
- disabled={currentPage === 1}
214
- className={`payment-button ${
215
- currentPage === 1 ? "" : "payment-button-success"
216
- }`}
217
- style={{
218
- background:
219
- currentPage === 1 ? "#f6f6f9" : undefined,
220
- color: currentPage === 1 ? "#666687" : undefined,
221
- }}
222
- >
223
- ← Previous
224
- </Button>
225
64
 
226
- <Box
227
- padding={2}
228
- background="#f6f6f9"
229
- borderRadius="6px"
230
- >
231
- <Typography
232
- variant="pi"
233
- textColor="neutral600"
234
- fontWeight="bold"
235
- >
236
- Page {currentPage} of {totalPages}
237
- </Typography>
238
- </Box>
239
-
240
- <Button
241
- variant="default"
242
- size="S"
243
- onClick={() =>
244
- onPageChange(
245
- Math.min(totalPages, currentPage + 1)
246
- )
247
- }
248
- disabled={currentPage === totalPages}
249
- className={`payment-button ${
250
- currentPage === totalPages
251
- ? ""
252
- : "payment-button-success"
253
- }`}
254
- style={{
255
- background:
256
- currentPage === totalPages
257
- ? "#f6f6f9"
258
- : undefined,
259
- color:
260
- currentPage === totalPages
261
- ? "#666687"
262
- : undefined,
263
- }}
264
- >
265
- Next →
266
- </Button>
267
- </Flex>
268
- ) : (
269
- <Typography
270
- variant="pi"
271
- textColor="neutral600"
272
- fontWeight="medium"
273
- >
274
- {transactionHistory.length <= pageSize
275
- ? "All transactions shown"
276
- : "No pagination needed"}
277
- </Typography>
278
- )}
279
- </Flex>
280
- </CardBody>
281
- </Card>
282
- <Typography
283
- variant="pi"
284
- textColor="neutral600"
285
- fontWeight="medium"
286
- >
287
- Showing {paginatedTransactions.length} of{" "}
288
- {transactionHistory.length} transactions
289
- </Typography>
290
- </Box>
291
- </Box>
292
- )}
293
- </Box>
65
+ {/* Transaction Table */}
66
+ <TransactionHistoryTable
67
+ transactions={paginatedTransactions}
68
+ isLoading={isLoadingHistory}
69
+ filters={filters}
70
+ onFilterChange={onFilterChange}
71
+ onFilterApply={onFilterApply}
72
+ sorting={sorting}
73
+ onSort={onSort}
74
+ currentPage={currentPage}
75
+ totalPages={totalPages}
76
+ pageSize={pageSize}
77
+ totalItems={transactionHistory.length}
78
+ onPageChange={onPageChange}
79
+ onPageSizeChange={onPageSizeChange}
80
+ />
294
81
 
295
82
  <Box paddingTop={4}>
296
83
  <Typography variant="sigma" textColor="neutral600">
@@ -51,7 +51,13 @@ const PaymentActionsPanel = ({
51
51
  const mode = (settings?.mode || "test").toLowerCase();
52
52
  const isLiveMode = mode === "live";
53
53
 
54
- if (isLiveMode) {
54
+ React.useEffect(() => {
55
+ if (isLiveMode && paymentMethod !== "apl") {
56
+ setPaymentMethod("apl");
57
+ }
58
+ }, [isLiveMode, paymentMethod]);
59
+
60
+ if (isLiveMode && paymentMethod !== "apl") {
55
61
  return (
56
62
  <Box
57
63
  style={{
@@ -99,6 +105,7 @@ const PaymentActionsPanel = ({
99
105
  cardcvc2={cardcvc2}
100
106
  setCardcvc2={setCardcvc2}
101
107
  onNavigateToConfig={onNavigateToConfig}
108
+ isLiveMode={isLiveMode}
102
109
  />
103
110
  );
104
111
  }
@@ -0,0 +1,113 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import { Box, Typography } from "@strapi/design-system";
3
+ import JsonView from "@uiw/react-json-view";
4
+
5
+ const RawDataPopover = ({ transaction, children }) => {
6
+ const [isOpen, setIsOpen] = useState(false);
7
+ const [position, setPosition] = useState("top"); // 'top' or 'bottom'
8
+ const popoverRef = useRef(null);
9
+ const buttonRef = useRef(null);
10
+
11
+ const calculatePosition = (buttonElement) => {
12
+ if (!buttonElement) return "top";
13
+
14
+ const buttonRect = buttonElement.getBoundingClientRect();
15
+ // Find the scrollable parent container
16
+ let scrollableParent = buttonElement.parentElement;
17
+ while (scrollableParent && scrollableParent !== document.body) {
18
+ const style = window.getComputedStyle(scrollableParent);
19
+ if (style.overflow === 'auto' || style.overflowY === 'auto' || style.overflow === 'scroll' || style.overflowY === 'scroll') {
20
+ const parentRect = scrollableParent.getBoundingClientRect();
21
+ const spaceAbove = buttonRect.top - parentRect.top;
22
+ const spaceBelow = parentRect.bottom - parentRect.bottom;
23
+ // RawDataPopover can be up to 500px tall, so we need more space
24
+ // If there's less space above than below (or less than 520px), show popover below
25
+ return spaceAbove < 520 || spaceAbove < spaceBelow ? "bottom" : "top";
26
+ }
27
+ scrollableParent = scrollableParent.parentElement;
28
+ }
29
+ // Fallback: use viewport space
30
+ const spaceAbove = buttonRect.top;
31
+ return spaceAbove < 520 ? "bottom" : "top";
32
+ };
33
+
34
+ const handleButtonClick = () => {
35
+ if (!isOpen && buttonRef.current) {
36
+ // Calculate position before opening
37
+ const newPosition = calculatePosition(buttonRef.current);
38
+ setPosition(newPosition);
39
+ }
40
+ setIsOpen(!isOpen);
41
+ };
42
+
43
+ useEffect(() => {
44
+ const handleClickOutside = (event) => {
45
+ if (
46
+ popoverRef.current &&
47
+ buttonRef.current &&
48
+ !popoverRef.current.contains(event.target) &&
49
+ !buttonRef.current.contains(event.target)
50
+ ) {
51
+ setIsOpen(false);
52
+ }
53
+ };
54
+
55
+ if (isOpen) {
56
+ document.addEventListener("mousedown", handleClickOutside);
57
+ return () => {
58
+ document.removeEventListener("mousedown", handleClickOutside);
59
+ };
60
+ }
61
+ }, [isOpen]);
62
+
63
+ if (!transaction) {
64
+ return children;
65
+ }
66
+
67
+ return (
68
+ <Box position="relative" style={{ display: "inline-block" }}>
69
+ <Box
70
+ ref={buttonRef}
71
+ onClick={handleButtonClick}
72
+ style={{ display: "inline-block" }}
73
+ >
74
+ {children}
75
+ </Box>
76
+ {isOpen && (
77
+ <Box
78
+ ref={popoverRef}
79
+ position="absolute"
80
+ zIndex={1000}
81
+ left={0}
82
+ {...(position === "top"
83
+ ? { bottom: "100%", marginBottom: 2 }
84
+ : { top: "100%", marginTop: 2 }
85
+ )}
86
+ padding={3}
87
+ background="neutral0"
88
+ hasRadius
89
+ style={{
90
+ minWidth: "400px",
91
+ maxWidth: "600px",
92
+ maxHeight: "500px",
93
+ overflow: "auto",
94
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
95
+ border: "1px solid var(--strapi-colors-neutral200)",
96
+ }}
97
+ >
98
+ <Box paddingBottom={2}>
99
+ <Typography variant="pi" fontWeight="bold" textColor="neutral800">
100
+ Raw Transaction Data
101
+ </Typography>
102
+ </Box>
103
+ <Box>
104
+ <JsonView value={transaction} style={{ fontSize: "12px" }} />
105
+ </Box>
106
+ </Box>
107
+ )}
108
+ </Box>
109
+ );
110
+ };
111
+
112
+ export default RawDataPopover;
113
+
@@ -1,23 +1,81 @@
1
- import React from "react";
2
- import { Badge } from "@strapi/design-system";
1
+ import React, { useState } from "react";
2
+ import { Badge, Box, Typography, Flex } from "@strapi/design-system";
3
+ import { ExclamationMarkCircle } from "@strapi/icons";
3
4
 
4
- const StatusBadge = ({ status }) => {
5
+ const StatusBadge = ({ status, transaction }) => {
6
+ const [isHovered, setIsHovered] = useState(false);
7
+
5
8
  const statusColors = {
6
- APPROVED: "success",
7
- PENDING: "warning",
8
- ERROR: "danger",
9
- FAILED: "danger",
10
- INVALID: "danger",
11
- REDIRECT: "secondary"
9
+ APPROVED: "success200",
10
+ PENDING: "warning200",
11
+ ERROR: "danger200",
12
+ CANCELLED: "warning100",
13
+ REDIRECTED: "success100",
14
+ CREATED: "success100"
12
15
  };
13
16
 
17
+ const getDisplayText = () => {
18
+ if (status === "ERROR" && transaction?.raw_response?.Error?.ErrorCode) {
19
+ return `${status} - ${transaction.raw_response.Error.ErrorCode}`;
20
+ }
21
+ return status;
22
+ };
23
+
24
+ const displayText = getDisplayText();
25
+ const errorMessage = status === "ERROR" && transaction?.raw_response?.Error?.ErrorMessage
26
+ ? transaction.raw_response.Error.ErrorMessage
27
+ : null;
28
+
29
+ const errorCode = status === "ERROR" && transaction?.raw_response?.Error?.ErrorCode
30
+ ? transaction.raw_response.Error.ErrorCode
31
+ : null;
32
+
33
+ const showExclamationIcon = status === "ERROR" && !errorCode && !errorMessage;
34
+
14
35
  return (
15
- <Badge
16
- textColor="neutral0"
17
- backgroundColor={statusColors[status] || "default"}
36
+ <Box
37
+ position="relative"
38
+ onMouseEnter={() => setIsHovered(true)}
39
+ onMouseLeave={() => setIsHovered(false)}
40
+ style={{ display: "inline-block", cursor: status === "ERROR" ? "pointer" : "default" }}
18
41
  >
19
- {status}
20
- </Badge>
42
+ <Flex gap={2} alignItems="center">
43
+ <Badge backgroundColor={statusColors[status] || "warning100"}>
44
+ {displayText}
45
+ </Badge>
46
+ {showExclamationIcon && (
47
+ <ExclamationMarkCircle color="danger500"
48
+ style={{
49
+ width: "16px",
50
+ height: "16px"
51
+ }}
52
+ />
53
+ )}
54
+ </Flex>
55
+ {isHovered && errorMessage && (
56
+ <Box
57
+ position="absolute"
58
+ zIndex={1000}
59
+ bottom="100%"
60
+ left="50%"
61
+ transform="translateX(-50%)"
62
+ marginBottom={2}
63
+ padding={3}
64
+ background="neutral900"
65
+ hasRadius
66
+ style={{
67
+ whiteSpace: "pre-line",
68
+ minWidth: "200px",
69
+ maxWidth: "300px",
70
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
71
+ }}
72
+ >
73
+ <Typography variant="pi" textColor="neutral0" style={{ fontSize: "12px" }}>
74
+ Error: {errorMessage}
75
+ </Typography>
76
+ </Box>
77
+ )}
78
+ </Box>
21
79
  );
22
80
  };
23
81
 
@@ -0,0 +1,113 @@
1
+ import React from "react";
2
+ import {
3
+ Box,
4
+ Flex,
5
+ Select,
6
+ Option,
7
+ Button,
8
+ TextInput,
9
+ Typography,
10
+ } from "@strapi/design-system";
11
+ import { Search } from "@strapi/icons";
12
+
13
+ const TransactionHistoryTableFilters = ({
14
+ filters,
15
+ onFilterChange,
16
+ onFilterApply,
17
+ isLoading,
18
+ }) => {
19
+ const handleDateFromClick = (e) => {
20
+ const input = e.target.closest('div')?.querySelector('input[type="date"]');
21
+ if (input) {
22
+ input.showPicker?.();
23
+ }
24
+ };
25
+
26
+ const handleDateToClick = (e) => {
27
+ const input = e.target.closest('div')?.querySelector('input[type="date"]');
28
+ if (input) {
29
+ input.showPicker?.();
30
+ }
31
+ };
32
+ return (
33
+ <Box marginBottom={2}>
34
+ <Flex gap={3} marginBottom={3} alignItems="center">
35
+ <TextInput
36
+ label="Search"
37
+ name="search"
38
+ value={filters?.search || ""}
39
+ onChange={(e) => onFilterChange("search", e.target.value)}
40
+ placeholder="Search by Status, Transaction ID, or Reference"
41
+ style={{ flex: 1, minWidth: "250px" }}
42
+ />
43
+ <Select
44
+ label="Status"
45
+ name="status"
46
+ value={filters?.status || ""}
47
+ onChange={(value) => onFilterChange("status", value)}
48
+ placeholder="All Statuses"
49
+ style={{ width: "180px", minWidth: "180px" }}
50
+ >
51
+ <Option value="">All Statuses</Option>
52
+ <Option value="APPROVED">APPROVED</Option>
53
+ <Option value="PENDING">PENDING</Option>
54
+ <Option value="ERROR">ERROR</Option>
55
+ <Option value="CANCELLED">CANCELLED</Option>
56
+ <Option value="REDIRECTED">REDIRECTED</Option>
57
+ <Option value="CREATED">CREATED</Option>
58
+ </Select>
59
+ <Select
60
+ label="Request Type"
61
+ name="request_type"
62
+ value={filters?.request_type || ""}
63
+ onChange={(value) => onFilterChange("request_type", value)}
64
+ placeholder="Select request type"
65
+ style={{ width: "220px", minWidth: "220px" }}
66
+ >
67
+ <Option value="">All Types</Option>
68
+ <Option value="preauthorization">Preauthorization</Option>
69
+ <Option value="authorization">Authorization</Option>
70
+ <Option value="capture">Capture</Option>
71
+ <Option value="refund">Refund</Option>
72
+ </Select>
73
+ </Flex>
74
+ <Flex gap={3} marginBottom={3} alignItems="center">
75
+ <Box onClick={handleDateFromClick} style={{ cursor: "pointer" }}>
76
+ <TextInput
77
+ label="Date From"
78
+ name="date_from"
79
+ value={filters?.date_from || ""}
80
+ onChange={(e) => onFilterChange("date_from", e.target.value)}
81
+ placeholder="YYYY-MM-DD"
82
+ type="date"
83
+ style={{ minWidth: "150px" }}
84
+ />
85
+ </Box>
86
+ <Box onClick={handleDateToClick} style={{ cursor: "pointer" }}>
87
+ <TextInput
88
+ label="Date To"
89
+ name="date_to"
90
+ value={filters?.date_to || ""}
91
+ onChange={(e) => onFilterChange("date_to", e.target.value)}
92
+ placeholder="YYYY-MM-DD"
93
+ type="date"
94
+ style={{ minWidth: "150px" }}
95
+ />
96
+ </Box>
97
+ <Typography variant="pi" textColor="neutral600" style={{ fontSize: "12px", marginTop: "20px" }}>
98
+ By default, the last 30 days are shown
99
+ </Typography>
100
+ </Flex>
101
+ <Button
102
+ variant="default"
103
+ onClick={onFilterApply}
104
+ loading={isLoading}
105
+ startIcon={<Search />}
106
+ >
107
+ Apply Filters
108
+ </Button>
109
+ </Box>
110
+ );
111
+ };
112
+
113
+ export default TransactionHistoryTableFilters;