strapi-plugin-payone-provider 5.7.25 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-payone-provider",
3
- "version": "5.7.25",
3
+ "version": "5.8.26",
4
4
  "description": "Strapi plugin for Payone payment gateway integration",
5
5
  "license": "MIT",
6
6
  "maintainers": [
@@ -2,7 +2,11 @@
2
2
 
3
3
  const crypto = require("crypto");
4
4
  const PLUGIN_NAME = "strapi-plugin-payone-provider";
5
- const { rowsToCsv, csvToRows, TRANSACTION_ATTRS } = require("../utils/csvTransactions");
5
+ const {
6
+ rowsToCsv,
7
+ csvToRows,
8
+ TRANSACTION_ATTRS
9
+ } = require("../utils/csvTransactions");
6
10
 
7
11
  const getPayoneService = (strapi) => {
8
12
  return strapi.plugin(PLUGIN_NAME).service("payone");
@@ -84,8 +88,8 @@ module.exports = ({ strapi }) => ({
84
88
  googlePay: settings?.enableGooglePay,
85
89
  applePay: settings?.enableApplePay,
86
90
  sofort: settings?.enableSofort,
87
- sepa: settings?.enableSepaDirectDebit,
88
- },
91
+ sepa: settings?.enableSepaDirectDebit
92
+ }
89
93
  }
90
94
  };
