strapi-plugin-payone-provider 1.0.1

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 (42) hide show
  1. package/README.md +571 -0
  2. package/admin/src/components/Initializer/index.js +16 -0
  3. package/admin/src/components/PluginIcon/index.js +6 -0
  4. package/admin/src/index.js +37 -0
  5. package/admin/src/pages/App/components/ConfigurationPanel.js +265 -0
  6. package/admin/src/pages/App/components/HistoryPanel.js +298 -0
  7. package/admin/src/pages/App/components/PaymentActionsPanel.js +333 -0
  8. package/admin/src/pages/App/components/StatusBadge.js +22 -0
  9. package/admin/src/pages/App/components/TransactionHistoryItem.js +374 -0
  10. package/admin/src/pages/App/components/icons/BankIcon.js +10 -0
  11. package/admin/src/pages/App/components/icons/ChevronDownIcon.js +9 -0
  12. package/admin/src/pages/App/components/icons/ChevronUpIcon.js +9 -0
  13. package/admin/src/pages/App/components/icons/CreditCardIcon.js +9 -0
  14. package/admin/src/pages/App/components/icons/ErrorIcon.js +10 -0
  15. package/admin/src/pages/App/components/icons/InfoIcon.js +9 -0
  16. package/admin/src/pages/App/components/icons/PaymentIcon.js +10 -0
  17. package/admin/src/pages/App/components/icons/PendingIcon.js +9 -0
  18. package/admin/src/pages/App/components/icons/PersonIcon.js +9 -0
  19. package/admin/src/pages/App/components/icons/SuccessIcon.js +9 -0
  20. package/admin/src/pages/App/components/icons/WalletIcon.js +9 -0
  21. package/admin/src/pages/App/components/icons/index.js +11 -0
  22. package/admin/src/pages/App/index.js +483 -0
  23. package/admin/src/pages/utils/api.js +75 -0
  24. package/admin/src/pages/utils/formatTransactionData.js +16 -0
  25. package/admin/src/pages/utils/paymentUtils.js +528 -0
  26. package/admin/src/pluginId.js +5 -0
  27. package/package.json +43 -0
  28. package/server/bootstrap.js +26 -0
  29. package/server/config/index.js +42 -0
  30. package/server/controllers/index.js +7 -0
  31. package/server/controllers/payone.js +134 -0
  32. package/server/destroy.js +5 -0
  33. package/server/index.js +21 -0
  34. package/server/policies/index.js +6 -0
  35. package/server/policies/isAuth.js +23 -0
  36. package/server/policies/isSuperAdmin.js +18 -0
  37. package/server/register.js +5 -0
  38. package/server/routes/index.js +124 -0
  39. package/server/services/index.js +7 -0
  40. package/server/services/payone.js +679 -0
  41. package/strapi-admin.js +3 -0
  42. package/strapi-server.js +3 -0
