strapi-plugin-payone-provider 1.1.3 → 1.3.0

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 (38) hide show
  1. package/README.md +1156 -380
  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 +134 -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 +119 -280
  9. package/admin/src/pages/App/components/StatusBadge.js +3 -1
  10. package/admin/src/pages/App/components/TransactionHistoryItem.js +4 -1
  11. package/admin/src/pages/App/components/paymentActions/AuthorizationForm.js +122 -0
  12. package/admin/src/pages/App/components/paymentActions/CaptureForm.js +64 -0
  13. package/admin/src/pages/App/components/paymentActions/CardDetailsInput.js +189 -0
  14. package/admin/src/pages/App/components/paymentActions/PaymentMethodSelector.js +52 -0
  15. package/admin/src/pages/App/components/paymentActions/PaymentResult.js +148 -0
  16. package/admin/src/pages/App/components/paymentActions/PreauthorizationForm.js +122 -0
  17. package/admin/src/pages/App/components/paymentActions/RefundForm.js +89 -0
  18. package/admin/src/pages/App/index.js +41 -465
  19. package/admin/src/pages/App/styles.css +294 -0
  20. package/admin/src/pages/constants/paymentConstants.js +37 -0
  21. package/admin/src/pages/hooks/usePaymentActions.js +456 -0
  22. package/admin/src/pages/hooks/useSettings.js +111 -0
  23. package/admin/src/pages/hooks/useTransactionHistory.js +87 -0
  24. package/admin/src/pages/utils/api.js +10 -0
  25. package/admin/src/pages/utils/injectGooglePayScript.js +31 -0
  26. package/admin/src/pages/utils/paymentUtils.js +119 -15
  27. package/package.json +1 -1
  28. package/server/controllers/payone.js +71 -64
  29. package/server/routes/index.js +17 -0
  30. package/server/services/paymentService.js +271 -0
  31. package/server/services/payone.js +25 -648
  32. package/server/services/settingsService.js +59 -0
  33. package/server/services/testConnectionService.js +190 -0
  34. package/server/services/transactionService.js +114 -0
  35. package/server/utils/normalize.js +51 -0
  36. package/server/utils/paymentMethodParams.js +126 -0
  37. package/server/utils/requestBuilder.js +121 -0
  38. package/server/utils/responseParser.js +134 -0