91
95
  } catch (error) {
@@ -97,7 +101,7 @@ module.exports = ({ strapi }) => ({
97
101
  try {
98
102
  const bodyData = ctx.request.body?.data || ctx.request.body;
99
103
 
100
- if (!bodyData || typeof bodyData !== 'object') {
104
+ if (!bodyData || typeof bodyData !== "object") {
101
105
  ctx.throw(400, "Invalid request body");
102
106
  }
103
107
 
@@ -106,7 +110,10 @@ module.exports = ({ strapi }) => ({
106
110
  if (bodyData.key === "***HIDDEN***" || !bodyData.key) {
107
111
  bodyData.key = currentSettings?.key;
108
112
  }
109
- if (bodyData.serverApiSecret === "***HIDDEN***" || !bodyData.serverApiSecret) {
113
+ if (
114
+ bodyData.serverApiSecret === "***HIDDEN***" ||
115
+ !bodyData.serverApiSecret
116
+ ) {
110
117
  bodyData.serverApiSecret = currentSettings?.serverApiSecret;
111
118
  }
112
119
 
@@ -120,7 +127,7 @@ module.exports = ({ strapi }) => ({
120
127
  async preauthorization(ctx) {
121
128
  try {
122
129
  const params = ctx.request.body?.data || ctx.request.body;
123
- if (!params || typeof params !== 'object') {
130
+ if (!params || typeof params !== "object") {
124
131
  ctx.throw(400, "Invalid request body");
125
132
  }
126
133
 
@@ -134,10 +141,7 @@ module.exports = ({ strapi }) => ({
134
141
  async authorization(ctx) {
135
142
  try {
136
143
  const params = ctx.request.body?.data || ctx.request.body;
137
-
138
- if (!params || typeof params !== 'object') {
139
- ctx.throw(400, "Invalid request body");
140
- }
144
+ if (!params || typeof params !== "object") ctx.throw(400, "Invalid request body");
141
145
 
142
146
  const result = await getPayoneService(strapi).authorization(params);
143
147
  ctx.body = result;
@@ -150,7 +154,7 @@ module.exports = ({ strapi }) => ({
150
154
  try {
151
155
  const params = ctx.request.body?.data || ctx.request.body;
152
156
 
153
- if (!params || typeof params !== 'object') {
157
+ if (!params || typeof params !== "object") {
154
158
  ctx.throw(400, "Invalid request body");
155
159
  }
156
160
 
@@ -165,7 +169,7 @@ module.exports = ({ strapi }) => ({
165
169
  try {
166
170
  const params = ctx.request.body?.data || ctx.request.body;
167
171
 
168
- if (!params || typeof params !== 'object') {
172
+ if (!params || typeof params !== "object") {
169
173
  ctx.throw(400, "Invalid request body");
170
174
  }
171
175
 
@@ -201,7 +205,7 @@ module.exports = ({ strapi }) => ({
201
205
  data: result.data || [],
202
206
  meta: {
203
207
  pagination: result.pagination
204
- },
208
+ }
205
209
  };
206
210
  } catch (error) {
207
211
  handleError(ctx, error);
@@ -210,25 +214,36 @@ module.exports = ({ strapi }) => ({
210
214
 
211
215
  async exportTransactions(ctx) {
212
216
  try {
213
- const { filters: rawFilters = {}, format = "json", sort_by, sort_order } = ctx.query || {};
217
+ const {
218
+ filters: rawFilters = {},
219
+ format = "json",
220
+ sort_by,
221
+ sort_order
222
+ } = ctx.query || {};
214
223
  const filters = buildFiltersFromQuery(rawFilters);
215
224
  const data = await getPayoneService(strapi).getTransactionsForExport({
216
225
  filters,
217
226
  sort_by: sort_by || "createdAt",
218
- sort_order: sort_order || "desc",
227
+ sort_order: sort_order || "desc"
219
228
  });
220
229
  const rows = Array.isArray(data) ? data : [];
221
230
  const fmt = (format || "json").toLowerCase();
222
231
 
223
232
  if (fmt === "csv") {
224
233
  ctx.set("Content-Type", "text/csv; charset=utf-8");
225
- ctx.set("Content-Disposition", 'attachment; filename="transactions.csv"');
234
+ ctx.set(
235
+ "Content-Disposition",
236
+ 'attachment; filename="transactions.csv"'
237
+ );
226
238
  ctx.body = rowsToCsv(rows, TRANSACTION_ATTRS);
227
239
  return;
228
240
  }
229
241
 
230
242
  ctx.set("Content-Type", "application/json");
231
- ctx.set("Content-Disposition", 'attachment; filename="transactions.json"');
243
+ ctx.set(
244
+ "Content-Disposition",
245
+ 'attachment; filename="transactions.json"'
246
+ );
232
247
  ctx.body = rows;
233
248
  } catch (error) {
234
249
  handleError(ctx, error);
@@ -238,7 +253,8 @@ module.exports = ({ strapi }) => ({
238
253
  async importTransactions(ctx) {
239
254
  try {
240
255
  const body = ctx.request.body;
241
- if (!body || typeof body !== "object") ctx.throw(400, "Request body must be JSON");
256
+ if (!body || typeof body !== "object")
257
+ ctx.throw(400, "Request body must be JSON");
242
258
 
243
259
  let rows = [];
244
260
  if (Array.isArray(body)) {
@@ -248,18 +264,26 @@ module.exports = ({ strapi }) => ({
248
264
  } else if (body.format === "csv" && typeof body.data === "string") {
249
265
  rows = csvToRows(body.data);
250
266
  } else {
251
- ctx.throw(400, "Body must be an array, { data: array }, or { format: 'csv', data: csvString }");
267
+ ctx.throw(
268
+ 400,
269
+ "Body must be an array, { data: array }, or { format: 'csv', data: csvString }"
270
+ );
252
271
  }
253
272
 
254
273
  if (rows.length === 0) {
255
- ctx.body = { imported: 0, failed: 0, errors: [], message: "No rows to import" };
274
+ ctx.body = {
275
+ imported: 0,
276
+ failed: 0,
277
+ errors: [],
278
+ message: "No rows to import"
279
+ };
256
280
  return;
257
281
  }
258
282
 
259
283
  const result = await getPayoneService(strapi).importTransactions(rows);
260
284
  ctx.body = {
261
285
  ...result,
262
- message: `Imported ${result.imported}, failed ${result.failed}`,
286
+ message: `Imported ${result.imported}, failed ${result.failed}`
263
287
  };
264
288
  } catch (error) {
265
289
  handleError(ctx, error);
@@ -291,19 +315,22 @@ module.exports = ({ strapi }) => ({
291
315
 
292
316
  const callbackData = isGetRequest
293
317
  ? ctx.query
294
- : (ctx.request.body || ctx.request.body?.data || ctx.request?.data);
318
+ : ctx.request.body || ctx.request.body?.data || ctx.request?.data;
295
319
 
296
- const result = await getPayoneService(strapi).handle3DSCallback(callbackData, resultType);
320
+ const result = await getPayoneService(strapi).handle3DSCallback(
321
+ callbackData,
322
+ resultType
323
+ );
297
324
 
298
325
  if (isGetRequest) {
299
- const isContentUI = currentPath.includes('/content-ui');
300
- const basePath = isContentUI ? '/content-ui' : '/admin';
301
- const pluginPath = '/plugins/strapi-plugin-payone-provider';
326
+ const isContentUI = currentPath.includes("/content-ui");
327
+ const basePath = isContentUI ? "/content-ui" : "/admin";
328
+ const pluginPath = "/plugins/strapi-plugin-payone-provider";
302
329
 
303
330
  const queryParams = new URLSearchParams();
304
- queryParams.set('3ds', resultType);
305
- if (result.txid) queryParams.set('txid', result.txid);
306
- if (result.status) queryParams.set('status', result.status);
331
+ queryParams.set("3ds", resultType);
332
+ if (result.txid) queryParams.set("txid", result.txid);
333
+ if (result.status) queryParams.set("status", result.status);
307
334
 
308
335
  const redirectUrl = `${basePath}${pluginPath}?${queryParams.toString()}`;
309
336
  return ctx.redirect(redirectUrl);
@@ -320,14 +347,15 @@ module.exports = ({ strapi }) => ({
320
347
  const settings = await getPayoneService(strapi).getSettings();
321
348
  const applePayConfig = settings?.applePayConfig || {};
322
349
 
323
- const params = ctx.request.body || ctx.request.body?.data || ctx.request?.data;
350
+ const params =
351
+ ctx.request.body || ctx.request.body?.data || ctx.request?.data;
324
352
 
325
353
  if (!params) {
326
354
  throw new Error("Request body is missing");
327
355
  }
328
356
 
329
357
  if (!params.domain && !params.domainName) {
330
- params.domain = ctx.request.hostname || ctx.request.host || 'localhost';
358
+ params.domain = ctx.request.hostname || ctx.request.host || "localhost";
331
359
  params.domainName = params.domain;
332
360
  } else if (params.domain && !params.domainName) {
333
361
  params.domainName = params.domain;
@@ -346,28 +374,40 @@ module.exports = ({ strapi }) => ({
346
374
  params.countryCode = applePayConfig.countryCode || "DE";
347
375
  }
348
376
 
349
- let result = await getPayoneService(strapi).validateApplePayMerchant(params);
377
+ let result =
378
+ await getPayoneService(strapi).validateApplePayMerchant(params);
350
379
 
351
380
  if (!result) {
352
- throw new Error("Merchant validation returned null. Please check your Payone Apple Pay configuration.");
381
+ throw new Error(
382
+ "Merchant validation returned null. Please check your Payone Apple Pay configuration."
383
+ );
353
384
  }
354
385
 
355
386
  ctx.body = result;
356
387
  } catch (error) {
357
- const errorStatus = error.status || (error.message?.includes('403') ? 403 : 500);
358
-
359
- if (error.response || errorStatus === 403 || errorStatus === 401 || errorStatus >= 500) {
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
+ ) {
360
397
  strapi.log.error("[Apple Pay] Controller error:", {
361
398
  status: errorStatus,
362
399
  message: error.message
363
400
  });
364
401
  }
365
402
 
366
- let errorMessage = error.message || "Apple Pay merchant validation failed";
367
- let errorDetails = "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.";
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.";
368
407
 
369
- if (errorStatus === 403 || error.message?.includes('403')) {
370
- errorDetails = "403 Forbidden: Authentication failed with Payone. " +
408
+ if (errorStatus === 403 || error.message?.includes("403")) {
409
+ errorDetails =
410
+ "403 Forbidden: Authentication failed with Payone. " +
371
411
  "Please check: 1) Your Payone credentials (aid, portalid, mid, key) in plugin settings, " +
372
412
  "2) Mode is set to 'live' (Apple Pay only works in live mode), " +
373
413
  "3) Your domain is registered with Payone Merchant Services, " +
@@ -392,7 +432,7 @@ module.exports = ({ strapi }) => ({
392
432
  const notificationData = ctx.request.body || {};
393
433
  await getPayoneService(strapi).processTransactionStatus(notificationData);
394
434
  console.warn("[Payone] Notification Status", {
395
- ip: ctx.request.ip,
435
+ ip: ctx.request.ip
396
436
  });
397
437
  } catch (error) {
398
438
  strapi.log.error("[Payone TransactionStatus] Error:", error);
@@ -405,12 +445,15 @@ module.exports = ({ strapi }) => ({
405
445
 
406
446
  async hostedTokenizationJwt(ctx) {
407
447
  try {
408
- const result = await getNewHostedTokenizationService(strapi).createHostedTokenizationJwt();
448
+ const result =
449
+ await getNewHostedTokenizationService(
450
+ strapi
451
+ ).createHostedTokenizationJwt();
409
452
  ctx.body = {
410
453
  data: {
411
454
  token: result?.token || null,
412
- expirationDate: result?.expirationDate || null,
413
- },
455
+ expirationDate: result?.expirationDate || null
456
+ }
414
457
  };
415
458
  } catch (error) {
416
459
  handleError(ctx, error);
@@ -428,8 +471,16 @@ module.exports = ({ strapi }) => ({
428
471
  }
429
472
 
430
473
  // Validate required settings
431
- if (!settings?.aid || !settings?.mid || !settings?.portalid || !settings?.mode) {
432
- ctx.throw(400, "Required settings (aid, mid, portalid, mode) are not configured");
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
+ );
433
484
  }
434
485
 
435
486
  // Construct the string from settings values
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const axios = require("axios");
4
- const { buildClientRequestParams, toFormData } = require("../utils/requestBuilder");
4
+ const { buildClientRequestParams, toFormData, is3dsViable } = require("../utils/requestBuilder");
5
5
  const { addPaymentMethodParams } = require("../utils/paymentMethodParams");
6
6
  const { parseResponse, extractTxId, requires3DSRedirect, get3DSRedirectUrl } = require("../utils/responseParser");
7
7
  const { getSettings, validateSettings } = require("./settingsService");
@@ -37,14 +37,11 @@ const getInvoiceIdObject = (invoiceid) => {
37
37
  const sendRequest = async (strapi, params) => {
38
38
  try {
39
39
  const settings = await getSettings(strapi);
40
-
41
- if (!validateSettings(settings)) {
42
- throw new Error("Payone settings not configured");
43
- }
40
+ if (!validateSettings(settings)) throw new Error("Payone settings not configured");
44
41
 
45
42
  const requestParams = buildClientRequestParams(settings, params, strapi.log);
46
- const formData = toFormData(requestParams);
47
43
 
44
+ const formData = toFormData(requestParams);
48
45
  const response = await axios.post(POST_GATEWAY_URL, formData, {
49
46
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
50
47
  timeout: 30000
@@ -60,7 +57,6 @@ const sendRequest = async (strapi, params) => {
60
57
  responseData.requires3DSRedirect = true;
61
58
  responseData.redirectUrl = redirectUrl;
62
59
  responseData.is3DSRequired = is3DSRequiredError;
63
-
64
60
  }
65
61
 
66
62
  const errorMessage = responseData?.Error?.ErrorMessage || null;
@@ -96,8 +92,7 @@ const sendRequest = async (strapi, params) => {
96
92
  };
97
93
 
98
94
  const preauthorization = async (strapi, params) => {
99
-
100
- const requiredParams = {
95
+ const requestParams = {
101
96
  request: "preauthorization",
102
97
  clearingtype: params.clearingtype,
103
98
  amount: params.amount,
@@ -115,14 +110,20 @@ const preauthorization = async (strapi, params) => {
115
110
  ...params
116
111
  };
117
112
 
113
+ const settings = await getSettings(strapi);
118
114
 
119
- const updatedParams = addPaymentMethodParams(requiredParams, strapi.log);
120
- return await sendRequest(strapi, updatedParams);
115
+ // Happnes only when CC-payment and 3DS is enabled and is Pre/Authorization request
116
+ if (is3dsViable(params, settings)) {
117
+ requestParams = await perform3DSCheck(strapi, requestParams);
118
+ if (!requestParams) throw new Error('3DS check failed');
119
+ }
120
+
121
+ requestParams = addPaymentMethodParams(requestParams, strapi.log);
122
+ return await sendRequest(strapi, requestParams);
121
123
  };
122
124
 
123
125
  const authorization = async (strapi, params) => {
124
-
125
- const requiredParams = {
126
+ let requestParams = {
126
127
  request: "authorization",
127
128
  clearingtype: params.clearingtype,
128
129
  reference: params.reference,
@@ -138,8 +139,16 @@ const authorization = async (strapi, params) => {
138
139
  ...params
139
140
  };
140
141
 
141
- const updatedParams = addPaymentMethodParams(requiredParams, strapi.log);
142
- return await sendRequest(strapi, updatedParams);
142
+ const settings = await getSettings(strapi);
143
+
144
+ // Happnes only when CC-payment and 3DS is enabled and is Pre/Authorization request
145
+ if (is3dsViable(params, settings)) {
146
+ requestParams = await perform3DSCheck(strapi, requestParams);
147
+ if (!requestParams) throw new Error('3DS check failed');
148
+ }
149
+
150
+ requestParams = addPaymentMethodParams(requestParams, strapi.log);
151
+ return await sendRequest(strapi, requestParams);
143
152
  };
144
153
 
145
154
  const capture = async (strapi, params) => {
@@ -147,7 +156,6 @@ const capture = async (strapi, params) => {
147
156
  throw new Error("Transaction ID (txid) is required for capture");
148
157
  }
149
158
 
150
-
151
159
  const requiredParams = {
152
160
  request: "capture",
153
161
  txid: params.txid,
@@ -165,7 +173,6 @@ const refund = async (strapi, params) => {
165
173
  throw new Error("Transaction ID (txid) is required for refund");
166
174
  }
167
175
 
168
-
169
176
  const requiredParams = {
170
177
  request: "refund",
171
178
  txid: params.txid,
@@ -179,6 +186,39 @@ const refund = async (strapi, params) => {
179
186
  return await sendRequest(strapi, requiredParams);
180
187
  };
181
188
 
189
+ /**
190
+ * Performs a 3DS check and returns the modified params
191
+ * The modified params include the pseudocardpan if the 3DS check is valid
192
+ *
193
+ * @param strapi - Strapi instance
194
+ * @param params - Request params
195
+ * @returns The modified params
196
+ */
197
+ const perform3DSCheck = async (strapi, params) => {
198
+ try {
199
+ const result = await sendRequest(strapi, {
200
+ ...params,
201
+ request: "3dscheck",
202
+ exiturl: params.successurl ?? '',
203
+ });
204
+
205
+ if (result?.errorCode || result?.errorMessage || result?.Status === 'Failed' || result?.Status === 'Invalid')
206
+ throw new Error('3DS check failed');
207
+
208
+ const modifiedParams = {
209
+ ...params,
210
+ pseudocardpan: result?.CreditCard?.PseudoCardPan,
211
+ };
212
+
213
+ if (result?.Status === 'Valid') modifiedParams.xid = result?.CreditCard?.ThreeDS?.Xid;
214
+
215
+ return modifiedParams;
216
+ } catch (error) {
217
+ strapi.log.error("3DS check error:", error);
218
+ return null;
219
+ }
220
+ };
221
+
182
222
  const handle3DSCallback = async (strapi, callbackData, resultType = 'callback') => {
183
223
  try {
184
224
  const parsedData = callbackData && Object.keys(callbackData).length > 0
@@ -218,6 +258,7 @@ module.exports = {
218
258
  authorization,
219
259
  capture,
220
260
  refund,
221
- handle3DSCallback
261
+ handle3DSCallback,
262
+ perform3DSCheck
222
263
  };
223
264
 
@@ -28,7 +28,7 @@ const buildClientRequestParams = (settings, params, logger = null) => {
28
28
  const isCreditCard = requestParams.clearingtype === "cc";
29
29
  const enable3DSecure = settings.enable3DSecure !== false;
30
30
 
31
- if (isCreditCard && enable3DSecure && (params.request === "preauthorization" || params.request === "authorization")) {
31
+ if (is3dsViable(requestParams, settings)) {
32
32
  requestParams["3dsecure"] = "yes";
33
33
  requestParams.ecommercemode = params.ecommercemode || "internet";
34
34
 
@@ -93,8 +93,23 @@ const toFormData = (requestParams) => {
93
93
  return formData;
94
94
  };
95
95
 
96
+ /**
97
+ * Checks if the request is viable for 3DS according to the settings
98
+ *
99
+ * @param params - Request params
100
+ * @param settings - Set up in admin
101
+ * @returns
102
+ */
103
+ const is3dsViable = (params, settings) => {
104
+ const isCreditCard = params.clearingtype === "cc";
105
+ const enable3DSecure = settings.enable3DSecure !== false;
106
+
107
+ return isCreditCard && enable3DSecure && (params.request === "preauthorization" || params.request === "authorization")
108
+ };
109
+
96
110
  module.exports = {
97
111
  buildClientRequestParams,
98
- toFormData
112
+ toFormData,
113
+ is3dsViable
99
114
  };
100
115