strapi-plugin-payone-provider 5.7.26 → 5.8.26

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 (83) hide show
  1. package/README.md +1191 -1191
  2. package/admin/src/components/Initializer/index.jsx +16 -16
  3. package/admin/src/components/PluginIcon/index.jsx +17 -17
  4. package/admin/src/index.js +57 -57
  5. package/admin/src/pages/App/components/AppHeader.jsx +45 -45
  6. package/admin/src/pages/App/components/AppTabs.jsx +105 -105
  7. package/admin/src/pages/App/components/ApplePayBtn.jsx +355 -355
  8. package/admin/src/pages/App/components/ApplePayConfig.jsx +357 -357
  9. package/admin/src/pages/App/components/DocsPanel.jsx +53 -53
  10. package/admin/src/pages/App/components/RenderInput.jsx +78 -78
  11. package/admin/src/pages/App/components/StatusBadge.jsx +87 -87
  12. package/admin/src/pages/App/components/icons/BankIcon.jsx +10 -10
  13. package/admin/src/pages/App/components/icons/ChevronDownIcon.jsx +9 -9
  14. package/admin/src/pages/App/components/icons/ChevronUpIcon.jsx +9 -9
  15. package/admin/src/pages/App/components/icons/CreditCardIcon.jsx +9 -9
  16. package/admin/src/pages/App/components/icons/ErrorIcon.jsx +10 -10
  17. package/admin/src/pages/App/components/icons/InfoIcon.jsx +9 -9
  18. package/admin/src/pages/App/components/icons/MarkCircle.jsx +19 -19
  19. package/admin/src/pages/App/components/icons/PaymentIcon.jsx +10 -10
  20. package/admin/src/pages/App/components/icons/PendingIcon.jsx +9 -9
  21. package/admin/src/pages/App/components/icons/PersonIcon.jsx +9 -9
  22. package/admin/src/pages/App/components/icons/SuccessIcon.jsx +9 -9
  23. package/admin/src/pages/App/components/icons/WalletIcon.jsx +9 -9
  24. package/admin/src/pages/App/components/icons/index.jsx +12 -12
  25. package/admin/src/pages/App/components/payment-actions/AuthorizationForm.jsx +334 -334
  26. package/admin/src/pages/App/components/payment-actions/CaptureForm.jsx +120 -120
  27. package/admin/src/pages/App/components/payment-actions/PaymentActionsPanel.jsx +183 -183
  28. package/admin/src/pages/App/components/payment-actions/PaymentMethodSelector.jsx +315 -315
  29. package/admin/src/pages/App/components/payment-actions/PaymentResult.jsx +129 -129
  30. package/admin/src/pages/App/components/payment-actions/PreauthorizationForm.jsx +273 -273
  31. package/admin/src/pages/App/components/payment-actions/RefundForm.jsx +114 -114
  32. package/admin/src/pages/App/components/transaction-history/ImportExportBar.jsx +153 -153
  33. package/admin/src/pages/App/components/transaction-history/details/TransactionHistoryItem.jsx +526 -526
  34. package/admin/src/pages/App/index.jsx +96 -96
  35. package/admin/src/pages/App/styles.css +176 -176
  36. package/admin/src/pages/constants/paymentConstants.js +71 -71
  37. package/admin/src/pages/hooks/use-system-theme.js +27 -27
  38. package/admin/src/pages/hooks/usePaymentActions.js +498 -498
  39. package/admin/src/pages/hooks/usePluginTranslations.js +12 -12
  40. package/admin/src/pages/hooks/useSettings.js +183 -183
  41. package/admin/src/pages/hooks/useTransactionHistory.js +148 -148
  42. package/admin/src/pages/utils/api.js +97 -97
  43. package/admin/src/pages/utils/applePayConstants.js +196 -196
  44. package/admin/src/pages/utils/formatTransactionData.js +15 -15
  45. package/admin/src/pages/utils/getInputComponent.jsx +200 -200
  46. package/admin/src/pages/utils/paymentUtils.js +661 -661
  47. package/admin/src/pages/utils/tooltipHelpers.js +18 -18
  48. package/admin/src/pages/utils/transactionTableUtils.js +71 -71
  49. package/admin/src/pluginId.js +9 -9
  50. package/admin/src/translations/de.json +235 -235
  51. package/admin/src/translations/en.json +235 -235
  52. package/admin/src/translations/fr.json +235 -235
  53. package/admin/src/translations/ru.json +235 -235
  54. package/admin/src/utils/prefixPluginTranslations.js +13 -13
  55. package/package.json +45 -45
  56. package/server/bootstrap.js +107 -107
  57. package/server/config/index.js +83 -83
  58. package/server/content-types/index.js +4 -4
  59. package/server/content-types/transactions/index.js +4 -4
  60. package/server/content-types/transactions/schema.json +86 -86
  61. package/server/controllers/index.js +7 -7
  62. package/server/controllers/payone.js +503 -506
  63. package/server/destroy.js +5 -5
  64. package/server/index.js +23 -23
  65. package/server/policies/index.js +7 -7
  66. package/server/policies/is-auth.js +29 -29
  67. package/server/policies/isSuperAdmin.js +20 -20
  68. package/server/register.js +5 -5
  69. package/server/routes/index.js +218 -218
  70. package/server/services/applePayService.js +295 -295
  71. package/server/services/index.js +9 -9
  72. package/server/services/paymentService.js +264 -223
  73. package/server/services/payone.js +78 -78
  74. package/server/services/settingsService.js +59 -59
  75. package/server/services/testConnectionService.js +115 -115
  76. package/server/services/transactionService.js +262 -262
  77. package/server/utils/csvTransactions.js +82 -82
  78. package/server/utils/normalize.js +39 -39
  79. package/server/utils/paymentMethodParams.js +288 -288
  80. package/server/utils/requestBuilder.js +115 -100
  81. package/server/utils/responseParser.js +141 -141
  82. package/strapi-admin.js +4 -4
  83. package/strapi-server.js +3 -3