@@ -0,0 +1,456 @@
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
+
35
+ // Generate reference automatically
36
+ const generateReference = (prefix = "REF") => {
37
+ const timestamp = Date.now().toString(36).toUpperCase();
38
+ const random = Math.random().toString(36).substring(2, 6).toUpperCase();
39
+ return `${prefix}-${timestamp}${random}`.slice(0, 20);
40
+ };
41
+
42
+ const [preauthReference, setPreauthReference] = useState(generateReference("PRE"));
43
+ const [authReference, setAuthReference] = useState(generateReference("AUTH"));
44
+ const [captureTxid, setCaptureTxid] = useState("");
45
+ const [refundTxid, setRefundTxid] = useState("");
46
+ const [refundSequenceNumber, setRefundSequenceNumber] = useState("2");
47
+ const [refundReference, setRefundReference] = useState("");
48
+ const [paymentMethod, setPaymentMethod] = useState("cc");
49
+ const [captureMode, setCaptureMode] = useState("full");
50
+ const [googlePayToken, setGooglePayToken] = useState(null);
51
+
52
+ // Card details for 3DS testing
53
+ const [cardtype, setCardtype] = useState("");
54
+ const [cardpan, setCardpan] = useState("");
55
+ const [cardexpiredate, setCardexpiredate] = useState("");
56
+ const [cardcvc2, setCardcvc2] = useState("");
57
+
58
+ // Payment processing state
59
+ const [isProcessingPayment, setIsProcessingPayment] = useState(false);
60
+ const [paymentResult, setPaymentResult] = useState(null);
61
+ const [paymentError, setPaymentError] = useState(null);
62
+
63
+ const handlePaymentError = (error, defaultMessage) => {
64
+ const errorMessage =
65
+ error.response?.data?.data?.Error?.ErrorMessage ||
66
+ error.message ||
67
+ defaultMessage;
68
+ setPaymentError(errorMessage);
69
+ toggleNotification({
70
+ type: "warning",
71
+ message: defaultMessage
72
+ });
73
+ };
74
+
75
+ const handlePaymentSuccess = (message) => {
76
+ toggleNotification({
77
+ type: "success",
78
+ message
79
+ });
80
+ };
81
+
82
+ const handlePreauthorization = async (tokenParam = null) => {
83
+ setIsProcessingPayment(true);
84
+ setPaymentError(null);
85
+ setPaymentResult(null);
86
+ try {
87
+ // Auto-generate reference if empty
88
+ const finalPreauthReference = preauthReference.trim() || generateReference("PRE");
89
+ if (!preauthReference.trim()) {
90
+ setPreauthReference(finalPreauthReference);
91
+ }
92
+
93
+ // Determine currency based on card type
94
+ // American Express typically requires USD, other cards use EUR
95
+ const currency = (paymentMethod === "cc" && cardtype === "A") ? "USD" : "EUR";
96
+
97
+ const baseParams = {
98
+ amount: parseInt(paymentAmount),
99
+ currency: currency,
100
+ reference: finalPreauthReference,
101
+ enable3DSecure: settings.enable3DSecure !== false,
102
+ ...DEFAULT_PAYMENT_DATA
103
+ };
104
+
105
+ // Add card details if credit card payment and 3DS enabled
106
+ if (paymentMethod === "cc" && settings.enable3DSecure !== false) {
107
+ if (cardtype) baseParams.cardtype = cardtype;
108
+ if (cardpan) baseParams.cardpan = cardpan;
109
+ if (cardexpiredate) baseParams.cardexpiredate = cardexpiredate;
110
+ if (cardcvc2) baseParams.cardcvc2 = cardcvc2;
111
+ }
112
+
113
+ const needsRedirectUrls =
114
+ (paymentMethod === "cc" && settings.enable3DSecure !== false) ||
115
+ ["wlt", "gpp", "apl", "sb"].includes(paymentMethod);
116
+
117
+ if (needsRedirectUrls) {
118
+ const baseUrl = window.location.origin;
119
+ baseParams.successurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/success`;
120
+ baseParams.errorurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/error`;
121
+ baseParams.backurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/back`;
122
+ }
123
+
124
+ const tokenToUse = tokenParam || googlePayToken;
125
+ if (paymentMethod === "gpp" && tokenToUse) {
126
+ baseParams.googlePayToken = tokenToUse;
127
+ baseParams.settings = settings;
128
+ }
129
+
130
+ const params = getPreauthorizationParams(paymentMethod, baseParams);
131
+
132
+ const result = await payoneRequests.preauthorization(params);
133
+ const responseData = result?.data || result;
134
+
135
+ // Log full response
136
+ console.log("Preauthorization Response:", responseData);
137
+ console.log("Response Status:", responseData.status || responseData.Status);
138
+ console.log("Response Error Code:", responseData.errorcode || responseData.errorCode || responseData.ErrorCode);
139
+ console.log("Response Error Message:", responseData.errormessage || responseData.errorMessage || responseData.ErrorMessage);
140
+ console.log("All redirect URL fields:", {
141
+ redirectUrl: responseData.redirectUrl,
142
+ redirecturl: responseData.redirecturl,
143
+ RedirectUrl: responseData.RedirectUrl,
144
+ redirect_url: responseData.redirect_url,
145
+ url: responseData.url,
146
+ Url: responseData.Url
147
+ });
148
+
149
+ const status = (responseData.status || responseData.Status || "").toUpperCase();
150
+ const errorCode = responseData.errorcode || responseData.errorCode || responseData.ErrorCode;
151
+ const errorMessage = responseData.errormessage || responseData.errorMessage || responseData.ErrorMessage;
152
+
153
+ // Check for 3DS required error (4219)
154
+ const requires3DSErrorCodes = ["4219", 4219];
155
+ const is3DSRequiredError = requires3DSErrorCodes.includes(errorCode);
156
+
157
+ // Check all possible redirect URL fields
158
+ const redirectUrl =
159
+ responseData.redirectUrl ||
160
+ responseData.redirecturl ||
161
+ responseData.RedirectUrl ||
162
+ responseData.redirect_url ||
163
+ responseData.url ||
164
+ responseData.Url ||
165
+ null;
166
+
167
+ // If 3DS required but no redirect URL, show helpful message
168
+ if (is3DSRequiredError && !redirectUrl) {
169
+ console.warn("3DS authentication required (Error 4219) but no redirect URL found in response");
170
+ console.log("Full response:", JSON.stringify(responseData, null, 2));
171
+ setPaymentError(
172
+ "3D Secure authentication required. Please check Payone configuration and ensure redirect URLs are properly set. Error: " +
173
+ (errorMessage || `Error code: ${errorCode}`)
174
+ );
175
+ setPaymentResult(responseData);
176
+ return;
177
+ }
178
+
179
+ // Check for other errors (but not 3DS required)
180
+ if ((status === "ERROR" || status === "INVALID" || errorCode) && !is3DSRequiredError) {
181
+ setPaymentError(
182
+ errorMessage ||
183
+ `Payment failed with error code: ${errorCode || "Unknown"}` ||
184
+ "Preauthorization failed"
185
+ );
186
+ setPaymentResult(responseData);
187
+ return;
188
+ }
189
+
190
+ const needsRedirect = responseData.requires3DSRedirect ||
191
+ (status === "REDIRECT" && redirectUrl) ||
192
+ (is3DSRequiredError && redirectUrl);
193
+
194
+ if (needsRedirect && redirectUrl) {
195
+ console.log("Redirecting to 3DS:", redirectUrl);
196
+ window.location.href = redirectUrl;
197
+ return;
198
+ }
199
+
200
+ setPaymentResult(responseData);
201
+
202
+ if (status === "APPROVED") {
203
+ handlePaymentSuccess("Preauthorization completed successfully");
204
+ } else {
205
+ handlePaymentError(
206
+ { message: `Unexpected status: ${status}` },
207
+ `Preauthorization completed with status: ${status}`
208
+ );
209
+ }
210
+ } catch (error) {
211
+ handlePaymentError(error, "Preauthorization failed");
212
+ } finally {
213
+ setIsProcessingPayment(false);
214
+ }
215
+ };
216
+
217
+ const handleAuthorization = async (tokenParam = null) => {
218
+ setIsProcessingPayment(true);
219
+ setPaymentError(null);
220
+ setPaymentResult(null);
221
+
222
+ try {
223
+ // Auto-generate reference if empty
224
+ const finalAuthReference = authReference.trim() || generateReference("AUTH");
225
+ if (!authReference.trim()) {
226
+ setAuthReference(finalAuthReference);
227
+ }
228
+
229
+ // Determine currency based on card type
230
+ // American Express typically requires USD, other cards use EUR
231
+ const currency = (paymentMethod === "cc" && cardtype === "A") ? "USD" : "EUR";
232
+
233
+ const baseParams = {
234
+ amount: parseInt(paymentAmount),
235
+ currency: currency,
236
+ reference: finalAuthReference,
237
+ enable3DSecure: settings.enable3DSecure !== false,
238
+ ...DEFAULT_PAYMENT_DATA
239
+ };
240
+
241
+ // Add card details if credit card payment and 3DS enabled
242
+ if (paymentMethod === "cc" && settings.enable3DSecure !== false) {
243
+ if (cardtype) baseParams.cardtype = cardtype;
244
+ if (cardpan) baseParams.cardpan = cardpan;
245
+ if (cardexpiredate) baseParams.cardexpiredate = cardexpiredate;
246
+ if (cardcvc2) baseParams.cardcvc2 = cardcvc2;
247
+ }
248
+
249
+ const needsRedirectUrls =
250
+ (paymentMethod === "cc" && settings.enable3DSecure !== false) ||
251
+ ["wlt", "gpp", "apl", "sb"].includes(paymentMethod);
252
+
253
+ if (needsRedirectUrls) {
254
+ const baseUrl = window.location.origin;
255
+ baseParams.successurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/success`;
256
+ baseParams.errorurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/error`;
257
+ baseParams.backurl = `${baseUrl}/admin/plugins/strapi-plugin-payone-provider/payment/back`;
258
+ }
259
+
260
+ const tokenToUse = tokenParam || googlePayToken;
261
+ if (paymentMethod === "gpp" && tokenToUse) {
262
+ baseParams.googlePayToken = tokenToUse;
263
+ baseParams.settings = settings;
264
+ }
265
+
266
+ const params = getAuthorizationParams(paymentMethod, baseParams);
267
+
268
+ const result = await payoneRequests.authorization(params);
269
+ const responseData = result?.data || result;
270
+
271
+ // Log full response
272
+ console.log("Authorization Response:", responseData);
273
+ console.log("Response Status:", responseData.status || responseData.Status);
274
+ console.log("Response Error Code:", responseData.errorcode || responseData.errorCode || responseData.ErrorCode);
275
+ console.log("Response Error Message:", responseData.errormessage || responseData.errorMessage || responseData.ErrorMessage);
276
+ console.log("All redirect URL fields:", {
277
+ redirectUrl: responseData.redirectUrl,
278
+ redirecturl: responseData.redirecturl,
279
+ RedirectUrl: responseData.RedirectUrl,
280
+ redirect_url: responseData.redirect_url,
281
+ url: responseData.url,
282
+ Url: responseData.Url
283
+ });
284
+
285
+ const status = (responseData.status || responseData.Status || "").toUpperCase();
286
+ const errorCode = responseData.errorcode || responseData.errorCode || responseData.ErrorCode;
287
+ const errorMessage = responseData.errormessage || responseData.errorMessage || responseData.ErrorMessage;
288
+
289
+ // Check for 3DS required error (4219)
290
+ const requires3DSErrorCodes = ["4219", 4219];
291
+ const is3DSRequiredError = requires3DSErrorCodes.includes(errorCode);
292
+
293
+ // Check all possible redirect URL fields
294
+ const redirectUrl =
295
+ responseData.redirectUrl ||
296
+ responseData.redirecturl ||
297
+ responseData.RedirectUrl ||
298
+ responseData.redirect_url ||
299
+ responseData.url ||
300
+ responseData.Url ||
301
+ null;
302
+
303
+ // If 3DS required but no redirect URL, show helpful message
304
+ if (is3DSRequiredError && !redirectUrl) {
305
+ console.warn("3DS authentication required (Error 4219) but no redirect URL found in response");
306
+ console.log("Full response:", JSON.stringify(responseData, null, 2));
307
+ setPaymentError(
308
+ "3D Secure authentication required. Please check Payone configuration and ensure redirect URLs are properly set. Error: " +
309
+ (errorMessage || `Error code: ${errorCode}`)
310
+ );
311
+ setPaymentResult(responseData);
312
+ return;
313
+ }
314
+
315
+ // Check for other errors (but not 3DS required)
316
+ if ((status === "ERROR" || status === "INVALID" || errorCode) && !is3DSRequiredError) {
317
+ setPaymentError(
318
+ errorMessage ||
319
+ `Payment failed with error code: ${errorCode || "Unknown"}` ||
320
+ "Authorization failed"
321
+ );
322
+ setPaymentResult(responseData);
323
+ return;
324
+ }
325
+
326
+ const needsRedirect = responseData.requires3DSRedirect ||
327
+ (status === "REDIRECT" && redirectUrl) ||
328
+ (is3DSRequiredError && redirectUrl);
329
+
330
+ if (needsRedirect && redirectUrl) {
331
+ console.log("Redirecting to 3DS:", redirectUrl);
332
+ window.location.href = redirectUrl;
333
+ return;
334
+ }
335
+
336
+ setPaymentResult(responseData);
337
+
338
+ if (status === "APPROVED") {
339
+ handlePaymentSuccess("Authorization completed successfully");
340
+ } else {
341
+ handlePaymentError(
342
+ { message: `Unexpected status: ${status}` },
343
+ `Authorization completed with status: ${status}`
344
+ );
345
+ }
346
+ } catch (error) {
347
+ handlePaymentError(error, "Authorization failed");
348
+ } finally {
349
+ setIsProcessingPayment(false);
350
+ }
351
+ };
352
+
353
+ const handleCapture = async () => {
354
+ if (!captureTxid.trim()) {
355
+ setPaymentError("Transaction ID is required for capture");
356
+ return;
357
+ }
358
+ setIsProcessingPayment(true);
359
+ setPaymentError(null);
360
+ setPaymentResult(null);
361
+ try {
362
+ const params = getCaptureParams(paymentMethod, {
363
+ txid: captureTxid,
364
+ amount: parseInt(paymentAmount),
365
+ currency: "EUR",
366
+ captureMode: captureMode,
367
+ sequencenumber: 1
368
+ });
369
+
370
+ const result = await payoneRequests.capture(params);
371
+ setPaymentResult(result);
372
+ handlePaymentSuccess("Capture completed successfully");
373
+ } catch (error) {
374
+ handlePaymentError(error, "Capture failed");
375
+ } finally {
376
+ setIsProcessingPayment(false);
377
+ }
378
+ };
379
+
380
+ const handleRefund = async () => {
381
+ if (!refundTxid.trim()) {
382
+ setPaymentError("Transaction ID is required for refund");
383
+ return;
384
+ }
385
+ setIsProcessingPayment(true);
386
+ setPaymentError(null);
387
+ setPaymentResult(null);
388
+ try {
389
+ const params = getRefundParams(paymentMethod, {
390
+ txid: refundTxid,
391
+ sequencenumber: parseInt(refundSequenceNumber),
392
+ amount: parseInt(paymentAmount),
393
+ currency: "EUR",
394
+ reference: refundReference || `REFUND-${Date.now()}`
395
+ });
396
+
397
+ const result = await payoneRequests.refund(params);
398
+ setPaymentResult(result);
399
+ handlePaymentSuccess("Refund completed successfully");
400
+ } catch (error) {
401
+ handlePaymentError(error, "Refund failed");
402
+ } finally {
403
+ setIsProcessingPayment(false);
404
+ }
405
+ };
406
+
407
+ return {
408
+ // Form state
409
+ paymentAmount,
410
+ setPaymentAmount,
411
+ preauthReference,
412
+ setPreauthReference,
413
+ authReference,
414
+ setAuthReference,
415
+ captureTxid,
416
+ setCaptureTxid,
417
+ refundTxid,
418
+ setRefundTxid,
419
+ refundSequenceNumber,
420
+ setRefundSequenceNumber,
421
+ refundReference,
422
+ setRefundReference,
423
+ paymentMethod,
424
+ setPaymentMethod,
425
+ captureMode,
426
+ setCaptureMode,
427
+
428
+ // Processing state
429
+ isProcessingPayment,
430
+ paymentResult,
431
+ paymentError,
432
+
433
+ // Handlers
434
+ handlePreauthorization,
435
+ handleAuthorization,
436
+ handleCapture,
437
+ handleRefund,
438
+
439
+ // Google Pay
440
+ googlePayToken,
441
+ setGooglePayToken,
442
+
443
+ // Card details for 3DS
444
+ cardtype,
445
+ setCardtype,
446
+ cardpan,
447
+ setCardpan,
448
+ cardexpiredate,
449
+ setCardexpiredate,
450
+ cardcvc2,
451
+ setCardcvc2
452
+ };
453
+ };
454
+
455
+ export default usePaymentActions;
456
+
@@ -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
+
@@ -0,0 +1,87 @@
1
+ import { useState, useEffect } from "react";
2
+ import { useNotification } from "@strapi/helper-plugin";
3
+ import payoneRequests from "../utils/api";
4
+
5
+ const PAGE_SIZE = 10;
6
+
7
+ const useTransactionHistory = () => {
8
+ const toggleNotification = useNotification();
9
+ const [transactionHistory, setTransactionHistory] = useState([]);
10
+ const [isLoadingHistory, setIsLoadingHistory] = useState(false);
11
+ const [selectedTransaction, setSelectedTransaction] = useState(null);
12
+ const [currentPage, setCurrentPage] = useState(1);
13
+ const [filters, setFilters] = useState({
14
+ status: "",
15
+ request_type: "",
16
+ txid: "",
17
+ reference: "",
18
+ date_from: "",
19
+ date_to: ""
20
+ });
21
+
22
+ useEffect(() => {
23
+ loadTransactionHistory();
24
+ }, []);
25
+
26
+ const loadTransactionHistory = async () => {
27
+ setIsLoadingHistory(true);
28
+ try {
29
+ const result = await payoneRequests.getTransactionHistory(filters);
30
+ setTransactionHistory(result.data || []);
31
+ setCurrentPage(1);
32
+ } catch (error) {
33
+ toggleNotification({
34
+ type: "warning",
35
+ message: "Failed to load transaction history"
36
+ });
37
+ } finally {
38
+ setIsLoadingHistory(false);
39
+ }
40
+ };
41
+
42
+ const handleFilterChange = (field, value) => {
43
+ setFilters((prev) => ({ ...prev, [field]: value }));
44
+ };
45
+
46
+ const handleFilterApply = () => {
47
+ loadTransactionHistory();
48
+ };
49
+
50
+ const handleTransactionSelect = (transaction) => {
51
+ if (selectedTransaction?.id === transaction?.id) {
52
+ setSelectedTransaction(null);
53
+ } else {
54
+ setSelectedTransaction(transaction);
55
+ }
56
+ };
57
+
58
+ const handlePageChange = (page) => {
59
+ setCurrentPage(page);
60
+ setSelectedTransaction(null);
61
+ };
62
+
63
+ // Pagination calculations
64
+ const totalPages = Math.ceil(transactionHistory.length / PAGE_SIZE);
65
+ const startIndex = (currentPage - 1) * PAGE_SIZE;
66
+ const endIndex = startIndex + PAGE_SIZE;
67
+ const paginatedTransactions = transactionHistory.slice(startIndex, endIndex);
68
+
69
+ return {
70
+ transactionHistory,
71
+ paginatedTransactions,
72
+ isLoadingHistory,
73
+ selectedTransaction,
74
+ filters,
75
+ currentPage,
76
+ totalPages,
77
+ pageSize: PAGE_SIZE,
78
+ handleFilterChange,
79
+ handleFilterApply,
80
+ handleTransactionSelect,
81
+ handlePageChange,
82
+ loadTransactionHistory
83
+ };
84
+ };
85
+
86
+ export default useTransactionHistory;
87
+
@@ -69,6 +69,16 @@ const payoneRequests = {
69
69
  "Content-Type": "application/json"
70
70
  }
71
71
  });
72
+ },
73
+
74
+ handle3DSCallback: (data) => {
75
+ return request(`/${pluginId}/3ds-callback`, {
76
+ method: "POST",
77
+ body: data,
78
+ headers: {
79
+ "Content-Type": "application/json"
80
+ }
81
+ });
72
82
  }
73
83
  };
74
84
 
@@ -0,0 +1,31 @@
1
+ export const injectGooglePayScript = () => {
2
+ const scriptUrl = "https://pay.google.com/gp/p/js/pay.js";
3
+
4
+ if (document.querySelector(`script[src="${scriptUrl}"]`)) {
5
+ if (typeof window !== 'undefined' && typeof window.google !== 'undefined' && window.google.payments?.api?.PaymentsClient) {
6
+ window.dispatchEvent(new CustomEvent("googlePayScriptLoaded"));
7
+ }
8
+ return;
9
+ }
10
+
11
+ const script = document.createElement("script");
12
+ script.src = scriptUrl;
13
+ script.async = true;
14
+
15
+ script.onload = () => {
16
+ setTimeout(() => {
17
+ if (typeof window !== 'undefined' && typeof window.google !== 'undefined' && window.google.payments?.api?.PaymentsClient) {
18
+ window.dispatchEvent(new CustomEvent("googlePayScriptLoaded"));
19
+ } else {
20
+ window.dispatchEvent(new CustomEvent("googlePayScriptLoaded"));
21
+ }
22
+ }, 500);
23
+ };
24
+
25
+ script.onerror = () => {
26
+ window.dispatchEvent(new CustomEvent("googlePayScriptError"));
27
+ };
28
+
29
+ document.head.appendChild(script);
30
+ };
31
+