@@ -0,0 +1,483 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { useNotification, useFetchClient } from "@strapi/helper-plugin";
3
+ import {
4
+ Layout,
5
+ HeaderLayout,
6
+ ContentLayout,
7
+ Box,
8
+ Button,
9
+ Tabs,
10
+ Tab,
11
+ TabGroup,
12
+ TabPanels,
13
+ TabPanel,
14
+ Typography
15
+ } from "@strapi/design-system";
16
+ import { Check } from "@strapi/icons";
17
+ import payoneRequests from "../utils/api";
18
+ import ConfigurationPanel from "./components/ConfigurationPanel";
19
+ import HistoryPanel from "./components/HistoryPanel";
20
+ import PaymentActionsPanel from "./components/PaymentActionsPanel";
21
+
22
+ const App = () => {
23
+ const toggleNotification = useNotification();
24
+ const { get, put } = useFetchClient();
25
+
26
+ const [settings, setSettings] = useState({
27
+ aid: "",
28
+ portalid: "",
29
+ mid: "",
30
+ key: "",
31
+ mode: "test",
32
+ api_version: "3.10"
33
+ });
34
+ const [isLoading, setIsLoading] = useState(false);
35
+ const [isSaving, setIsSaving] = useState(false);
36
+ const [isTesting, setIsTesting] = useState(false);
37
+ const [testResult, setTestResult] = useState(null);
38
+
39
+ // Transaction Management state
40
+ const [transactionHistory, setTransactionHistory] = useState([]);
41
+ const [isLoadingHistory, setIsLoadingHistory] = useState(false);
42
+ const [selectedTransaction, setSelectedTransaction] = useState(null);
43
+ const [filters, setFilters] = useState({
44
+ status: "",
45
+ request_type: "",
46
+ txid: "",
47
+ reference: "",
48
+ date_from: "",
49
+ date_to: ""
50
+ });
51
+
52
+ // Payment Actions state
53
+ const [paymentAmount, setPaymentAmount] = useState("1000");
54
+ const [preauthReference, setPreauthReference] = useState("");
55
+ const [authReference, setAuthReference] = useState("");
56
+ const [captureTxid, setCaptureTxid] = useState("");
57
+ const [refundTxid, setRefundTxid] = useState("");
58
+ const [refundSequenceNumber, setRefundSequenceNumber] = useState("2");
59
+ const [refundReference, setRefundReference] = useState("");
60
+ const [paymentMethod, setPaymentMethod] = useState("cc");
61
+ const [captureMode, setCaptureMode] = useState("full"); // full, partial
62
+
63
+
64
+ const [isProcessingPayment, setIsProcessingPayment] = useState(false);
65
+ const [paymentResult, setPaymentResult] = useState(null);
66
+ const [paymentError, setPaymentError] = useState(null);
67
+ const [activeTab, setActiveTab] = useState(0);
68
+
69
+ // Pagination state
70
+ const [currentPage, setCurrentPage] = useState(1);
71
+ const [pageSize] = useState(10);
72
+
73
+ useEffect(() => {
74
+ loadSettings();
75
+ loadTransactionHistory();
76
+ }, []);
77
+
78
+ // Pagination calculations
79
+ const totalPages = Math.ceil(transactionHistory.length / pageSize);
80
+ const startIndex = (currentPage - 1) * pageSize;
81
+ const endIndex = startIndex + pageSize;
82
+ const paginatedTransactions = transactionHistory.slice(startIndex, endIndex);
83
+
84
+ const handlePageChange = (page) => {
85
+ setCurrentPage(page);
86
+ setSelectedTransaction(null);
87
+ };
88
+
89
+ const loadSettings = async () => {
90
+ setIsLoading(true);
91
+ try {
92
+ const { data } = await get(`/payone-provider/settings`);
93
+ if (data?.data) setSettings(data.data);
94
+ } catch (error) {
95
+ toggleNotification({
96
+ type: "warning",
97
+ message: "Failed to load settings"
98
+ });
99
+ } finally {
100
+ setIsLoading(false);
101
+ }
102
+ };
103
+
104
+ const handleInputChange = (field, value) => {
105
+ setSettings((prev) => ({ ...prev, [field]: value }));
106
+ };
107
+
108
+ const handleSave = async () => {
109
+ setIsSaving(true);
110
+ try {
111
+ await put(`/payone-provider/settings`, settings);
112
+ toggleNotification({
113
+ type: "success",
114
+ message: "Settings saved successfully"
115
+ });
116
+ } catch (error) {
117
+ toggleNotification({
118
+ type: "warning",
119
+ message: "Failed to save settings"
120
+ });
121
+ } finally {
122
+ setIsSaving(false);
123
+ }
124
+ };
125
+
126
+ const handleTestConnection = async () => {
127
+ setIsTesting(true);
128
+ setTestResult(null);
129
+ try {
130
+ const response = await payoneRequests.testConnection();
131
+ if (response.data) {
132
+ const result = response.data;
133
+ setTestResult(result);
134
+ if (result.success !== undefined) {
135
+ toggleNotification({
136
+ type: Boolean(result.success) ? "success" : "warning",
137
+ message: result.message || "Test completed"
138
+ });
139
+ }
140
+ } else {
141
+ throw new Error("Invalid response format from server");
142
+ }
143
+ } catch (error) {
144
+ toggleNotification({
145
+ type: "warning",
146
+ message: "Failed to test connection"
147
+ });
148
+ setTestResult({
149
+ success: false,
150
+ message:
151
+ "Failed to test connection. Please check your network and server logs for details.",
152
+ details: {
153
+ errorCode: "NETWORK",
154
+ rawResponse: error.message || "Network error"
155
+ }
156
+ });
157
+ } finally {
158
+ setIsTesting(false);
159
+ }
160
+ };
161
+
162
+ const loadTransactionHistory = async () => {
163
+ setIsLoadingHistory(true);
164
+ try {
165
+ const result = await payoneRequests.getTransactionHistory(filters);
166
+ setTransactionHistory(result.data || []);
167
+ setCurrentPage(1);
168
+ } catch (error) {
169
+ toggleNotification({
170
+ type: "warning",
171
+ message: "Failed to load transaction history"
172
+ });
173
+ } finally {
174
+ setIsLoadingHistory(false);
175
+ }
176
+ };
177
+
178
+ const handleFilterChange = (field, value) => {
179
+ setFilters((prev) => ({ ...prev, [field]: value }));
180
+ };
181
+
182
+ const handleFilterApply = () => {
183
+ loadTransactionHistory();
184
+ };
185
+
186
+ const handleTransactionSelect = (transaction) => {
187
+ if (selectedTransaction?.id === transaction?.id) {
188
+ setSelectedTransaction(null);
189
+ } else {
190
+ setSelectedTransaction(transaction);
191
+ }
192
+ };
193
+
194
+ const handlePreauthorization = async () => {
195
+ setIsProcessingPayment(true);
196
+ setPaymentError(null);
197
+ setPaymentResult(null);
198
+ try {
199
+ const params = {
200
+ amount: parseInt(paymentAmount),
201
+ currency: "EUR",
202
+ reference: preauthReference || `PREAUTH-${Date.now()}`,
203
+ clearingtype: "cc",
204
+ cardtype: "V",
205
+ cardpan: "4111111111111111",
206
+ cardexpiredate: "2512",
207
+ cardcvc2: "123",
208
+ firstname: "John",
209
+ lastname: "Doe",
210
+ street: "Test Street 123",
211
+ zip: "12345",
212
+ city: "Test City",
213
+ country: "DE",
214
+ email: "test@example.com",
215
+ salutation: "Herr",
216
+ gender: "m",
217
+ telephonenumber: "01752345678",
218
+ ip: "127.0.0.1",
219
+ customer_is_present: "yes",
220
+ language: "de"
221
+ };
222
+ const result = await payoneRequests.preauthorization(params);
223
+ setPaymentResult(result);
224
+ toggleNotification({
225
+ type: "success",
226
+ message: "Preauthorization completed successfully"
227
+ });
228
+ } catch (error) {
229
+ const errorMessage =
230
+ error.response?.data?.data?.Error?.ErrorMessage ||
231
+ error.message ||
232
+ "Preauthorization failed";
233
+ setPaymentError(errorMessage);
234
+ toggleNotification({
235
+ type: "warning",
236
+ message: "Preauthorization failed"
237
+ });
238
+ } finally {
239
+ setIsProcessingPayment(false);
240
+ }
241
+ };
242
+
243
+ const handleAuthorization = async () => {
244
+ setIsProcessingPayment(true);
245
+ setPaymentError(null);
246
+ setPaymentResult(null);
247
+ try {
248
+ const params = {
249
+ amount: parseInt(paymentAmount),
250
+ currency: "EUR",
251
+ reference: authReference || `AUTH-${Date.now()}`,
252
+ clearingtype: "cc",
253
+ cardtype: "V",
254
+ cardpan: "4111111111111111",
255
+ cardexpiredate: "2512",
256
+ cardcvc2: "123",
257
+ firstname: "John",
258
+ lastname: "Doe",
259
+ street: "Test Street 123",
260
+ zip: "12345",
261
+ city: "Test City",
262
+ country: "DE",
263
+ email: "test@example.com",
264
+ salutation: "Herr",
265
+ gender: "m",
266
+ telephonenumber: "01752345678",
267
+ ip: "127.0.0.1",
268
+ customer_is_present: "yes",
269
+ language: "de"
270
+ };
271
+ const result = await payoneRequests.authorization(params);
272
+ setPaymentResult(result);
273
+ toggleNotification({
274
+ type: "success",
275
+ message: "Authorization completed successfully"
276
+ });
277
+ } catch (error) {
278
+ const errorMessage =
279
+ error.response?.data?.data?.Error?.ErrorMessage ||
280
+ error.message ||
281
+ "Authorization failed";
282
+ setPaymentError(errorMessage);
283
+ toggleNotification({ type: "warning", message: "Authorization failed" });
284
+ } finally {
285
+ setIsProcessingPayment(false);
286
+ }
287
+ };
288
+
289
+ const handleCapture = async () => {
290
+ if (!captureTxid.trim()) {
291
+ setPaymentError("Transaction ID is required for capture");
292
+ return;
293
+ }
294
+ setIsProcessingPayment(true);
295
+ setPaymentError(null);
296
+ setPaymentResult(null);
297
+ try {
298
+ const params = {
299
+ txid: captureTxid,
300
+ amount: parseInt(paymentAmount),
301
+ currency: "EUR"
302
+ };
303
+ const result = await payoneRequests.capture(params);
304
+ setPaymentResult(result);
305
+ toggleNotification({
306
+ type: "success",
307
+ message: "Capture completed successfully"
308
+ });
309
+ } catch (error) {
310
+ const errorMessage =
311
+ error.response?.data?.data?.Error?.ErrorMessage ||
312
+ error.message ||
313
+ "Capture failed";
314
+ setPaymentError(errorMessage);
315
+ toggleNotification({ type: "warning", message: "Capture failed" });
316
+ } finally {
317
+ setIsProcessingPayment(false);
318
+ }
319
+ };
320
+
321
+ const handleRefund = async () => {
322
+ if (!refundTxid.trim()) {
323
+ setPaymentError("Transaction ID is required for refund");
324
+ return;
325
+ }
326
+ setIsProcessingPayment(true);
327
+ setPaymentError(null);
328
+ setPaymentResult(null);
329
+ try {
330
+ const params = {
331
+ txid: refundTxid,
332
+ sequencenumber: parseInt(refundSequenceNumber),
333
+ amount: -Math.abs(parseInt(paymentAmount)),
334
+ currency: "EUR",
335
+ reference: refundReference || `REFUND-${Date.now()}`
336
+ };
337
+ const result = await payoneRequests.refund(params);
338
+ setPaymentResult(result);
339
+ toggleNotification({
340
+ type: "success",
341
+ message: "Refund completed successfully"
342
+ });
343
+ } catch (error) {
344
+ const errorMessage =
345
+ error.response?.data?.data?.Error?.ErrorMessage ||
346
+ error.message ||
347
+ "Refund failed";
348
+ setPaymentError(errorMessage);
349
+ toggleNotification({ type: "warning", message: "Refund failed" });
350
+ } finally {
351
+ setIsProcessingPayment(false);
352
+ }
353
+ };
354
+
355
+ return (
356
+ <Layout>
357
+ <HeaderLayout
358
+ title={
359
+ <Box>
360
+ <Typography variant="alpha" as="h1" fontWeight="bold">
361
+ Payone Provider
362
+ </Typography>
363
+ <Typography variant="pi" marginTop={2}>
364
+ Configure your Payone integration and manage payment transactions
365
+ </Typography>
366
+ </Box>
367
+ }
368
+ primaryAction={
369
+ activeTab === 0 ? (
370
+ <Button
371
+ loading={isSaving}
372
+ onClick={handleSave}
373
+ startIcon={<Check />}
374
+ size="L"
375
+ variant="default"
376
+ style={{
377
+ background: "#28a745",
378
+ border: "none",
379
+ borderRadius: "8px",
380
+ fontWeight: "600"
381
+ }}
382
+ >
383
+ Save Configuration
384
+ </Button>
385
+ ) : null
386
+ }
387
+ />
388
+ <ContentLayout>
389
+ <Box padding={6}>
390
+ <TabGroup
391
+ label="Payone Provider Tabs"
392
+ onTabChange={(index) => setActiveTab(index)}
393
+ >
394
+ <Tabs style={{ borderBottom: "2px solid #f6f6f9" }}>
395
+ <Tab
396
+ style={{
397
+ fontWeight: activeTab === 0 ? "600" : "400",
398
+ color: activeTab === 0 ? "#4945ff" : "#666687"
399
+ }}
400
+ >
401
+ Configuration
402
+ </Tab>
403
+ <Tab style={{ fontWeight: activeTab === 1 ? "600" : "400" }}>
404
+ Transaction History
405
+ </Tab>
406
+ <Tab
407
+ style={{
408
+ fontWeight: activeTab === 2 ? "600" : "400",
409
+ color: activeTab === 2 ? "#4945ff" : "#666687"
410
+ }}
411
+ >
412
+ Payment Actions
413
+ </Tab>
414
+ </Tabs>
415
+ <TabPanels>
416
+ <TabPanel>
417
+ <ConfigurationPanel
418
+ settings={settings}
419
+ isSaving={isSaving}
420
+ isTesting={isTesting}
421
+ testResult={testResult}
422
+ onSave={handleSave}
423
+ onTestConnection={handleTestConnection}
424
+ onInputChange={handleInputChange}
425
+ />
426
+ </TabPanel>
427
+
428
+ <TabPanel>
429
+ <HistoryPanel
430
+ filters={filters}
431
+ onFilterChange={handleFilterChange}
432
+ onFilterApply={handleFilterApply}
433
+ isLoadingHistory={isLoadingHistory}
434
+ transactionHistory={transactionHistory}
435
+ paginatedTransactions={paginatedTransactions}
436
+ currentPage={currentPage}
437
+ totalPages={totalPages}
438
+ pageSize={pageSize}
439
+ onRefresh={loadTransactionHistory}
440
+ onPageChange={handlePageChange}
441
+ selectedTransaction={selectedTransaction}
442
+ onTransactionSelect={handleTransactionSelect}
443
+ />
444
+ </TabPanel>
445
+
446
+ <TabPanel>
447
+ <PaymentActionsPanel
448
+ paymentAmount={paymentAmount}
449
+ setPaymentAmount={setPaymentAmount}
450
+ preauthReference={preauthReference}
451
+ setPreauthReference={setPreauthReference}
452
+ authReference={authReference}
453
+ setAuthReference={setAuthReference}
454
+ captureTxid={captureTxid}
455
+ setCaptureTxid={setCaptureTxid}
456
+ setCaptureMode={setCaptureMode}
457
+ captureMode={captureMode}
458
+ paymentMethod={paymentMethod}
459
+ setPaymentMethod={setPaymentMethod}
460
+ refundTxid={refundTxid}
461
+ setRefundTxid={setRefundTxid}
462
+ refundSequenceNumber={refundSequenceNumber}
463
+ setRefundSequenceNumber={setRefundSequenceNumber}
464
+ refundReference={refundReference}
465
+ setRefundReference={setRefundReference}
466
+ isProcessingPayment={isProcessingPayment}
467
+ paymentError={paymentError}
468
+ paymentResult={paymentResult}
469
+ onPreauthorization={handlePreauthorization}
470
+ onAuthorization={handleAuthorization}
471
+ onCapture={handleCapture}
472
+ onRefund={handleRefund}
473
+ />
474
+ </TabPanel>
475
+ </TabPanels>
476
+ </TabGroup>
477
+ </Box>
478
+ </ContentLayout>
479
+ </Layout>
480
+ );
481
+ };
482
+
483
+ export default App;
@@ -0,0 +1,75 @@
1
+ import { request } from "@strapi/helper-plugin";
2
+ import pluginId from "../../pluginId";
3
+
4
+ const payoneRequests = {
5
+ getSettings: () => {
6
+ return request(`/${pluginId}/settings`, {
7
+ method: "GET"
8
+ });
9
+ },
10
+
11
+ updateSettings: (data) => {
12
+ return request(`/${pluginId}/settings`, {
13
+ method: "PUT",
14
+ body: data
15
+ });
16
+ },
17
+
18
+ getTransactionHistory: (params = {}) => {
19
+ const queryString = new URLSearchParams(params).toString();
20
+ return request(
21
+ `/${pluginId}/transaction-history${queryString ? `?${queryString}` : ""}`,
22
+ {
23
+ method: "GET"
24
+ }
25
+ );
26
+ },
27
+
28
+ testConnection: () => {
29
+ return request(`/${pluginId}/test-connection`, {
30
+ method: "POST"
31
+ });
32
+ },
33
+
34
+ preauthorization: (data) => {
35
+ return request(`/${pluginId}/preauthorization`, {
36
+ method: "POST",
37
+ body: data,
38
+ headers: {
39
+ "Content-Type": "application/json"
40
+ }
41
+ });
42
+ },
43
+
44
+ authorization: (data) => {
45
+ return request(`/${pluginId}/authorization`, {
46
+ method: "POST",
47
+ body: data,
48
+ headers: {
49
+ "Content-Type": "application/json"
50
+ }
51
+ });
52
+ },
53
+
54
+ capture: (data) => {
55
+ return request(`/${pluginId}/capture`, {
56
+ method: "POST",
57
+ body: data,
58
+ headers: {
59
+ "Content-Type": "application/json"
60
+ }
61
+ });
62
+ },
63
+
64
+ refund: (data) => {
65
+ return request(`/${pluginId}/refund`, {
66
+ method: "POST",
67
+ body: data,
68
+ headers: {
69
+ "Content-Type": "application/json"
70
+ }
71
+ });
72
+ }
73
+ };
74
+
75
+ export default payoneRequests;
@@ -0,0 +1,16 @@
1
+ export const formatTransactionData = (data) => {
2
+ const formattedData = [];
3
+ if (!data || typeof data !== "object") return formattedData;
4
+
5
+ for (const [key, value] of Object.entries(data)) {
6
+ if (value !== null && value !== undefined) {
7
+ formattedData.push({
8
+ key:
9
+ key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, " $1"),
10
+ value: typeof value === "object" ? JSON.stringify(value) : String(value)
11
+ });
12
+ }
13
+ }
14
+
15
+ return formattedData;
16
+ };