@@ -1,506 +1,503 @@
1
- "use strict";
2
-
3
- const crypto = require("crypto");
4
- const PLUGIN_NAME = "strapi-plugin-payone-provider";
5
- const {
6
- rowsToCsv,
7
- csvToRows,
8
- TRANSACTION_ATTRS
9
- } = require("../utils/csvTransactions");
10
-
11
- const getPayoneService = (strapi) => {
12
- return strapi.plugin(PLUGIN_NAME).service("payone");
13
- };
14
-
15
- const getNewHostedTokenizationService = (strapi) => {
16
- return strapi.plugin(PLUGIN_NAME).service("newHostedTokenizationService");
17
- };
18
-
19
- const buildFiltersFromQuery = (rawFilters = {}) => {
20
- const filters = {};
21
- if (rawFilters && typeof rawFilters === "object") {
22
- for (const [key, value] of Object.entries(rawFilters)) {
23
- const v = value == null ? "" : String(value).trim();
24
- if (v !== "" && v.toLowerCase() !== "all") filters[key] = value;
25
- }
26
- }
27
- return filters;
28
- };
29
-
30
- const handleError = (ctx, error) => {
31
- const status = error.status || error.response?.status || 500;
32
- const message = error.message || "Internal server error";
33
-
34
- if (status >= 400) {
35
- console.log("[Payone] Controller error:", {
36
- status,
37
- message,
38
- error: error.stack || error
39
- });
40
- }
41
-
42
- ctx.status = status;
43
- ctx.body = {
44
- error: {
45
- status,
46
- message,
47
- name: error.name || "Error"
48
- }
49
- };
50
- };
51
-
52
- const hideKey = (settings) => {
53
- if (settings && settings.key) {
54
- settings.key = "***HIDDEN***";
55
- }
56
- if (settings && settings.serverApiSecret) {
57
- settings.serverApiSecret = "***HIDDEN***";
58
- }
59
- return settings;
60
- };
61
-
62
- module.exports = ({ strapi }) => ({
63
- async getSettings(ctx) {
64
- try {
65
- const settings = await getPayoneService(strapi).getSettings();
66
- ctx.body = {
67
- ...hideKey(settings || {})
68
- };
69
- } catch (error) {
70
- handleError(ctx, error);
71
- }
72
- },
73
-
74
- async getPublicSettings(ctx) {
75
- try {
76
- const settings = await getPayoneService(strapi).getSettings();
77
- ctx.body = {
78
- data: {
79
- mid: settings?.mid || null,
80
- mode: settings?.mode || null,
81
- domainName: settings?.domainName || null,
82
- displayName: settings?.displayName || null,
83
- portalid: settings?.portalid || null,
84
- accountId: settings?.aid || null,
85
- paymentMethods: {
86
- creditCard: settings?.enableCreditCard,
87
- paypal: settings?.enablePayPal,
88
- googlePay: settings?.enableGooglePay,
89
- applePay: settings?.enableApplePay,
90
- sofort: settings?.enableSofort,
91
- sepa: settings?.enableSepaDirectDebit
92
- }
93
- }
94
- };
95
- } catch (error) {
96
- handleError(ctx, error);
97
- }
98
- },
99
-
100
- async updateSettings(ctx) {
101
- try {
102
- const bodyData = ctx.request.body?.data || ctx.request.body;
103
-
104
- if (!bodyData || typeof bodyData !== "object") {
105
- ctx.throw(400, "Invalid request body");
106
- }
107
-
108
- const currentSettings = await getPayoneService(strapi).getSettings();
109
-
110
- if (bodyData.key === "***HIDDEN***" || !bodyData.key) {
111
- bodyData.key = currentSettings?.key;
112
- }
113
- if (
114
- bodyData.serverApiSecret === "***HIDDEN***" ||
115
- !bodyData.serverApiSecret
116
- ) {
117
- bodyData.serverApiSecret = currentSettings?.serverApiSecret;
118
- }
119
-
120
- const settings = await getPayoneService(strapi).updateSettings(bodyData);
121
- ctx.body = { ...hideKey(settings) };
122
- } catch (error) {
123
- handleError(ctx, error);
124
- }
125
- },
126
-
127
- async preauthorization(ctx) {
128
- try {
129
- const params = ctx.request.body?.data || ctx.request.body;
130
- if (!params || typeof params !== "object") {
131
- ctx.throw(400, "Invalid request body");
132
- }
133
-
134
- const result = await getPayoneService(strapi).preauthorization(params);
135
- ctx.body = result;
136
- } catch (error) {
137
- handleError(ctx, error);
138
- }
139
- },
140
-
141
- async authorization(ctx) {
142
- try {
143
- const params = ctx.request.body?.data || ctx.request.body;
144
-
145
- if (!params || typeof params !== "object") {
146
- ctx.throw(400, "Invalid request body");
147
- }
148
-
149
- const result = await getPayoneService(strapi).authorization(params);
150
- ctx.body = result;
151
- } catch (error) {
152
- handleError(ctx, error);
153
- }
154
- },
155
-
156
- async capture(ctx) {
157
- try {
158
- const params = ctx.request.body?.data || ctx.request.body;
159
-
160
- if (!params || typeof params !== "object") {
161
- ctx.throw(400, "Invalid request body");
162
- }
163
-
164
- const result = await getPayoneService(strapi).capture(params);
165
- ctx.body = result;
166
- } catch (error) {
167
- handleError(ctx, error);
168
- }
169
- },
170
-
171
- async refund(ctx) {
172
- try {
173
- const params = ctx.request.body?.data || ctx.request.body;
174
-
175
- if (!params || typeof params !== "object") {
176
- ctx.throw(400, "Invalid request body");
177
- }
178
-
179
- const result = await getPayoneService(strapi).refund(params);
180
- ctx.body = result;
181
- } catch (error) {
182
- handleError(ctx, error);
183
- }
184
- },
185
-
186
- async getTransactionHistory(ctx) {
187
- try {
188
- const { filters: rawFilters = {}, pagination = {} } = ctx.query || {};
189
- const page = parseInt(pagination.page || "1", 10);
190
- const pageSize = parseInt(pagination.pageSize || "10", 10);
191
-
192
- const filters = {};
193
- if (rawFilters && typeof rawFilters === "object") {
194
- for (const [key, value] of Object.entries(rawFilters)) {
195
- const v = value == null ? "" : String(value).trim();
196
- if (v !== "" && v.toLowerCase() !== "all") {
197
- filters[key] = value;
198
- }
199
- }
200
- }
201
-
202
- const result = await getPayoneService(strapi).getTransactionHistory({
203
- filters,
204
- pagination: { page, pageSize }
205
- });
206
-
207
- ctx.body = {
208
- data: result.data || [],
209
- meta: {
210
- pagination: result.pagination
211
- }
212
- };
213
- } catch (error) {
214
- handleError(ctx, error);
215
- }
216
- },
217
-
218
- async exportTransactions(ctx) {
219
- try {
220
- const {
221
- filters: rawFilters = {},
222
- format = "json",
223
- sort_by,
224
- sort_order
225
- } = ctx.query || {};
226
- const filters = buildFiltersFromQuery(rawFilters);
227
- const data = await getPayoneService(strapi).getTransactionsForExport({
228
- filters,
229
- sort_by: sort_by || "createdAt",
230
- sort_order: sort_order || "desc"
231
- });
232
- const rows = Array.isArray(data) ? data : [];
233
- const fmt = (format || "json").toLowerCase();
234
-
235
- if (fmt === "csv") {
236
- ctx.set("Content-Type", "text/csv; charset=utf-8");
237
- ctx.set(
238
- "Content-Disposition",
239
- 'attachment; filename="transactions.csv"'
240
- );
241
- ctx.body = rowsToCsv(rows, TRANSACTION_ATTRS);
242
- return;
243
- }
244
-
245
- ctx.set("Content-Type", "application/json");
246
- ctx.set(
247
- "Content-Disposition",
248
- 'attachment; filename="transactions.json"'
249
- );
250
- ctx.body = rows;
251
- } catch (error) {
252
- handleError(ctx, error);
253
- }
254
- },
255
-
256
- async importTransactions(ctx) {
257
- try {
258
- const body = ctx.request.body;
259
- if (!body || typeof body !== "object")
260
- ctx.throw(400, "Request body must be JSON");
261
-
262
- let rows = [];
263
- if (Array.isArray(body)) {
264
- rows = body;
265
- } else if (Array.isArray(body.data)) {
266
- rows = body.data;
267
- } else if (body.format === "csv" && typeof body.data === "string") {
268
- rows = csvToRows(body.data);
269
- } else {
270
- ctx.throw(
271
- 400,
272
- "Body must be an array, { data: array }, or { format: 'csv', data: csvString }"
273
- );
274
- }
275
-
276
- if (rows.length === 0) {
277
- ctx.body = {
278
- imported: 0,
279
- failed: 0,
280
- errors: [],
281
- message: "No rows to import"
282
- };
283
- return;
284
- }
285
-
286
- const result = await getPayoneService(strapi).importTransactions(rows);
287
- ctx.body = {
288
- ...result,
289
- message: `Imported ${result.imported}, failed ${result.failed}`
290
- };
291
- } catch (error) {
292
- handleError(ctx, error);
293
- }
294
- },
295
-
296
- async testConnection(ctx) {
297
- try {
298
- const result = await getPayoneService(strapi).testConnection();
299
- ctx.body = result || {};
300
- } catch (error) {
301
- handleError(ctx, error);
302
- }
303
- },
304
-
305
- async handle3DSCallback(ctx) {
306
- try {
307
- const isGetRequest = ctx.request.method === "GET";
308
- const currentPath = ctx.request.url;
309
-
310
- let resultType = "callback";
311
- if (currentPath.includes("/success")) {
312
- resultType = "success";
313
- } else if (currentPath.includes("/error")) {
314
- resultType = "error";
315
- } else if (currentPath.includes("/back")) {
316
- resultType = "cancelled";
317
- }
318
-
319
- const callbackData = isGetRequest
320
- ? ctx.query
321
- : ctx.request.body || ctx.request.body?.data || ctx.request?.data;
322
-
323
- const result = await getPayoneService(strapi).handle3DSCallback(
324
- callbackData,
325
- resultType
326
- );
327
-
328
- if (isGetRequest) {
329
- const isContentUI = currentPath.includes("/content-ui");
330
- const basePath = isContentUI ? "/content-ui" : "/admin";
331
- const pluginPath = "/plugins/strapi-plugin-payone-provider";
332
-
333
- const queryParams = new URLSearchParams();
334
- queryParams.set("3ds", resultType);
335
- if (result.txid) queryParams.set("txid", result.txid);
336
- if (result.status) queryParams.set("status", result.status);
337
-
338
- const redirectUrl = `${basePath}${pluginPath}?${queryParams.toString()}`;
339
- return ctx.redirect(redirectUrl);
340
- }
341
-
342
- ctx.body = result;
343
- } catch (error) {
344
- handleError(ctx, error);
345
- }
346
- },
347
-
348
- async validateApplePayMerchant(ctx) {
349
- try {
350
- const settings = await getPayoneService(strapi).getSettings();
351
- const applePayConfig = settings?.applePayConfig || {};
352
-
353
- const params =
354
- ctx.request.body || ctx.request.body?.data || ctx.request?.data;
355
-
356
- if (!params) {
357
- throw new Error("Request body is missing");
358
- }
359
-
360
- if (!params.domain && !params.domainName) {
361
- params.domain = ctx.request.hostname || ctx.request.host || "localhost";
362
- params.domainName = params.domain;
363
- } else if (params.domain && !params.domainName) {
364
- params.domainName = params.domain;
365
- } else if (params.domainName && !params.domain) {
366
- params.domain = params.domainName;
367
- }
368
-
369
- if (!params.displayName) {
370
- params.displayName = settings?.merchantName || "Store";
371
- }
372
-
373
- if (!params.currency) {
374
- params.currency = applePayConfig.currencyCode || "EUR";
375
- }
376
- if (!params.countryCode) {
377
- params.countryCode = applePayConfig.countryCode || "DE";
378
- }
379
-
380
- let result =
381
- await getPayoneService(strapi).validateApplePayMerchant(params);
382
-
383
- if (!result) {
384
- throw new Error(
385
- "Merchant validation returned null. Please check your Payone Apple Pay configuration."
386
- );
387
- }
388
-
389
- ctx.body = result;
390
- } catch (error) {
391
- const errorStatus =
392
- error.status || (error.message?.includes("403") ? 403 : 500);
393
-
394
- if (
395
- error.response ||
396
- errorStatus === 403 ||
397
- errorStatus === 401 ||
398
- errorStatus >= 500
399
- ) {
400
- strapi.log.error("[Apple Pay] Controller error:", {
401
- status: errorStatus,
402
- message: error.message
403
- });
404
- }
405
-
406
- let errorMessage =
407
- error.message || "Apple Pay merchant validation failed";
408
- let errorDetails =
409
- "Please check your Payone Apple Pay configuration in PMI (CONFIGURATION → PAYMENT PORTALS → [Your Portal] → Apple Pay). Ensure that Merchant ID (mid) is correctly configured and Apple Pay is enabled for your portal.";
410
-
411
- if (errorStatus === 403 || error.message?.includes("403")) {
412
- errorDetails =
413
- "403 Forbidden: Authentication failed with Payone. " +
414
- "Please check: 1) Your Payone credentials (aid, portalid, mid, key) in plugin settings, " +
415
- "2) Mode is set to 'live' (Apple Pay only works in live mode), " +
416
- "3) Your domain is registered with Payone Merchant Services, " +
417
- "4) Merchant ID (mid) matches your merchantIdentifier in PMI, " +
418
- "5) Apple Pay is enabled for your portal in PMI.";
419
- }
420
-
421
- ctx.status = errorStatus;
422
- ctx.body = {
423
- error: {
424
- status: errorStatus,
425
- name: error.name || "Error",
426
- message: errorMessage,
427
- details: errorDetails
428
- }
429
- };
430
- }
431
- },
432
-
433
- async handleTransactionStatus(ctx) {
434
- try {
435
- const notificationData = ctx.request.body || {};
436
- await getPayoneService(strapi).processTransactionStatus(notificationData);
437
- console.warn("[Payone] Notification Status", {
438
- ip: ctx.request.ip
439
- });
440
- } catch (error) {
441
- strapi.log.error("[Payone TransactionStatus] Error:", error);
442
- }
443
-
444
- ctx.status = 200;
445
- ctx.body = "TSOK";
446
- ctx.type = "text/plain";
447
- },
448
-
449
- async hostedTokenizationJwt(ctx) {
450
- try {
451
- const result =
452
- await getNewHostedTokenizationService(
453
- strapi
454
- ).createHostedTokenizationJwt();
455
- ctx.body = {
456
- data: {
457
- token: result?.token || null,
458
- expirationDate: result?.expirationDate || null
459
- }
460
- };
461
- } catch (error) {
462
- handleError(ctx, error);
463
- }
464
- },
465
-
466
- async hash384(ctx) {
467
- try {
468
- // Get settings
469
- const settings = await getPayoneService(strapi).getSettings();
470
- const hmacKey = settings?.key || "";
471
-
472
- if (!hmacKey) {
473
- ctx.throw(400, "Payone key is not configured in plugin settings");
474
- }
475
-
476
- // Validate required settings
477
- if (
478
- !settings?.aid ||
479
- !settings?.mid ||
480
- !settings?.portalid ||
481
- !settings?.mode
482
- ) {
483
- ctx.throw(
484
- 400,
485
- "Required settings (aid, mid, portalid, mode) are not configured"
486
- );
487
- }
488
-
489
- // Construct the string from settings values
490
- // Format: ${aid}UTF-8${mid}${mode}${portalid}creditcardcheckJSONyes
491
- const textToHash = `${settings.aid}UTF-8${settings.mid}${settings.mode}${settings.portalid}creditcardcheckJSONyes`;
492
-
493
- // Create HMAC SHA-384 hash
494
- const hmac = crypto.createHmac("sha384", hmacKey);
495
- hmac.update(textToHash);
496
- const hash = hmac.digest("hex");
497
-
498
- ctx.body = {
499
- hash,
500
- text: textToHash
501
- };
502
- } catch (error) {
503
- handleError(ctx, error);
504
- }
505
- }
506
- });
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+ const PLUGIN_NAME = "strapi-plugin-payone-provider";
5
+ const {
6
+ rowsToCsv,
7
+ csvToRows,
8
+ TRANSACTION_ATTRS
9
+ } = require("../utils/csvTransactions");
10
+
11
+ const getPayoneService = (strapi) => {
12
+ return strapi.plugin(PLUGIN_NAME).service("payone");
13
+ };
14
+
15
+ const getNewHostedTokenizationService = (strapi) => {
16
+ return strapi.plugin(PLUGIN_NAME).service("newHostedTokenizationService");
17
+ };
18
+
19
+ const buildFiltersFromQuery = (rawFilters = {}) => {
20
+ const filters = {};
21
+ if (rawFilters && typeof rawFilters === "object") {
22
+ for (const [key, value] of Object.entries(rawFilters)) {
23
+ const v = value == null ? "" : String(value).trim();
24
+ if (v !== "" && v.toLowerCase() !== "all") filters[key] = value;
25
+ }
26
+ }
27
+ return filters;
28
+ };
29
+
30
+ const handleError = (ctx, error) => {
31
+ const status = error.status || error.response?.status || 500;
32
+ const message = error.message || "Internal server error";
33
+
34
+ if (status >= 400) {
35
+ console.log("[Payone] Controller error:", {
36
+ status,
37
+ message,
38
+ error: error.stack || error
39
+ });
40
+ }
41
+
42
+ ctx.status = status;
43
+ ctx.body = {
44
+ error: {
45
+ status,
46
+ message,
47
+ name: error.name || "Error"
48
+ }
49
+ };
50
+ };
51
+
52
+ const hideKey = (settings) => {
53
+ if (settings && settings.key) {
54
+ settings.key = "***HIDDEN***";
55
+ }
56
+ if (settings && settings.serverApiSecret) {
57
+ settings.serverApiSecret = "***HIDDEN***";
58
+ }
59
+ return settings;
60
+ };
61
+
62
+ module.exports = ({ strapi }) => ({
63
+ async getSettings(ctx) {
64
+ try {
65
+ const settings = await getPayoneService(strapi).getSettings();
66
+ ctx.body = {
67
+ ...hideKey(settings || {})
68
+ };
69
+ } catch (error) {
70
+ handleError(ctx, error);
71
+ }
72
+ },
73
+
74
+ async getPublicSettings(ctx) {
75
+ try {
76
+ const settings = await getPayoneService(strapi).getSettings();
77
+ ctx.body = {
78
+ data: {
79
+ mid: settings?.mid || null,
80
+ mode: settings?.mode || null,
81
+ domainName: settings?.domainName || null,
82
+ displayName: settings?.displayName || null,
83
+ portalid: settings?.portalid || null,
84
+ accountId: settings?.aid || null,
85
+ paymentMethods: {
86
+ creditCard: settings?.enableCreditCard,
87
+ paypal: settings?.enablePayPal,
88
+ googlePay: settings?.enableGooglePay,
89
+ applePay: settings?.enableApplePay,
90
+ sofort: settings?.enableSofort,
91
+ sepa: settings?.enableSepaDirectDebit
92
+ }
93
+ }
94
+ };
95
+ } catch (error) {
96
+ handleError(ctx, error);
97
+ }
98
+ },
99
+
100
+ async updateSettings(ctx) {
101
+ try {
102
+ const bodyData = ctx.request.body?.data || ctx.request.body;
103
+
104
+ if (!bodyData || typeof bodyData !== "object") {
105
+ ctx.throw(400, "Invalid request body");
106
+ }
107
+
108
+ const currentSettings = await getPayoneService(strapi).getSettings();
109
+
110
+ if (bodyData.key === "***HIDDEN***" || !bodyData.key) {
111
+ bodyData.key = currentSettings?.key;
112
+ }
113
+ if (
114
+ bodyData.serverApiSecret === "***HIDDEN***" ||
115
+ !bodyData.serverApiSecret
116
+ ) {
117
+ bodyData.serverApiSecret = currentSettings?.serverApiSecret;
118
+ }
119
+
120
+ const settings = await getPayoneService(strapi).updateSettings(bodyData);
121
+ ctx.body = { ...hideKey(settings) };
122
+ } catch (error) {
123
+ handleError(ctx, error);
124
+ }
125
+ },
126
+
127
+ async preauthorization(ctx) {
128
+ try {
129
+ const params = ctx.request.body?.data || ctx.request.body;
130
+ if (!params || typeof params !== "object") {
131
+ ctx.throw(400, "Invalid request body");
132
+ }
133
+
134
+ const result = await getPayoneService(strapi).preauthorization(params);
135
+ ctx.body = result;
136
+ } catch (error) {
137
+ handleError(ctx, error);
138
+ }
139
+ },
140
+
141
+ async authorization(ctx) {
142
+ try {
143
+ const params = ctx.request.body?.data || ctx.request.body;
144
+ if (!params || typeof params !== "object") ctx.throw(400, "Invalid request body");
145
+
146
+ const result = await getPayoneService(strapi).authorization(params);
147
+ ctx.body = result;
148
+ } catch (error) {
149
+ handleError(ctx, error);
150
+ }
151
+ },
152
+
153
+ async capture(ctx) {
154
+ try {
155
+ const params = ctx.request.body?.data || ctx.request.body;
156
+
157
+ if (!params || typeof params !== "object") {
158
+ ctx.throw(400, "Invalid request body");
159
+ }
160
+
161
+ const result = await getPayoneService(strapi).capture(params);
162
+ ctx.body = result;
163
+ } catch (error) {
164
+ handleError(ctx, error);
165
+ }
166
+ },
167
+
168
+ async refund(ctx) {
169
+ try {
170
+ const params = ctx.request.body?.data || ctx.request.body;
171
+
172
+ if (!params || typeof params !== "object") {
173
+ ctx.throw(400, "Invalid request body");
174
+ }
175
+
176
+ const result = await getPayoneService(strapi).refund(params);
177
+ ctx.body = result;
178
+ } catch (error) {
179
+ handleError(ctx, error);
180
+ }
181
+ },
182
+
183
+ async getTransactionHistory(ctx) {
184
+ try {
185
+ const { filters: rawFilters = {}, pagination = {} } = ctx.query || {};
186
+ const page = parseInt(pagination.page || "1", 10);
187
+ const pageSize = parseInt(pagination.pageSize || "10", 10);
188
+
189
+ const filters = {};
190
+ if (rawFilters && typeof rawFilters === "object") {
191
+ for (const [key, value] of Object.entries(rawFilters)) {
192
+ const v = value == null ? "" : String(value).trim();
193
+ if (v !== "" && v.toLowerCase() !== "all") {
194
+ filters[key] = value;
195
+ }
196
+ }
197
+ }
198
+
199
+ const result = await getPayoneService(strapi).getTransactionHistory({
200
+ filters,
201
+ pagination: { page, pageSize }
202
+ });
203
+
204
+ ctx.body = {
205
+ data: result.data || [],
206
+ meta: {
207
+ pagination: result.pagination
208
+ }
209
+ };
210
+ } catch (error) {
211
+ handleError(ctx, error);
212
+ }
213
+ },
214
+
215
+ async exportTransactions(ctx) {
216
+ try {
217
+ const {
218
+ filters: rawFilters = {},
219
+ format = "json",
220
+ sort_by,
221
+ sort_order
222
+ } = ctx.query || {};
223
+ const filters = buildFiltersFromQuery(rawFilters);
224
+ const data = await getPayoneService(strapi).getTransactionsForExport({
225
+ filters,
226
+ sort_by: sort_by || "createdAt",
227
+ sort_order: sort_order || "desc"
228
+ });
229
+ const rows = Array.isArray(data) ? data : [];
230
+ const fmt = (format || "json").toLowerCase();
231
+
232
+ if (fmt === "csv") {
233
+ ctx.set("Content-Type", "text/csv; charset=utf-8");
234
+ ctx.set(
235
+ "Content-Disposition",
236
+ 'attachment; filename="transactions.csv"'
237
+ );
238
+ ctx.body = rowsToCsv(rows, TRANSACTION_ATTRS);
239
+ return;
240
+ }
241
+
242
+ ctx.set("Content-Type", "application/json");
243
+ ctx.set(
244
+ "Content-Disposition",
245
+ 'attachment; filename="transactions.json"'
246
+ );
247
+ ctx.body = rows;
248
+ } catch (error) {
249
+ handleError(ctx, error);
250
+ }
251
+ },
252
+
253
+ async importTransactions(ctx) {
254
+ try {
255
+ const body = ctx.request.body;
256
+ if (!body || typeof body !== "object")
257
+ ctx.throw(400, "Request body must be JSON");
258
+
259
+ let rows = [];
260
+ if (Array.isArray(body)) {
261
+ rows = body;
262
+ } else if (Array.isArray(body.data)) {
263
+ rows = body.data;
264
+ } else if (body.format === "csv" && typeof body.data === "string") {
265
+ rows = csvToRows(body.data);
266
+ } else {
267
+ ctx.throw(
268
+ 400,
269
+ "Body must be an array, { data: array }, or { format: 'csv', data: csvString }"
270
+ );
271
+ }
272
+
273
+ if (rows.length === 0) {
274
+ ctx.body = {
275
+ imported: 0,
276
+ failed: 0,
277
+ errors: [],
278
+ message: "No rows to import"
279
+ };
280
+ return;
281
+ }
282
+
283
+ const result = await getPayoneService(strapi).importTransactions(rows);
284
+ ctx.body = {
285
+ ...result,
286
+ message: `Imported ${result.imported}, failed ${result.failed}`
287
+ };
288
+ } catch (error) {
289
+ handleError(ctx, error);
290
+ }
291
+ },
292
+
293
+ async testConnection(ctx) {
294
+ try {
295
+ const result = await getPayoneService(strapi).testConnection();
296
+ ctx.body = result || {};
297
+ } catch (error) {
298
+ handleError(ctx, error);
299
+ }
300
+ },
301
+
302
+ async handle3DSCallback(ctx) {
303
+ try {
304
+ const isGetRequest = ctx.request.method === "GET";
305
+ const currentPath = ctx.request.url;
306
+
307
+ let resultType = "callback";
308
+ if (currentPath.includes("/success")) {
309
+ resultType = "success";
310
+ } else if (currentPath.includes("/error")) {
311
+ resultType = "error";
312
+ } else if (currentPath.includes("/back")) {
313
+ resultType = "cancelled";
314
+ }
315
+
316
+ const callbackData = isGetRequest
317
+ ? ctx.query
318
+ : ctx.request.body || ctx.request.body?.data || ctx.request?.data;
319
+
320
+ const result = await getPayoneService(strapi).handle3DSCallback(
321
+ callbackData,
322
+ resultType
323
+ );
324
+
325
+ if (isGetRequest) {
326
+ const isContentUI = currentPath.includes("/content-ui");
327
+ const basePath = isContentUI ? "/content-ui" : "/admin";
328
+ const pluginPath = "/plugins/strapi-plugin-payone-provider";
329
+
330
+ const queryParams = new URLSearchParams();
331
+ queryParams.set("3ds", resultType);
332
+ if (result.txid) queryParams.set("txid", result.txid);
333
+ if (result.status) queryParams.set("status", result.status);
334
+
335
+ const redirectUrl = `${basePath}${pluginPath}?${queryParams.toString()}`;
336
+ return ctx.redirect(redirectUrl);
337
+ }
338
+
339
+ ctx.body = result;
340
+ } catch (error) {
341
+ handleError(ctx, error);
342
+ }
343
+ },
344
+
345
+ async validateApplePayMerchant(ctx) {
346
+ try {
347
+ const settings = await getPayoneService(strapi).getSettings();
348
+ const applePayConfig = settings?.applePayConfig || {};
349
+
350
+ const params =
351
+ ctx.request.body || ctx.request.body?.data || ctx.request?.data;
352
+
353
+ if (!params) {
354
+ throw new Error("Request body is missing");
355
+ }
356
+
357
+ if (!params.domain && !params.domainName) {
358
+ params.domain = ctx.request.hostname || ctx.request.host || "localhost";
359
+ params.domainName = params.domain;
360
+ } else if (params.domain && !params.domainName) {
361
+ params.domainName = params.domain;
362
+ } else if (params.domainName && !params.domain) {
363
+ params.domain = params.domainName;
364
+ }
365
+
366
+ if (!params.displayName) {
367
+ params.displayName = settings?.merchantName || "Store";
368
+ }
369
+
370
+ if (!params.currency) {
371
+ params.currency = applePayConfig.currencyCode || "EUR";
372
+ }
373
+ if (!params.countryCode) {
374
+ params.countryCode = applePayConfig.countryCode || "DE";
375
+ }
376
+
377
+ let result =
378
+ await getPayoneService(strapi).validateApplePayMerchant(params);
379
+
380
+ if (!result) {
381
+ throw new Error(
382
+ "Merchant validation returned null. Please check your Payone Apple Pay configuration."
383
+ );
384
+ }
385
+
386
+ ctx.body = result;
387
+ } catch (error) {
388
+ const errorStatus =
389
+ error.status || (error.message?.includes("403") ? 403 : 500);
390
+
391
+ if (
392
+ error.response ||
393
+ errorStatus === 403 ||
394
+ errorStatus === 401 ||
395
+ errorStatus >= 500
396
+ ) {
397
+ strapi.log.error("[Apple Pay] Controller error:", {
398
+ status: errorStatus,
399
+ message: error.message
400
+ });
401
+ }
402
+
403
+ let errorMessage =
404
+ error.message || "Apple Pay merchant validation failed";
405
+ let errorDetails =
406
+ "Please check your Payone Apple Pay configuration in PMI (CONFIGURATION → PAYMENT PORTALS → [Your Portal] → Apple Pay). Ensure that Merchant ID (mid) is correctly configured and Apple Pay is enabled for your portal.";
407
+
408
+ if (errorStatus === 403 || error.message?.includes("403")) {
409
+ errorDetails =
410
+ "403 Forbidden: Authentication failed with Payone. " +
411
+ "Please check: 1) Your Payone credentials (aid, portalid, mid, key) in plugin settings, " +
412
+ "2) Mode is set to 'live' (Apple Pay only works in live mode), " +
413
+ "3) Your domain is registered with Payone Merchant Services, " +
414
+ "4) Merchant ID (mid) matches your merchantIdentifier in PMI, " +
415
+ "5) Apple Pay is enabled for your portal in PMI.";
416
+ }
417
+
418
+ ctx.status = errorStatus;
419
+ ctx.body = {
420
+ error: {
421
+ status: errorStatus,
422
+ name: error.name || "Error",
423
+ message: errorMessage,
424
+ details: errorDetails
425
+ }
426
+ };
427
+ }
428
+ },
429
+
430
+ async handleTransactionStatus(ctx) {
431
+ try {
432
+ const notificationData = ctx.request.body || {};
433
+ await getPayoneService(strapi).processTransactionStatus(notificationData);
434
+ console.warn("[Payone] Notification Status", {
435
+ ip: ctx.request.ip
436
+ });
437
+ } catch (error) {
438
+ strapi.log.error("[Payone TransactionStatus] Error:", error);
439
+ }
440
+
441
+ ctx.status = 200;
442
+ ctx.body = "TSOK";
443
+ ctx.type = "text/plain";
444
+ },
445
+
446
+ async hostedTokenizationJwt(ctx) {
447
+ try {
448
+ const result =
449
+ await getNewHostedTokenizationService(
450
+ strapi
451
+ ).createHostedTokenizationJwt();
452
+ ctx.body = {
453
+ data: {
454
+ token: result?.token || null,
455
+ expirationDate: result?.expirationDate || null
456
+ }
457
+ };
458
+ } catch (error) {
459
+ handleError(ctx, error);
460
+ }
461
+ },
462
+
463
+ async hash384(ctx) {
464
+ try {
465
+ // Get settings
466
+ const settings = await getPayoneService(strapi).getSettings();
467
+ const hmacKey = settings?.key || "";
468
+
469
+ if (!hmacKey) {
470
+ ctx.throw(400, "Payone key is not configured in plugin settings");
471
+ }
472
+
473
+ // Validate required settings
474
+ if (
475
+ !settings?.aid ||
476
+ !settings?.mid ||
477
+ !settings?.portalid ||
478
+ !settings?.mode
479
+ ) {
480
+ ctx.throw(
481
+ 400,
482
+ "Required settings (aid, mid, portalid, mode) are not configured"
483
+ );
484
+ }
485
+
486
+ // Construct the string from settings values
487
+ // Format: ${aid}UTF-8${mid}${mode}${portalid}creditcardcheckJSONyes
488
+ const textToHash = `${settings.aid}UTF-8${settings.mid}${settings.mode}${settings.portalid}creditcardcheckJSONyes`;
489
+
490
+ // Create HMAC SHA-384 hash
491
+ const hmac = crypto.createHmac("sha384", hmacKey);
492
+ hmac.update(textToHash);
493
+ const hash = hmac.digest("hex");
494
+
495
+ ctx.body = {
496
+ hash,
497
+ text: textToHash
498
+ };
499
+ } catch (error) {
500
+ handleError(ctx, error);
501
+ }
502
+ }
503
+ });