strapi-plugin-payone-provider 1.1.2 → 1.2.4

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 (36) hide show
  1. package/README.md +1045 -330
  2. package/admin/src/index.js +4 -1
  3. package/admin/src/pages/App/components/AppHeader.js +37 -0
  4. package/admin/src/pages/App/components/AppTabs.js +126 -0
  5. package/admin/src/pages/App/components/ConfigurationPanel.js +34 -35
  6. package/admin/src/pages/App/components/GooglePaybutton.js +300 -0
  7. package/admin/src/pages/App/components/HistoryPanel.js +25 -38
  8. package/admin/src/pages/App/components/PaymentActionsPanel.js +95 -280
  9. package/admin/src/pages/App/components/TransactionHistoryItem.js +4 -1
  10. package/admin/src/pages/App/components/paymentActions/AuthorizationForm.js +93 -0
  11. package/admin/src/pages/App/components/paymentActions/CaptureForm.js +64 -0
  12. package/admin/src/pages/App/components/paymentActions/PaymentMethodSelector.js +52 -0
  13. package/admin/src/pages/App/components/paymentActions/PaymentResult.js +85 -0
  14. package/admin/src/pages/App/components/paymentActions/PreauthorizationForm.js +93 -0
  15. package/admin/src/pages/App/components/paymentActions/RefundForm.js +89 -0
  16. package/admin/src/pages/App/index.js +41 -465
  17. package/admin/src/pages/App/styles.css +294 -0
  18. package/admin/src/pages/constants/paymentConstants.js +37 -0
  19. package/admin/src/pages/hooks/usePaymentActions.js +271 -0
  20. package/admin/src/pages/hooks/useSettings.js +111 -0
  21. package/admin/src/pages/hooks/useTransactionHistory.js +87 -0
  22. package/admin/src/pages/utils/api.js +10 -0
  23. package/admin/src/pages/utils/injectGooglePayScript.js +31 -0
  24. package/admin/src/pages/utils/paymentUtils.js +113 -13
  25. package/package.json +1 -1
  26. package/server/controllers/payone.js +71 -64
  27. package/server/routes/index.js +17 -0
  28. package/server/services/paymentService.js +214 -0
  29. package/server/services/payone.js +25 -648
  30. package/server/services/settingsService.js +59 -0
  31. package/server/services/testConnectionService.js +190 -0
  32. package/server/services/transactionService.js +114 -0
  33. package/server/utils/normalize.js +51 -0
  34. package/server/utils/paymentMethodParams.js +126 -0
  35. package/server/utils/requestBuilder.js +110 -0
  36. package/server/utils/responseParser.js +80 -0
