strapi-plugin-payone-provider 1.5.3 → 1.5.5

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.
@@ -1,5 +1,7 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
2
  import { Box, Flex, Typography } from "@strapi/design-system";
3
+ import { request } from "@strapi/helper-plugin";
4
+ import pluginId from "../../../pluginId";
3
5
  import { DEFAULT_APPLE_PAY_CONFIG } from "../../utils/applePayConstants";
4
6
 
5
7
  /**
@@ -41,13 +43,41 @@ const ApplePayButton = ({
41
43
  try {
42
44
  console.log("[Apple Pay] Checking availability...");
43
45
 
44
- // Check if we're on HTTPS (required for Apple Pay JS API)
45
- const isSecure = typeof window !== 'undefined' &&
46
- (window.location.protocol === 'https:' ||
47
- window.location.hostname === 'localhost' ||
48
- window.location.hostname === '127.0.0.1');
46
+ // Check secure context using browser's native property
47
+ // This is the most reliable way to check if we're in a secure context
48
+ const isSecureContext = typeof window !== 'undefined' && window.isSecureContext;
49
+ const protocol = typeof window !== 'undefined' ? window.location.protocol : '';
50
+ const hostname = typeof window !== 'undefined' ? window.location.hostname : '';
51
+ const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1';
52
+
53
+ // For Apple Pay, we need a secure context (HTTPS or localhost)
54
+ // window.isSecureContext is true for:
55
+ // - HTTPS pages
56
+ // - localhost (even on HTTP)
57
+ // - 127.0.0.1 (even on HTTP)
58
+ // - file:// URLs
59
+ const isSecure = isSecureContext || isLocalhost;
49
60
 
50
- console.log("[Apple Pay] Secure context:", isSecure, "Protocol:", window.location?.protocol);
61
+ console.log("[Apple Pay] Secure context check:", {
62
+ isSecureContext: isSecureContext,
63
+ protocol: protocol,
64
+ hostname: hostname,
65
+ isLocalhost: isLocalhost,
66
+ isSecure: isSecure,
67
+ fullUrl: typeof window !== 'undefined' ? window.location.href : ''
68
+ });
69
+
70
+ // If not secure, log detailed information
71
+ if (!isSecure) {
72
+ console.error("[Apple Pay] NOT in secure context!", {
73
+ isSecureContext: isSecureContext,
74
+ protocol: protocol,
75
+ hostname: hostname,
76
+ isLocalhost: isLocalhost,
77
+ fullUrl: typeof window !== 'undefined' ? window.location.href : '',
78
+ reason: !isSecureContext ? "window.isSecureContext is false" : "Unknown"
79
+ });
80
+ }
51
81
 
52
82
  // First, check if Payment Request API is available
53
83
  // Payment Request API works on HTTP too, but Apple Pay JS API requires HTTPS
@@ -168,15 +198,23 @@ const ApplePayButton = ({
168
198
  }
169
199
  }
170
200
 
171
- // If canMakePayment is not available, assume it's available (for older browsers)
172
- // But only if we're in a secure context
173
- // Reuse isSecure from line 47
201
+ // If canMakePayment is not available, check secure context again
202
+ // Re-check secure context to ensure we have the latest state
203
+ const isSecureContextFinal = typeof window !== 'undefined' && window.isSecureContext;
204
+ const hostnameFinal = typeof window !== 'undefined' ? window.location.hostname : '';
205
+ const isLocalhostFinal = hostnameFinal === 'localhost' || hostnameFinal === '127.0.0.1';
206
+ const isSecureFinal = isSecureContextFinal || isLocalhostFinal;
174
207
 
175
- if (isSecure) {
208
+ if (isSecureFinal) {
176
209
  console.log("[Apple Pay] canMakePayment not available, assuming support (secure context)");
177
210
  return { available: true, method: 'paymentRequest' };
178
211
  } else {
179
212
  console.warn("[Apple Pay] canMakePayment not available and insecure context");
213
+ console.warn("[Apple Pay] Context details:", {
214
+ isSecureContext: isSecureContextFinal,
215
+ hostname: hostnameFinal,
216
+ isLocalhost: isLocalhostFinal
217
+ });
180
218
  return { available: false, method: null, error: 'insecure_context' };
181
219
  }
182
220
  } catch (error) {
@@ -189,9 +227,13 @@ const ApplePayButton = ({
189
227
  }
190
228
 
191
229
  // Fallback: Try Apple Pay JS API (only on HTTPS)
192
- // Reuse isSecure from line 47
230
+ // Re-check secure context
231
+ const isSecureContextFallback = typeof window !== 'undefined' && window.isSecureContext;
232
+ const hostnameFallback = typeof window !== 'undefined' ? window.location.hostname : '';
233
+ const isLocalhostFallback = hostnameFallback === 'localhost' || hostnameFallback === '127.0.0.1';
234
+ const isSecureFallback = isSecureContextFallback || isLocalhostFallback;
193
235
 
194
- if (typeof window !== 'undefined' && window.ApplePaySession && isSecure) {
236
+ if (typeof window !== 'undefined' && window.ApplePaySession && isSecureFallback) {
195
237
  try {
196
238
  const canMakePayments = ApplePaySession.canMakePayments();
197
239
  return { available: canMakePayments, method: 'applePayJS' };
@@ -456,21 +498,39 @@ const ApplePayButton = ({
456
498
  merchantSessionPromise.then(session => {
457
499
  console.log("[Apple Pay] Merchant session received:", {
458
500
  hasMerchantIdentifier: !!session.merchantIdentifier,
501
+ merchantIdentifier: session.merchantIdentifier,
459
502
  domainName: session.domainName,
460
- displayName: session.displayName
503
+ displayName: session.displayName,
504
+ epochTimestamp: session.epochTimestamp,
505
+ expiresAt: session.expiresAt,
506
+ fullSession: session
461
507
  });
508
+
509
+ // Validate merchant session
510
+ if (!session || (!session.merchantIdentifier && !session.merchantSessionIdentifier)) {
511
+ console.error("[Apple Pay] Invalid merchant session - missing merchantIdentifier");
512
+ console.error("[Apple Pay] Session object:", JSON.stringify(session, null, 2));
513
+ throw new Error("Invalid merchant session: missing merchantIdentifier");
514
+ }
462
515
  }).catch(err => {
463
516
  console.error("[Apple Pay] Merchant session error:", err);
517
+ // Re-throw so Payment Request API knows validation failed
518
+ throw err;
464
519
  });
465
520
 
521
+ // Complete with the merchant session promise
522
+ // If the promise rejects, Payment Request API will close the dialog
466
523
  event.complete(merchantSessionPromise);
467
524
  } catch (error) {
468
525
  console.error("[Apple Pay] Merchant validation error:", error);
469
- if (onError) {
526
+ // Only call onError if it's defined
527
+ if (typeof onError === 'function') {
470
528
  onError(error);
471
529
  }
472
- // Complete with empty object - Payone will handle validation
473
- event.complete({});
530
+ // Complete with a promise that resolves to empty object
531
+ // This allows the dialog to continue even if validation fails
532
+ // Apple Pay will show an error but won't close immediately
533
+ event.complete(Promise.resolve({}));
474
534
  }
475
535
  };
476
536
 
@@ -502,10 +562,42 @@ const ApplePayButton = ({
502
562
  let response;
503
563
  try {
504
564
  response = await request.show();
565
+ console.log("[Apple Pay] Payment sheet shown successfully");
505
566
  } catch (error) {
506
- console.error("[Apple Pay] Error showing payment sheet:", error);
507
- if (onError) {
508
- onError(error);
567
+ console.error("[Apple Pay] Error showing payment sheet:", {
568
+ name: error.name,
569
+ message: error.message,
570
+ stack: error.stack
571
+ });
572
+
573
+ // Check if error is due to cancellation (user cancelled)
574
+ // Payment Request API throws "AbortError" when user cancels
575
+ if (error.name === 'AbortError' ||
576
+ (error.message && (
577
+ error.message.includes('Cancelled') ||
578
+ error.message.includes('cancel') ||
579
+ error.message.includes('abort')
580
+ ))) {
581
+ console.log("[Apple Pay] User cancelled the payment");
582
+ // Don't call onError for user cancellation
583
+ return;
584
+ }
585
+
586
+ // If it's a merchant validation error, log it specifically
587
+ if (error.message && (
588
+ error.message.includes('merchant') ||
589
+ error.message.includes('validation') ||
590
+ error.message.includes('identifier')
591
+ )) {
592
+ console.error("[Apple Pay] Merchant validation failed - this may cause the dialog to close");
593
+ if (typeof onError === 'function') {
594
+ onError(new Error("Merchant validation failed. Please check your Apple Pay configuration and merchant identifier in Payone settings."));
595
+ }
596
+ } else {
597
+ // For other errors, call onError
598
+ if (typeof onError === 'function') {
599
+ onError(error);
600
+ }
509
601
  }
510
602
  return;
511
603
  }
@@ -630,7 +722,8 @@ const ApplePayButton = ({
630
722
  } catch (finalError) {
631
723
  console.error("[Apple Pay] Failed to complete payment even with fail status:", finalError);
632
724
  }
633
- if (onError) {
725
+ // Only call onError if it's defined
726
+ if (typeof onError === 'function') {
634
727
  onError(completeError);
635
728
  }
636
729
  }
@@ -641,7 +734,14 @@ const ApplePayButton = ({
641
734
  stack: error.stack,
642
735
  name: error.name
643
736
  });
644
- if (onError) {
737
+ // Check if error is due to cancellation (user cancelled)
738
+ if (error.message && error.message.includes('Cancelled')) {
739
+ console.log("[Apple Pay] User cancelled the payment");
740
+ // Don't call onError for user cancellation
741
+ return;
742
+ }
743
+ // Only call onError if it's defined and it's not a cancellation
744
+ if (typeof onError === 'function') {
645
745
  onError(error);
646
746
  }
647
747
  }
@@ -660,43 +760,45 @@ const ApplePayButton = ({
660
760
  });
661
761
 
662
762
  try {
663
- // Call Strapi backend to validate with Payone
664
- const response = await fetch('/api/strapi-plugin-payone-provider/validate-apple-pay-merchant', {
763
+ console.log("[Apple Pay] Making validation request using Strapi helper...");
764
+
765
+ // Use Strapi helper-plugin's request function which automatically handles authentication
766
+ // This uses the admin API route which requires admin authentication
767
+ const merchantSession = await request(`/${pluginId}/validate-apple-pay-merchant`, {
665
768
  method: 'POST',
666
- headers: {
667
- 'Content-Type': 'application/json',
668
- },
669
- body: JSON.stringify({
769
+ body: {
670
770
  validationURL,
671
771
  ...config
672
- })
772
+ }
673
773
  });
674
774
 
675
- console.log("[Apple Pay] Validation response status:", response.status);
775
+ console.log("[Apple Pay] Merchant session received from backend:", {
776
+ hasData: !!merchantSession.data,
777
+ merchantIdentifier: merchantSession.data?.merchantIdentifier
778
+ });
676
779
 
677
- if (response.ok) {
678
- const merchantSession = await response.json();
679
- console.log("[Apple Pay] Merchant session received from backend:", {
680
- hasData: !!merchantSession.data,
681
- merchantIdentifier: merchantSession.data?.merchantIdentifier
682
- });
683
- return merchantSession.data || merchantSession;
684
- } else {
685
- // If validation fails, return empty object - Payone will handle it
686
- const errorText = await response.text();
687
- console.warn("[Apple Pay] Merchant validation failed:", {
688
- status: response.status,
689
- statusText: response.statusText,
690
- error: errorText
691
- });
692
- return {};
693
- }
780
+ return merchantSession.data || merchantSession;
694
781
  } catch (error) {
695
782
  console.error("[Apple Pay] Merchant validation error:", {
696
783
  message: error.message,
784
+ status: error.response?.status,
785
+ statusText: error.response?.statusText,
786
+ data: error.response?.data,
697
787
  stack: error.stack
698
788
  });
699
- // Return empty object - Payone will handle validation
789
+
790
+ // Log specific error details
791
+ if (error.response?.status === 403) {
792
+ console.error("[Apple Pay] 403 Forbidden - Authentication failed. Make sure you are logged in as admin.");
793
+ } else if (error.response?.status === 401) {
794
+ console.error("[Apple Pay] 401 Unauthorized - Please log in again.");
795
+ } else if (error.response?.status >= 500) {
796
+ console.error("[Apple Pay] Server error - Check server logs for details.");
797
+ }
798
+
799
+ // If validation fails, return empty object
800
+ // This allows the dialog to continue, though Apple Pay may show an error
801
+ // The user can still proceed with payment if they want
700
802
  return {};
701
803
  }
702
804
  };
@@ -720,12 +822,6 @@ const ApplePayButton = ({
720
822
  )}
721
823
  {!isLoading && isAvailable && (
722
824
  <>
723
- <Typography variant="sigma" textColor="neutral700" fontWeight="semiBold" style={{ textAlign: "left" }}>
724
- Apple Pay Payment
725
- </Typography>
726
- <Typography variant="pi" textColor="neutral600" style={{ textAlign: "left" }}>
727
- Click the button below to pay with Apple Pay. The token will be automatically sent to Payone.
728
- </Typography>
729
825
  <Box ref={buttonContainerRef} style={{ minHeight: "40px", width: "100%", display: "flex", justifyContent: "flex-start" }}>
730
826
  {typeof window !== 'undefined' && window.customElements && window.customElements.get('apple-pay-button') ? (
731
827
  <apple-pay-button
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-payone-provider",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "Strapi plugin for Payone payment gateway integration",
5
5
  "license": "MIT",
6
6
  "maintainers": [
@@ -17,7 +17,7 @@ const getPayoneService = (strapi) => {
17
17
  * @param {Error} error - Error object
18
18
  */
19
19
  const handleError = (ctx, error) => {
20
- strapi.log.error("Payone controller error:", error);
20
+ ctx.strapi.log.error("Payone controller error:", error);
21
21
  ctx.throw(500, error);
22
22
  };
23
23
 
@@ -180,10 +180,28 @@ module.exports = ({ strapi }) => ({
180
180
 
181
181
  async validateApplePayMerchant(ctx) {
182
182
  try {
183
+ strapi.log.info("[Apple Pay] Merchant validation request received");
184
+ strapi.log.info("[Apple Pay] Request body:", JSON.stringify(ctx.request.body, null, 2));
185
+ strapi.log.info("[Apple Pay] User:", ctx.state.user ? {
186
+ id: ctx.state.user.id,
187
+ email: ctx.state.user.email,
188
+ roles: ctx.state.user.roles?.map(r => r.code)
189
+ } : "No user");
190
+
183
191
  const params = ctx.request.body;
184
192
  const result = await getPayoneService(strapi).validateApplePayMerchant(params);
193
+
194
+ strapi.log.info("[Apple Pay] Merchant validation result:", {
195
+ hasResult: !!result,
196
+ hasMerchantIdentifier: !!result.merchantIdentifier
197
+ });
198
+
185
199
  ctx.body = { data: result };
186
200
  } catch (error) {
201
+ strapi.log.error("[Apple Pay] Controller error:", {
202
+ message: error.message,
203
+ stack: error.stack
204
+ });
187
205
  handleError(ctx, error);
188
206
  }
189
207
  }
@@ -24,7 +24,7 @@ const initializeApplePaySession = async (strapi, params) => {
24
24
  try {
25
25
  strapi.log.info("[Apple Pay] Initializing Apple Pay session with Payone");
26
26
  strapi.log.info("[Apple Pay] Request params:", JSON.stringify(params, null, 2));
27
-
27
+
28
28
  const settings = await getSettings(strapi);
29
29
 
30
30
  if (!validateSettings(settings)) {
@@ -55,9 +55,9 @@ const initializeApplePaySession = async (strapi, params) => {
55
55
  const mode = settings.mode || "test"; // test or live
56
56
 
57
57
  // Get domain from params or settings or server config
58
- const domain = domainName || settings.domainName ||
59
- (strapi.config.get("server.url") ? new URL(strapi.config.get("server.url")).hostname : null) ||
60
- "localhost";
58
+ const domain = domainName || settings.domainName ||
59
+ (strapi.config.get("server.url") ? new URL(strapi.config.get("server.url")).hostname : null) ||
60
+ "localhost";
61
61
 
62
62
  // Build request parameters for Apple Pay session initialization
63
63
  // According to Payone documentation: request="genericpayment"
@@ -87,14 +87,38 @@ const initializeApplePaySession = async (strapi, params) => {
87
87
 
88
88
  const formData = toFormData(requestParams);
89
89
 
90
- const response = await axios.post(POST_GATEWAY_URL, formData, {
91
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
92
- timeout: 30000
90
+ strapi.log.info("[Apple Pay] Sending request to Payone API:", {
91
+ url: POST_GATEWAY_URL,
92
+ params: {
93
+ ...requestParams,
94
+ key: "***HIDDEN***" // Hide API key in logs
95
+ }
93
96
  });
94
97
 
98
+ let response;
99
+ try {
100
+ response = await axios.post(POST_GATEWAY_URL, formData, {
101
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
102
+ timeout: 30000
103
+ });
104
+ } catch (axiosError) {
105
+ strapi.log.error("[Apple Pay] Payone API request failed:", {
106
+ message: axiosError.message,
107
+ status: axiosError.response?.status,
108
+ statusText: axiosError.response?.statusText,
109
+ data: axiosError.response?.data,
110
+ config: {
111
+ url: axiosError.config?.url,
112
+ method: axiosError.config?.method
113
+ }
114
+ });
115
+ throw axiosError;
116
+ }
117
+
95
118
  strapi.log.info("[Apple Pay] Payone response received:", {
96
119
  status: response.status,
97
- statusText: response.statusText
120
+ statusText: response.statusText,
121
+ headers: response.headers
98
122
  });
99
123
 
100
124
  // Parse response
@@ -102,7 +126,7 @@ const initializeApplePaySession = async (strapi, params) => {
102
126
 
103
127
  strapi.log.info("[Apple Pay] Session initialization response:", JSON.stringify(responseData, null, 2));
104
128
  strapi.log.info("[Apple Pay] Response status:", responseData.status || responseData.Status);
105
-
129
+
106
130
  if (responseData.errorcode || responseData.ErrorCode) {
107
131
  strapi.log.warn("[Apple Pay] Response contains error:", {
108
132
  errorcode: responseData.errorcode || responseData.ErrorCode,
@@ -135,7 +159,7 @@ const validateApplePayMerchant = async (strapi, params) => {
135
159
  mid: params.mid,
136
160
  portalid: params.portalid
137
161
  }, null, 2));
138
-
162
+
139
163
  const settings = await getSettings(strapi);
140
164
 
141
165
  if (!validateSettings(settings)) {
@@ -155,11 +179,11 @@ const validateApplePayMerchant = async (strapi, params) => {
155
179
  const merchantName = displayName || settings.merchantName || settings.displayName || "Test Store";
156
180
  const merchantId = mid || settings.mid || settings.merchantIdentifier;
157
181
  const portalId = portalid || settings.portalid;
158
-
182
+
159
183
  // Get domain from params or settings or server config
160
- const domainName = domain || settings.domainName ||
161
- (strapi.config.get("server.url") ? new URL(strapi.config.get("server.url")).hostname : null) ||
162
- "localhost";
184
+ const domainName = domain || settings.domainName ||
185
+ (strapi.config.get("server.url") ? new URL(strapi.config.get("server.url")).hostname : null) ||
186
+ "localhost";
163
187
 
164
188
  // For Payone integration without developer account,
165
189
  // Payone handles merchant validation
@@ -174,44 +198,184 @@ const validateApplePayMerchant = async (strapi, params) => {
174
198
  strapi.log.info("[Apple Pay] Initializing session with params:", JSON.stringify(sessionParams, null, 2));
175
199
 
176
200
  // Initialize Apple Pay session with Payone
177
- const sessionResponse = await initializeApplePaySession(strapi, sessionParams);
201
+ let sessionResponse;
202
+ try {
203
+ sessionResponse = await initializeApplePaySession(strapi, sessionParams);
204
+ } catch (error) {
205
+ strapi.log.error("[Apple Pay] Failed to initialize session with Payone:", {
206
+ message: error.message,
207
+ status: error.response?.status,
208
+ data: error.response?.data
209
+ });
210
+ // Return empty object on error - Payment Request API will handle it
211
+ return {};
212
+ }
178
213
 
179
214
  strapi.log.info("[Apple Pay] Session initialization result:", {
180
215
  status: sessionResponse.status || sessionResponse.Status,
181
- hasMerchantIdentifier: !!(sessionResponse.merchantIdentifier || sessionResponse.merchantSessionIdentifier)
216
+ hasMerchantIdentifier: !!(sessionResponse.merchantIdentifier || sessionResponse.merchantSessionIdentifier),
217
+ hasApplePaySession: !!(sessionResponse["add_paydata[applepay_payment_session]"] ||
218
+ sessionResponse["add_paydata[applepay_payment_session]"] ||
219
+ sessionResponse.add_paydata?.applepay_payment_session),
220
+ fullResponse: JSON.stringify(sessionResponse, null, 2)
182
221
  });
183
222
 
184
- // If session initialization is successful, return merchant session
185
- // Payone will provide the merchant identifier and validation data
186
- if (sessionResponse.status === "APPROVED" || sessionResponse.status === "REDIRECT") {
187
- strapi.log.info("[Apple Pay] Session approved, creating merchant session object");
188
- // Get merchant identifier from Payone response or settings
189
- const merchantIdentifier = sessionResponse.merchantIdentifier ||
190
- sessionResponse.merchantSessionIdentifier ||
191
- settings.merchantIdentifier ||
192
- settings.mid ||
193
- settings.portalid ||
194
- `merchant.${domainName}`;
195
-
196
- // Return merchant session object
197
- // In a real implementation, you would get this from Payone's response
198
- const merchantSession = {
199
- epochTimestamp: Date.now(),
200
- expiresAt: Date.now() + (5 * 60 * 1000), // 5 minutes
201
- merchantSessionIdentifier: sessionResponse.merchantSessionIdentifier || `merchant.${domainName}`,
202
- nonce: sessionResponse.nonce || generateNonce(),
203
- merchantIdentifier: merchantIdentifier,
204
- domainName: domainName,
205
- displayName: merchantName
206
- };
207
-
208
- strapi.log.info("[Apple Pay] Merchant session created:", {
209
- merchantIdentifier: merchantSession.merchantIdentifier,
210
- domainName: merchantSession.domainName,
211
- expiresAt: new Date(merchantSession.expiresAt).toISOString()
223
+ // If session initialization is successful, extract merchant session from Payone response
224
+ // Payone returns: add_paydata[applepay_payment_session] = BASE64 encoded merchant session
225
+ // Check for both uppercase and lowercase status
226
+ const responseStatus = sessionResponse.status || sessionResponse.Status;
227
+ if (responseStatus === "APPROVED" || responseStatus === "OK" ||
228
+ responseStatus === "approved" || responseStatus === "ok") {
229
+
230
+ // Extract BASE64 encoded merchant session from Payone response
231
+ // Payone returns it in: add_paydata[applepay_payment_session]
232
+ // Try all possible variations of the field name
233
+ const applePaySessionBase64 =
234
+ sessionResponse["add_paydata[applepay_payment_session]"] ||
235
+ sessionResponse["add_paydata[applepay_payment_session]"] ||
236
+ sessionResponse["add_paydata_applepay_payment_session"] ||
237
+ sessionResponse.add_paydata?.applepay_payment_session ||
238
+ sessionResponse.add_paydata?.["applepay_payment_session"] ||
239
+ sessionResponse["addPaydata[applepay_payment_session]"] ||
240
+ sessionResponse["addPaydata_applepay_payment_session"] ||
241
+ null;
242
+
243
+ strapi.log.info("[Apple Pay] Checking for merchant session in response:", {
244
+ hasWorkorderid: !!sessionResponse.workorderid,
245
+ workorderid: sessionResponse.workorderid,
246
+ allKeys: Object.keys(sessionResponse).filter(k => k.includes('applepay') || k.includes('session') || k.includes('paydata')),
247
+ responseKeys: Object.keys(sessionResponse)
248
+ });
249
+
250
+ strapi.log.info("[Apple Pay] Extracted Apple Pay session data:", {
251
+ hasBase64Session: !!applePaySessionBase64,
252
+ sessionLength: applePaySessionBase64?.length,
253
+ workorderid: sessionResponse.workorderid
212
254
  });
213
255
 
214
- return merchantSession;
256
+ if (applePaySessionBase64) {
257
+ try {
258
+ // Decode BASE64 merchant session
259
+ const merchantSessionJson = Buffer.from(applePaySessionBase64, 'base64').toString('utf-8');
260
+ const merchantSession = JSON.parse(merchantSessionJson);
261
+
262
+ strapi.log.info("[Apple Pay] Decoded merchant session:", {
263
+ merchantIdentifier: merchantSession.merchantIdentifier,
264
+ domainName: merchantSession.domainName,
265
+ displayName: merchantSession.displayName,
266
+ hasEpochTimestamp: !!merchantSession.epochTimestamp,
267
+ hasExpiresAt: !!merchantSession.expiresAt,
268
+ fullSession: merchantSession
269
+ });
270
+
271
+ // Validate decoded merchant session
272
+ if (!merchantSession.merchantIdentifier) {
273
+ strapi.log.warn("[Apple Pay] Decoded merchant session missing merchantIdentifier, using fallback");
274
+ // Use fallback merchant identifier
275
+ merchantSession.merchantIdentifier = settings.merchantIdentifier ||
276
+ settings.mid ||
277
+ settings.portalid ||
278
+ `merchant.${domainName}`;
279
+ }
280
+
281
+ // Ensure epochTimestamp and expiresAt are in seconds (not milliseconds)
282
+ if (merchantSession.epochTimestamp && merchantSession.epochTimestamp > 1000000000000) {
283
+ // If timestamp is in milliseconds, convert to seconds
284
+ merchantSession.epochTimestamp = Math.floor(merchantSession.epochTimestamp / 1000);
285
+ }
286
+ if (merchantSession.expiresAt && merchantSession.expiresAt > 1000000000000) {
287
+ // If timestamp is in milliseconds, convert to seconds
288
+ merchantSession.expiresAt = Math.floor(merchantSession.expiresAt / 1000);
289
+ }
290
+
291
+ // Validate final merchant session
292
+ if (!merchantSession.merchantIdentifier || merchantSession.merchantIdentifier === 'undefined' || merchantSession.merchantIdentifier === 'null') {
293
+ throw new Error("Decoded merchant session has invalid merchantIdentifier");
294
+ }
295
+
296
+ strapi.log.info("[Apple Pay] Validated merchant session:", {
297
+ merchantIdentifier: merchantSession.merchantIdentifier,
298
+ domainName: merchantSession.domainName,
299
+ epochTimestamp: merchantSession.epochTimestamp,
300
+ expiresAt: merchantSession.expiresAt
301
+ });
302
+
303
+ return merchantSession;
304
+ } catch (decodeError) {
305
+ strapi.log.error("[Apple Pay] Failed to decode merchant session:", {
306
+ error: decodeError.message,
307
+ base64Length: applePaySessionBase64?.length,
308
+ base64Preview: applePaySessionBase64?.substring(0, 100)
309
+ });
310
+
311
+ // If decoding fails, we cannot proceed - merchant session is invalid
312
+ throw new Error(`Failed to decode Apple Pay merchant session: ${decodeError.message}`);
313
+ }
314
+ } else {
315
+ // If no session data in response, we need to create a valid merchant session
316
+ // According to Payone docs, merchant identifier should be obtained from PMI
317
+ // after domain verification and onboarding
318
+ strapi.log.warn("[Apple Pay] No Apple Pay session data in response, creating merchant session from settings");
319
+
320
+ // Get merchant identifier from settings
321
+ // According to Payone docs, merchant identifier should be visible in PMI after onboarding
322
+ // Path: CONFIGURATION/PAYMENT PORTALS - choose an onboarded Portal - Payment type configuration tab
323
+ let merchantIdentifier = settings.merchantIdentifier ||
324
+ settings.mid ||
325
+ settings.portalid;
326
+
327
+ // If still no merchant identifier, try to construct one from domain
328
+ // But this is not ideal - merchant identifier should come from Payone PMI
329
+ if (!merchantIdentifier) {
330
+ strapi.log.warn("[Apple Pay] No merchant identifier found in settings, using domain-based fallback");
331
+ merchantIdentifier = `merchant.${domainName}`;
332
+ }
333
+
334
+ // Ensure merchant identifier is a string and not empty
335
+ merchantIdentifier = merchantIdentifier.toString().trim();
336
+ if (!merchantIdentifier || merchantIdentifier === 'undefined' || merchantIdentifier === 'null') {
337
+ strapi.log.error("[Apple Pay] Invalid merchant identifier:", merchantIdentifier);
338
+ throw new Error("Merchant identifier is invalid. Please configure a valid merchant identifier in Payone Merchant Interface (PMI) after Apple Pay onboarding. Path: CONFIGURATION → PAYMENT PORTALS → [Your Portal] → Payment type configuration tab");
339
+ }
340
+
341
+ // Create a valid merchant session object
342
+ // This format is required by Apple Pay Payment Request API
343
+ // IMPORTANT: epochTimestamp and expiresAt must be in seconds (Unix timestamp), not milliseconds
344
+ const merchantSession = {
345
+ epochTimestamp: Math.floor(Date.now() / 1000), // Unix timestamp in seconds
346
+ expiresAt: Math.floor((Date.now() + (5 * 60 * 1000)) / 1000), // 5 minutes from now, in seconds
347
+ merchantSessionIdentifier: `merchant.${domainName}`,
348
+ nonce: generateNonce(),
349
+ merchantIdentifier: merchantIdentifier, // Already validated and converted to string
350
+ domainName: domainName,
351
+ displayName: merchantName
352
+ };
353
+
354
+ // Validate merchant session before returning
355
+ if (!merchantSession.merchantIdentifier || merchantSession.merchantIdentifier === 'undefined' || merchantSession.merchantIdentifier === 'null') {
356
+ strapi.log.error("[Apple Pay] Created merchant session is missing or invalid merchantIdentifier!", {
357
+ merchantIdentifier: merchantSession.merchantIdentifier,
358
+ settings: {
359
+ hasMerchantIdentifier: !!settings.merchantIdentifier,
360
+ hasMid: !!settings.mid,
361
+ hasPortalid: !!settings.portalid,
362
+ mid: settings.mid,
363
+ portalid: settings.portalid
364
+ }
365
+ });
366
+ throw new Error("Merchant identifier is required but not found in settings. Please configure merchant identifier in Payone Merchant Interface (PMI) after Apple Pay onboarding.");
367
+ }
368
+
369
+ strapi.log.info("[Apple Pay] Created merchant session from settings:", {
370
+ merchantIdentifier: merchantSession.merchantIdentifier,
371
+ domainName: merchantSession.domainName,
372
+ displayName: merchantSession.displayName,
373
+ epochTimestamp: merchantSession.epochTimestamp,
374
+ expiresAt: merchantSession.expiresAt
375
+ });
376
+
377
+ return merchantSession;
378
+ }
215
379
  }
216
380
 
217
381
  // If initialization failed, return empty object
@@ -249,8 +413,8 @@ const parseResponse = (responseData, logger) => {
249
413
  * Generate nonce for merchant session
250
414
  */
251
415
  const generateNonce = () => {
252
- return Math.random().toString(36).substring(2, 15) +
253
- Math.random().toString(36).substring(2, 15);
416
+ return Math.random().toString(36).substring(2, 15) +
417
+ Math.random().toString(36).substring(2, 15);
254
418
  };
255
419
 
256
420
  module.exports = {
@@ -24,8 +24,20 @@ const parseResponse = (responseText, logger) => {
24
24
  const params = new URLSearchParams(responseText);
25
25
  const response = {};
26
26
  for (const [key, value] of params) {
27
+ // Store both lowercase and original case
27
28
  response[key.toLowerCase()] = value;
28
29
  response[key] = value;
30
+
31
+ // Also handle add_paydata fields with brackets
32
+ // Payone returns: add_paydata[applepay_payment_session]=BASE64_STRING
33
+ // URLSearchParams handles brackets, but we need to ensure we can access it
34
+ if (key.includes('add_paydata') || key.includes('addPaydata')) {
35
+ // Store with original key format
36
+ response[key] = value;
37
+ // Also try normalized versions
38
+ const normalizedKey = key.replace(/\[/g, '_').replace(/\]/g, '');
39
+ response[normalizedKey] = value;
40
+ }
29
41
  }
30
42
  return response;
31
43
  };