@@ -0,0 +1,294 @@
1
+ /* Payment System UI - Minimalistic & Professional Styles */
2
+
3
+ /* ===== Global Animations ===== */
4
+ @keyframes fadeIn {
5
+ from {
6
+ opacity: 0;
7
+ transform: translateY(8px);
8
+ }
9
+
10
+ to {
11
+ opacity: 1;
12
+ transform: translateY(0);
13
+ }
14
+ }
15
+
16
+ @keyframes slideIn {
17
+ from {
18
+ opacity: 0;
19
+ transform: translateX(-12px);
20
+ }
21
+
22
+ to {
23
+ opacity: 1;
24
+ transform: translateX(0);
25
+ }
26
+ }
27
+
28
+ @keyframes pulse {
29
+
30
+ 0%,
31
+ 100% {
32
+ opacity: 1;
33
+ }
34
+
35
+ 50% {
36
+ opacity: 0.7;
37
+ }
38
+ }
39
+
40
+ @keyframes shimmer {
41
+ 0% {
42
+ background-position: -1000px 0;
43
+ }
44
+
45
+ 100% {
46
+ background-position: 1000px 0;
47
+ }
48
+ }
49
+
50
+ /* ===== Container Styles ===== */
51
+
52
+ /* ===== Card Styles ===== */
53
+ .payment-card {
54
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
55
+ }
56
+
57
+
58
+ /* ===== Button Styles ===== */
59
+ .payment-button {
60
+ border-radius: 8px;
61
+ font-weight: 500;
62
+ letter-spacing: 0.01em;
63
+ border: none !important;
64
+ }
65
+
66
+ .payment-button-primary {
67
+ background: linear-gradient(135deg, #4945ff 0%, #7b3af4 100%);
68
+ color: white;
69
+ }
70
+
71
+
72
+ .payment-button-success {
73
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
74
+ color: white;
75
+ }
76
+
77
+ .payment-button-primary:hover {
78
+ background: linear-gradient(135deg, #4945ff 0%, #7b3af4 100%);
79
+ color: white;
80
+ }
81
+
82
+
83
+ .payment-button-success:hover {
84
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
85
+ color: white;
86
+ }
87
+
88
+
89
+ /* ===== Input Styles ===== */
90
+ .payment-input {
91
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
92
+ border-radius: 8px;
93
+ }
94
+
95
+ .payment-input:focus-within {
96
+ border-color: #4945ff;
97
+ box-shadow: 0 0 0 3px rgba(73, 69, 255, 0.1);
98
+ }
99
+
100
+ /* ===== Tab Styles ===== */
101
+ .payment-tab {
102
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
103
+ position: relative;
104
+ padding: 12px 20px;
105
+ font-weight: 500;
106
+ color: #6b7280;
107
+ }
108
+
109
+ .payment-tab:hover {
110
+ color: #4945ff;
111
+ }
112
+
113
+ .payment-tab-active {
114
+ color: #4945ff;
115
+ font-weight: 600;
116
+ }
117
+
118
+ .payment-tab-active::after {
119
+ content: '';
120
+ position: absolute;
121
+ bottom: -2px;
122
+ left: 0;
123
+ right: 0;
124
+ height: 2px;
125
+ background: linear-gradient(90deg, #4945ff 0%, #7b3af4 100%);
126
+ animation: slideIn 0.3s ease-out;
127
+ }
128
+
129
+ /* ===== Form Section Styles ===== */
130
+ .payment-form-section {
131
+ animation: fadeIn 0.5s ease-out;
132
+ padding: 24px;
133
+ margin-bottom: 20px;
134
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
135
+ }
136
+
137
+ .payment-form-section:hover {
138
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
139
+ }
140
+
141
+ .payment-form-title {
142
+ font-size: 14px;
143
+ font-weight: 600;
144
+ margin-bottom: 8px;
145
+ letter-spacing: -0.01em;
146
+ }
147
+
148
+ .payment-form-description {
149
+ font-size: 13px;
150
+ line-height: 1.5;
151
+ margin-top: 8px;
152
+ margin-bottom: 20px;
153
+ }
154
+
155
+ /* ===== Divider Styles ===== */
156
+ .payment-divider {
157
+ border: none;
158
+ height: 1px;
159
+ background: linear-gradient(90deg, transparent 0%, #e8e8ea 20%, #e8e8ea 80%, transparent 100%);
160
+ margin: 32px 0;
161
+ animation: fadeIn 0.4s ease-out;
162
+ }
163
+
164
+ /* ===== Status Badge Styles ===== */
165
+ .payment-status-badge {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ padding: 6px 12px;
169
+ border-radius: 6px;
170
+ font-size: 12px;
171
+ font-weight: 500;
172
+ letter-spacing: 0.02em;
173
+ animation: fadeIn 0.3s ease-out;
174
+ transition: all 0.2s ease;
175
+ }
176
+
177
+ .payment-status-badge:hover {
178
+ transform: scale(1.05);
179
+ }
180
+
181
+ /* ===== Loading States ===== */
182
+ .payment-loading {
183
+ position: relative;
184
+ overflow: hidden;
185
+ }
186
+
187
+ .payment-loading::after {
188
+ content: '';
189
+ position: absolute;
190
+ top: 0;
191
+ left: -100%;
192
+ width: 100%;
193
+ height: 100%;
194
+ background: linear-gradient(90deg,
195
+ transparent,
196
+ rgba(255, 255, 255, 0.4),
197
+ transparent);
198
+ animation: shimmer 1.5s infinite;
199
+ }
200
+
201
+ /* ===== Alert/Notification Styles ===== */
202
+ .payment-alert {
203
+ animation: slideIn 0.3s ease-out;
204
+ transition: all 0.2s ease;
205
+ }
206
+
207
+ .payment-alert:hover {
208
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
209
+ }
210
+
211
+ /* ===== Result Card Styles ===== */
212
+ .payment-result-card {
213
+ animation: fadeIn 0.5s ease-out;
214
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
215
+ }
216
+
217
+ .payment-result-card:hover {
218
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
219
+ transform: translateY(-2px);
220
+ }
221
+
222
+ /* ===== Transaction Item Styles ===== */
223
+ .payment-transaction-item {
224
+ margin-bottom: 12px;
225
+ animation: fadeIn 0.4s ease-out;
226
+ cursor: pointer;
227
+ }
228
+
229
+ .payment-transaction-item:hover {
230
+ box-shadow: 0 4px 12px rgba(73, 69, 255, 0.1);
231
+ }
232
+
233
+ /* ===== Typography Styles ===== */
234
+ .payment-title {
235
+ font-size: 24px;
236
+ font-weight: 700;
237
+ letter-spacing: -0.02em;
238
+ margin-bottom: 8px;
239
+ animation: fadeIn 0.4s ease-out;
240
+ }
241
+
242
+ .payment-subtitle {
243
+ font-size: 15px;
244
+ line-height: 1.6;
245
+ animation: fadeIn 0.5s ease-out;
246
+ }
247
+
248
+ .payment-section-title {
249
+ font-size: 16px;
250
+ font-weight: 600;
251
+ margin-bottom: 4px;
252
+ letter-spacing: -0.01em;
253
+ }
254
+
255
+ /* ===== Grid & Layout ===== */
256
+ .payment-grid {
257
+ display: grid;
258
+ gap: 20px;
259
+ animation: fadeIn 0.5s ease-out;
260
+ }
261
+
262
+ .payment-grid-2 {
263
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
264
+ }
265
+
266
+ .payment-grid-3 {
267
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
268
+ }
269
+
270
+ /* ===== Smooth Transitions ===== */
271
+
272
+ /* ===== Focus States ===== */
273
+ .payment-focus:focus-visible {
274
+ outline: 2px solid #4945ff;
275
+ outline-offset: 2px;
276
+ border-radius: 6px;
277
+ }
278
+
279
+ /* ===== Responsive ===== */
280
+ @media (max-width: 768px) {
281
+ .payment-container {
282
+ border-radius: 12px;
283
+ padding: 16px;
284
+ }
285
+
286
+ .payment-form-section {
287
+ padding: 16px;
288
+ }
289
+
290
+ .payment-grid-2,
291
+ .payment-grid-3 {
292
+ grid-template-columns: 1fr;
293
+ }
294
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Default payment data for testing
3
+ * Used across all payment operations
4
+ *
5
+ * Note: successurl, errorurl, and backurl are only required for:
6
+ * - 3D Secure enabled credit card payments
7
+ * - Redirect-based payment methods (PayPal, Google Pay, Apple Pay, Sofort)
8
+ *
9
+ * For non-redirect payments (3DS disabled credit card, SEPA), these URLs are optional
10
+ */
11
+ export const DEFAULT_PAYMENT_DATA = {
12
+ firstname: "John",
13
+ lastname: "Doe",
14
+ street: "Test Street 123",
15
+ zip: "12345",
16
+ city: "Test City",
17
+ country: "DE",
18
+ email: "test@example.com",
19
+ salutation: "Herr",
20
+ gender: "m",
21
+ telephonenumber: "01752345678",
22
+ ip: "127.0.0.1",
23
+ customer_is_present: "yes",
24
+ language: "de"
25
+ // Note: successurl, errorurl, backurl are added conditionally based on 3DS setting
26
+ };
27
+
28
+ /**
29
+ * Default currency
30
+ */
31
+ export const DEFAULT_CURRENCY = "EUR";
32
+
33
+ /**
34
+ * Default page size for pagination
35
+ */
36
+ export const DEFAULT_PAGE_SIZE = 10;
37
+
@@ -0,0 +1,271 @@
1
+ import { useState, useEffect } from "react";
2
+ import { useNotification } from "@strapi/helper-plugin";
3
+ import payoneRequests from "../utils/api";
4
+ import {
5
+ getPreauthorizationParams,
6
+ getAuthorizationParams,
7
+ getCaptureParams,
8
+ getRefundParams
9
+ } from "../utils/paymentUtils";
10
+ import { DEFAULT_PAYMENT_DATA } from "../constants/paymentConstants";
11
+
12
+ const usePaymentActions = () => {
13
+ const toggleNotification = useNotification();
14
+
15
+ // Load settings to get enable3DSecure value
16
+ const [settings, setSettings] = useState({ enable3DSecure: false });
17
+
18
+ useEffect(() => {
19
+ const loadSettings = async () => {
20
+ try {
21
+ const response = await payoneRequests.getSettings();
22
+ if (response?.data) {
23
+ setSettings(response.data);
24
+ }
25
+ } catch (error) {
26
+ // Silent fail
27
+ }
28
+ };
29
+ loadSettings();
30
+ }, []);
31
+
32
+ // Payment form state
33
+ const [paymentAmount, setPaymentAmount] = useState("1000");
34
+ const [preauthReference, setPreauthReference] = useState("");
35
+ const [authReference, setAuthReference] = useState("");
36
+ const [captureTxid, setCaptureTxid] = useState("");
37
+ const [refundTxid, setRefundTxid] = useState("");
38
+ const [refundSequenceNumber, setRefundSequenceNumber] = useState("2");
39
+ const [refundReference, setRefundReference] = useState("");
40
+ const [paymentMethod, setPaymentMethod] = useState("cc");
41
+ const [captureMode, setCaptureMode] = useState("full");
42
+ const [googlePayToken, setGooglePayToken] = useState(null);
43
+
44
+ // Payment processing state
45
+ const [isProcessingPayment, setIsProcessingPayment] = useState(false);
46
+ const [paymentResult, setPaymentResult] = useState(null);
47
+ const [paymentError, setPaymentError] = useState(null);
48
+
49
+ const handlePaymentError = (error, defaultMessage) => {
50
+ const errorMessage =
51
+ error.response?.data?.data?.Error?.ErrorMessage ||
52
+ error.message ||
53
+ defaultMessage;
54
+ setPaymentError(errorMessage);
55
+ toggleNotification({
56
+ type: "warning",
57
+ message: defaultMessage
58
+ });
59
+ };
60
+
61
+ const handlePaymentSuccess = (message) => {
62
+ toggleNotification({
63
+ type: "success",
64
+ message
65
+ });
66
+ };
67
+
68
+ const handlePreauthorization = async (tokenParam = null) => {
69
+ setIsProcessingPayment(true);
70
+ setPaymentError(null);
71
+ setPaymentResult(null);
72
+ try {
73
+ const baseParams = {
74
+ amount: parseInt(paymentAmount),
75
+ currency: "EUR",
76
+ reference: preauthReference || `PREAUTH-${Date.now()}`,
77
+ enable3DSecure: settings.enable3DSecure !== false,
78
+ ...DEFAULT_PAYMENT_DATA
79
+ };
80
+
81
+ const needsRedirectUrls =
82
+ (paymentMethod === "cc" && settings.enable3DSecure !== false) ||
83
+ ["wlt", "gpp", "apl", "sb"].includes(paymentMethod);
84
+
85
+ if (needsRedirectUrls) {
86
+ const baseUrl = window.location.origin;
87
+ baseParams.successurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/success`;
88
+ baseParams.errorurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/error`;
89
+ baseParams.backurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/back`;
90
+ }
91
+
92
+ const tokenToUse = tokenParam || googlePayToken;
93
+ if (paymentMethod === "gpp" && tokenToUse) {
94
+ baseParams.googlePayToken = tokenToUse;
95
+ baseParams.settings = settings;
96
+ }
97
+
98
+ const params = getPreauthorizationParams(paymentMethod, baseParams);
99
+
100
+ const result = await payoneRequests.preauthorization(params);
101
+ const responseData = result?.data || result;
102
+
103
+
104
+ const redirectUrl = responseData.redirectUrl || responseData.redirecturl || responseData.RedirectUrl;
105
+ const needsRedirect = responseData.requires3DSRedirect ||
106
+ (responseData.status === "REDIRECT" && redirectUrl) ||
107
+ (responseData.Status === "REDIRECT" && redirectUrl);
108
+
109
+ if (needsRedirect && redirectUrl) {
110
+ window.location.href = redirectUrl;
111
+ return;
112
+ }
113
+
114
+ setPaymentResult(responseData);
115
+ handlePaymentSuccess("Preauthorization completed successfully");
116
+ } catch (error) {
117
+ handlePaymentError(error, "Preauthorization failed");
118
+ } finally {
119
+ setIsProcessingPayment(false);
120
+ }
121
+ };
122
+
123
+ const handleAuthorization = async (tokenParam = null) => {
124
+ setIsProcessingPayment(true);
125
+ setPaymentError(null);
126
+ setPaymentResult(null);
127
+
128
+ try {
129
+ const baseParams = {
130
+ amount: parseInt(paymentAmount),
131
+ currency: "EUR",
132
+ reference: authReference || `AUTH-${Date.now()}`,
133
+ enable3DSecure: settings.enable3DSecure !== false,
134
+ ...DEFAULT_PAYMENT_DATA
135
+ };
136
+
137
+ const needsRedirectUrls =
138
+ (paymentMethod === "cc" && settings.enable3DSecure !== false) ||
139
+ ["wlt", "gpp", "apl", "sb"].includes(paymentMethod);
140
+
141
+ if (needsRedirectUrls) {
142
+ const baseUrl = window.location.origin;
143
+ baseParams.successurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/success`;
144
+ baseParams.errorurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/error`;
145
+ baseParams.backurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/back`;
146
+ }
147
+
148
+ const tokenToUse = tokenParam || googlePayToken;
149
+ if (paymentMethod === "gpp" && tokenToUse) {
150
+ baseParams.googlePayToken = tokenToUse;
151
+ baseParams.settings = settings;
152
+ }
153
+
154
+ const params = getAuthorizationParams(paymentMethod, baseParams);
155
+
156
+ const result = await payoneRequests.authorization(params);
157
+ const responseData = result?.data || result;
158
+
159
+ const redirectUrl = responseData.redirectUrl || responseData.redirecturl || responseData.RedirectUrl;
160
+ const needsRedirect = responseData.requires3DSRedirect ||
161
+ (responseData.status === "REDIRECT" && redirectUrl) ||
162
+ (responseData.Status === "REDIRECT" && redirectUrl);
163
+
164
+ if (needsRedirect && redirectUrl) {
165
+ window.location.href = redirectUrl;
166
+ return;
167
+ }
168
+
169
+ setPaymentResult(responseData);
170
+ handlePaymentSuccess("Authorization completed successfully");
171
+ } catch (error) {
172
+ handlePaymentError(error, "Authorization failed");
173
+ } finally {
174
+ setIsProcessingPayment(false);
175
+ }
176
+ };
177
+
178
+ const handleCapture = async () => {
179
+ if (!captureTxid.trim()) {
180
+ setPaymentError("Transaction ID is required for capture");
181
+ return;
182
+ }
183
+ setIsProcessingPayment(true);
184
+ setPaymentError(null);
185
+ setPaymentResult(null);
186
+ try {
187
+ const params = getCaptureParams(paymentMethod, {
188
+ txid: captureTxid,
189
+ amount: parseInt(paymentAmount),
190
+ currency: "EUR",
191
+ captureMode: captureMode,
192
+ sequencenumber: 1
193
+ });
194
+
195
+ const result = await payoneRequests.capture(params);
196
+ setPaymentResult(result);
197
+ handlePaymentSuccess("Capture completed successfully");
198
+ } catch (error) {
199
+ handlePaymentError(error, "Capture failed");
200
+ } finally {
201
+ setIsProcessingPayment(false);
202
+ }
203
+ };
204
+
205
+ const handleRefund = async () => {
206
+ if (!refundTxid.trim()) {
207
+ setPaymentError("Transaction ID is required for refund");
208
+ return;
209
+ }
210
+ setIsProcessingPayment(true);
211
+ setPaymentError(null);
212
+ setPaymentResult(null);
213
+ try {
214
+ const params = getRefundParams(paymentMethod, {
215
+ txid: refundTxid,
216
+ sequencenumber: parseInt(refundSequenceNumber),
217
+ amount: parseInt(paymentAmount),
218
+ currency: "EUR",
219
+ reference: refundReference || `REFUND-${Date.now()}`
220
+ });
221
+
222
+ const result = await payoneRequests.refund(params);
223
+ setPaymentResult(result);
224
+ handlePaymentSuccess("Refund completed successfully");
225
+ } catch (error) {
226
+ handlePaymentError(error, "Refund failed");
227
+ } finally {
228
+ setIsProcessingPayment(false);
229
+ }
230
+ };
231
+
232
+ return {
233
+ // Form state
234
+ paymentAmount,
235
+ setPaymentAmount,
236
+ preauthReference,
237
+ setPreauthReference,
238
+ authReference,
239
+ setAuthReference,
240
+ captureTxid,
241
+ setCaptureTxid,
242
+ refundTxid,
243
+ setRefundTxid,
244
+ refundSequenceNumber,
245
+ setRefundSequenceNumber,
246
+ refundReference,
247
+ setRefundReference,
248
+ paymentMethod,
249
+ setPaymentMethod,
250
+ captureMode,
251
+ setCaptureMode,
252
+
253
+ // Processing state
254
+ isProcessingPayment,
255
+ paymentResult,
256
+ paymentError,
257
+
258
+ // Handlers
259
+ handlePreauthorization,
260
+ handleAuthorization,
261
+ handleCapture,
262
+ handleRefund,
263
+
264
+ // Google Pay
265
+ googlePayToken,
266
+ setGooglePayToken
267
+ };
268
+ };
269
+
270
+ export default usePaymentActions;
271
+
@@ -0,0 +1,111 @@
1
+ import { useState, useEffect } from "react";
2
+ import { useNotification } from "@strapi/helper-plugin";
3
+ import payoneRequests from "../utils/api";
4
+
5
+ const useSettings = () => {
6
+ const toggleNotification = useNotification();
7
+ const [settings, setSettings] = useState({
8
+ aid: "",
9
+ portalid: "",
10
+ mid: "",
11
+ key: "",
12
+ mode: "test",
13
+ api_version: "3.10",
14
+ enable3DSecure: false
15
+ });
16
+ const [isLoading, setIsLoading] = useState(false);
17
+ const [isSaving, setIsSaving] = useState(false);
18
+ const [isTesting, setIsTesting] = useState(false);
19
+ const [testResult, setTestResult] = useState(null);
20
+
21
+ useEffect(() => {
22
+ loadSettings();
23
+ }, []);
24
+
25
+ const loadSettings = async () => {
26
+ setIsLoading(true);
27
+ try {
28
+ const response = await payoneRequests.getSettings();
29
+ if (response?.data) setSettings(response.data);
30
+ } catch (error) {
31
+ toggleNotification({
32
+ type: "warning",
33
+ message: "Failed to load settings"
34
+ });
35
+ } finally {
36
+ setIsLoading(false);
37
+ }
38
+ };
39
+
40
+ const handleInputChange = (field, value) => {
41
+ setSettings((prev) => ({ ...prev, [field]: value }));
42
+ };
43
+
44
+ const handleSave = async () => {
45
+ setIsSaving(true);
46
+ try {
47
+ await payoneRequests.updateSettings(settings);
48
+ toggleNotification({
49
+ type: "success",
50
+ message: "Settings saved successfully"
51
+ });
52
+ } catch (error) {
53
+ toggleNotification({
54
+ type: "warning",
55
+ message: "Failed to save settings"
56
+ });
57
+ } finally {
58
+ setIsSaving(false);
59
+ }
60
+ };
61
+
62
+ const handleTestConnection = async () => {
63
+ setIsTesting(true);
64
+ setTestResult(null);
65
+ try {
66
+ const response = await payoneRequests.testConnection();
67
+ if (response.data) {
68
+ const result = response.data;
69
+ setTestResult(result);
70
+ if (result.success !== undefined) {
71
+ toggleNotification({
72
+ type: Boolean(result.success) ? "success" : "warning",
73
+ message: result.message || "Test completed"
74
+ });
75
+ }
76
+ } else {
77
+ throw new Error("Invalid response format from server");
78
+ }
79
+ } catch (error) {
80
+ toggleNotification({
81
+ type: "warning",
82
+ message: "Failed to test connection"
83
+ });
84
+ setTestResult({
85
+ success: false,
86
+ message:
87
+ "Failed to test connection. Please check your network and server logs for details.",
88
+ details: {
89
+ errorCode: "NETWORK",
90
+ rawResponse: error.message || "Network error"
91
+ }
92
+ });
93
+ } finally {
94
+ setIsTesting(false);
95
+ }
96
+ };
97
+
98
+ return {
99
+ settings,
100
+ isLoading,
101
+ isSaving,
102
+ isTesting,
103
+ testResult,
104
+ handleInputChange,
105
+ handleSave,
106
+ handleTestConnection
107
+ };
108
+ };
109
+
110
+ export default useSettings;
111
+