strapi-plugin-payone-provider 1.5.4 → 1.5.6

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.
@@ -42,7 +42,7 @@ const ApplePayButton = ({
42
42
  const checkApplePayAvailability = async () => {
43
43
  try {
44
44
  console.log("[Apple Pay] Checking availability...");
45
-
45
+
46
46
  // Check secure context using browser's native property
47
47
  // This is the most reliable way to check if we're in a secure context
48
48
  const isSecureContext = typeof window !== 'undefined' && window.isSecureContext;
@@ -57,7 +57,7 @@ const ApplePayButton = ({
57
57
  // - 127.0.0.1 (even on HTTP)
58
58
  // - file:// URLs
59
59
  const isSecure = isSecureContext || isLocalhost;
60
-
60
+
61
61
  console.log("[Apple Pay] Secure context check:", {
62
62
  isSecureContext: isSecureContext,
63
63
  protocol: protocol,
@@ -78,12 +78,12 @@ const ApplePayButton = ({
78
78
  reason: !isSecureContext ? "window.isSecureContext is false" : "Unknown"
79
79
  });
80
80
  }
81
-
81
+
82
82
  // First, check if Payment Request API is available
83
83
  // Payment Request API works on HTTP too, but Apple Pay JS API requires HTTPS
84
84
  if (typeof window === 'undefined' || !window.PaymentRequest) {
85
85
  console.log("[Apple Pay] Payment Request API not available");
86
-
86
+
87
87
  // Fallback: Check Apple Pay JS API (for Safari, requires HTTPS)
88
88
  if (typeof window !== 'undefined' && window.ApplePaySession && isSecure) {
89
89
  try {
@@ -95,11 +95,11 @@ const ApplePayButton = ({
95
95
  return { available: false, method: null, error: 'insecure_context' };
96
96
  }
97
97
  }
98
-
98
+
99
99
  if (!isSecure && typeof window !== 'undefined' && window.ApplePaySession) {
100
100
  console.warn("[Apple Pay] Apple Pay JS API requires HTTPS. Using Payment Request API fallback.");
101
101
  }
102
-
102
+
103
103
  console.log("[Apple Pay] No Apple Pay support found");
104
104
  return { available: false, method: null };
105
105
  }
@@ -152,11 +152,11 @@ const ApplePayButton = ({
152
152
  try {
153
153
  const canPay = await testRequest.canMakePayment();
154
154
  console.log("[Apple Pay] canMakePayment result:", canPay);
155
-
155
+
156
156
  if (canPay) {
157
157
  return { available: true, method: 'paymentRequest' };
158
158
  }
159
-
159
+
160
160
  // If PaymentRequest says no, try Apple Pay JS API as fallback (only on HTTPS)
161
161
  if (typeof window !== 'undefined' && window.ApplePaySession && isSecure) {
162
162
  try {
@@ -172,17 +172,17 @@ const ApplePayButton = ({
172
172
  }
173
173
  }
174
174
  }
175
-
175
+
176
176
  return { available: false, method: null };
177
177
  } catch (e) {
178
178
  console.error("[Apple Pay] Error checking canMakePayment:", e);
179
-
179
+
180
180
  // If it's insecure context error, we can't use Apple Pay JS API
181
181
  if (e.message && e.message.includes('insecure')) {
182
182
  console.warn("[Apple Pay] Insecure context detected. Apple Pay requires HTTPS.");
183
183
  return { available: false, method: null, error: 'insecure_context' };
184
184
  }
185
-
185
+
186
186
  // For other errors, try Apple Pay JS API as fallback (only on HTTPS)
187
187
  if (typeof window !== 'undefined' && window.ApplePaySession && isSecure) {
188
188
  try {
@@ -193,7 +193,7 @@ const ApplePayButton = ({
193
193
  return { available: false, method: null };
194
194
  }
195
195
  }
196
-
196
+
197
197
  return { available: false, method: null };
198
198
  }
199
199
  }
@@ -204,7 +204,7 @@ const ApplePayButton = ({
204
204
  const hostnameFinal = typeof window !== 'undefined' ? window.location.hostname : '';
205
205
  const isLocalhostFinal = hostnameFinal === 'localhost' || hostnameFinal === '127.0.0.1';
206
206
  const isSecureFinal = isSecureContextFinal || isLocalhostFinal;
207
-
207
+
208
208
  if (isSecureFinal) {
209
209
  console.log("[Apple Pay] canMakePayment not available, assuming support (secure context)");
210
210
  return { available: true, method: 'paymentRequest' };
@@ -219,20 +219,20 @@ const ApplePayButton = ({
219
219
  }
220
220
  } catch (error) {
221
221
  console.error("[Apple Pay] Error checking availability:", error);
222
-
222
+
223
223
  // Check if it's insecure context error
224
224
  if (error.message && error.message.includes('insecure')) {
225
225
  console.warn("[Apple Pay] Insecure context - Apple Pay requires HTTPS");
226
226
  return { available: false, method: null, error: 'insecure_context' };
227
227
  }
228
-
228
+
229
229
  // Fallback: Try Apple Pay JS API (only on HTTPS)
230
230
  // Re-check secure context
231
231
  const isSecureContextFallback = typeof window !== 'undefined' && window.isSecureContext;
232
232
  const hostnameFallback = typeof window !== 'undefined' ? window.location.hostname : '';
233
233
  const isLocalhostFallback = hostnameFallback === 'localhost' || hostnameFallback === '127.0.0.1';
234
234
  const isSecureFallback = isSecureContextFallback || isLocalhostFallback;
235
-
235
+
236
236
  if (typeof window !== 'undefined' && window.ApplePaySession && isSecureFallback) {
237
237
  try {
238
238
  const canMakePayments = ApplePaySession.canMakePayments();
@@ -244,16 +244,16 @@ const ApplePayButton = ({
244
244
  }
245
245
  }
246
246
  }
247
-
247
+
248
248
  return { available: false, method: null };
249
249
  }
250
250
  };
251
251
 
252
252
  useEffect(() => {
253
253
  const scriptUrl = "https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js";
254
-
254
+
255
255
  console.log("[Apple Pay] Loading Apple Pay SDK script...");
256
-
256
+
257
257
  if (document.querySelector(`script[src="${scriptUrl}"]`)) {
258
258
  console.log("[Apple Pay] Script already loaded");
259
259
  // Script already loaded, check if it's ready
@@ -270,20 +270,20 @@ const ApplePayButton = ({
270
270
  script.src = scriptUrl;
271
271
  script.crossOrigin = "anonymous";
272
272
  script.async = true;
273
-
273
+
274
274
  script.onload = () => {
275
275
  console.log("[Apple Pay] SDK script loaded successfully");
276
276
  setTimeout(() => {
277
277
  initializeButton();
278
278
  }, 500);
279
279
  };
280
-
280
+
281
281
  script.onerror = (error) => {
282
282
  console.error("[Apple Pay] Failed to load SDK script:", error);
283
283
  setIsLoading(false);
284
284
  setIsAvailable(false);
285
285
  setErrorMessage("Failed to load Apple Pay SDK. Please check Content Security Policy settings.");
286
-
286
+
287
287
  // Even if script fails, try to use Payment Request API
288
288
  console.log("[Apple Pay] Trying Payment Request API as fallback...");
289
289
  setTimeout(() => {
@@ -306,22 +306,22 @@ const ApplePayButton = ({
306
306
  const initializeButton = async () => {
307
307
  try {
308
308
  console.log("[Apple Pay] Initializing button...");
309
-
310
- const isSecure = typeof window !== 'undefined' &&
311
- (window.location.protocol === 'https:' ||
312
- window.location.hostname === 'localhost' ||
313
- window.location.hostname === '127.0.0.1');
314
-
309
+
310
+ const isSecure = typeof window !== 'undefined' &&
311
+ (window.location.protocol === 'https:' ||
312
+ window.location.hostname === 'localhost' ||
313
+ window.location.hostname === '127.0.0.1');
314
+
315
315
  console.log("[Apple Pay] Secure context check:", {
316
316
  protocol: window.location?.protocol,
317
317
  hostname: window.location?.hostname,
318
318
  isSecure: isSecure
319
319
  });
320
-
321
- const isLocalhost = typeof window !== 'undefined' &&
322
- (window.location.hostname === 'localhost' ||
323
- window.location.hostname === '127.0.0.1');
324
-
320
+
321
+ const isLocalhost = typeof window !== 'undefined' &&
322
+ (window.location.hostname === 'localhost' ||
323
+ window.location.hostname === '127.0.0.1');
324
+
325
325
  if (!isSecure && !isLocalhost && window.location?.protocol === 'http:') {
326
326
  const errorMsg = "Apple Pay requires HTTPS. Please access this page via HTTPS (https://yourdomain.com) instead of HTTP. Localhost (http://localhost) is allowed for development.";
327
327
  setErrorMessage(errorMsg);
@@ -331,7 +331,7 @@ const ApplePayButton = ({
331
331
  console.warn("[Apple Pay] Current URL:", window.location.href);
332
332
  return;
333
333
  }
334
-
334
+
335
335
  // Log context information
336
336
  console.log("[Apple Pay] Context info:", {
337
337
  protocol: window.location?.protocol,
@@ -340,16 +340,16 @@ const ApplePayButton = ({
340
340
  isLocalhost: isLocalhost,
341
341
  fullUrl: window.location?.href
342
342
  });
343
-
343
+
344
344
  // Check availability
345
345
  const availability = await checkApplePayAvailability();
346
346
  console.log("[Apple Pay] Availability check result:", availability);
347
-
347
+
348
348
  setIsAvailable(availability.available);
349
-
349
+
350
350
  if (!availability.available) {
351
351
  let errorMsg = "Apple Pay is not available on this device or browser.";
352
-
352
+
353
353
  if (isLocalhost) {
354
354
  errorMsg = "Apple Pay is not available on localhost. Apple Pay requires a registered domain with HTTPS. " +
355
355
  "For testing, please use a production domain with HTTPS or test on a device with Safari (iOS/macOS). " +
@@ -366,7 +366,7 @@ const ApplePayButton = ({
366
366
  } else {
367
367
  errorMsg += " Please use Safari on iOS, macOS, or iPadOS, or a browser that supports Payment Request API (Chrome, Edge, Safari).";
368
368
  }
369
-
369
+
370
370
  setErrorMessage(errorMsg);
371
371
  setIsLoading(false);
372
372
  console.warn("[Apple Pay] Not available:", errorMsg);
@@ -380,14 +380,14 @@ const ApplePayButton = ({
380
380
  console.error("[Apple Pay] Initialization error:", error);
381
381
  setIsLoading(false);
382
382
  setIsAvailable(false);
383
-
383
+
384
384
  // Check for insecure context error
385
385
  if (error.message && error.message.includes('insecure')) {
386
386
  setErrorMessage("Apple Pay requires HTTPS. Please access this page via HTTPS (https://yourdomain.com) instead of HTTP. Localhost is allowed for development.");
387
387
  } else {
388
388
  setErrorMessage(error.message || "Failed to initialize Apple Pay");
389
389
  }
390
-
390
+
391
391
  if (onError) {
392
392
  onError(error);
393
393
  }
@@ -406,11 +406,11 @@ const ApplePayButton = ({
406
406
  });
407
407
 
408
408
  // Check HTTPS requirement
409
- const isSecure = typeof window !== 'undefined' &&
410
- (window.location.protocol === 'https:' ||
411
- window.location.hostname === 'localhost' ||
412
- window.location.hostname === '127.0.0.1');
413
-
409
+ const isSecure = typeof window !== 'undefined' &&
410
+ (window.location.protocol === 'https:' ||
411
+ window.location.hostname === 'localhost' ||
412
+ window.location.hostname === '127.0.0.1');
413
+
414
414
  if (!isSecure && window.location?.protocol === 'http:') {
415
415
  const errorMsg = "Apple Pay requires HTTPS. Please access this page via HTTPS.";
416
416
  console.error("[Apple Pay]", errorMsg);
@@ -498,27 +498,46 @@ const ApplePayButton = ({
498
498
  merchantSessionPromise.then(session => {
499
499
  console.log("[Apple Pay] Merchant session received:", {
500
500
  hasMerchantIdentifier: !!session.merchantIdentifier,
501
+ merchantIdentifier: session.merchantIdentifier,
501
502
  domainName: session.domainName,
502
- displayName: session.displayName
503
+ displayName: session.displayName,
504
+ epochTimestamp: session.epochTimestamp,
505
+ expiresAt: session.expiresAt,
506
+ fullSession: session
503
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
+ }
504
515
  }).catch(err => {
505
516
  console.error("[Apple Pay] Merchant session error:", err);
506
- // Don't call onError here - let the dialog handle it
517
+ // Re-throw so Payment Request API knows validation failed
518
+ throw err;
507
519
  });
508
520
 
509
521
  // Complete with the merchant session promise
510
- // If it fails, Apple Pay will handle it gracefully
522
+ // If the promise rejects, Payment Request API will close the dialog
523
+ // This is expected behavior - we cannot proceed without a valid merchant session
511
524
  event.complete(merchantSessionPromise);
512
525
  } catch (error) {
513
- console.error("[Apple Pay] Merchant validation error:", error);
514
- // Only call onError if it's defined
526
+ console.error("[Apple Pay] Merchant validation error:", {
527
+ message: error.message,
528
+ stack: error.stack,
529
+ response: error.response
530
+ });
531
+
532
+ // Call onError to notify the user
515
533
  if (typeof onError === 'function') {
516
- onError(error);
534
+ onError(new Error(`Apple Pay merchant validation failed: ${error.message}. Please check your Payone Apple Pay configuration in PMI (CONFIGURATION → PAYMENT PORTALS → [Your Portal] → Apple Pay).`));
517
535
  }
518
- // Complete with a promise that resolves to empty object
519
- // This allows the dialog to continue even if validation fails
520
- // Apple Pay will show an error but won't close immediately
521
- event.complete(Promise.resolve({}));
536
+
537
+ // Complete with a rejected promise
538
+ // This will cause Apple Pay to close the dialog, which is expected behavior
539
+ // We cannot proceed without a valid merchant session from Payone
540
+ event.complete(Promise.reject(error));
522
541
  }
523
542
  };
524
543
 
@@ -550,17 +569,42 @@ const ApplePayButton = ({
550
569
  let response;
551
570
  try {
552
571
  response = await request.show();
572
+ console.log("[Apple Pay] Payment sheet shown successfully");
553
573
  } catch (error) {
554
- console.error("[Apple Pay] Error showing payment sheet:", error);
574
+ console.error("[Apple Pay] Error showing payment sheet:", {
575
+ name: error.name,
576
+ message: error.message,
577
+ stack: error.stack
578
+ });
579
+
555
580
  // Check if error is due to cancellation (user cancelled)
556
- if (error.message && error.message.includes('Cancelled')) {
581
+ // Payment Request API throws "AbortError" when user cancels
582
+ if (error.name === 'AbortError' ||
583
+ (error.message && (
584
+ error.message.includes('Cancelled') ||
585
+ error.message.includes('cancel') ||
586
+ error.message.includes('abort')
587
+ ))) {
557
588
  console.log("[Apple Pay] User cancelled the payment");
558
589
  // Don't call onError for user cancellation
559
590
  return;
560
591
  }
561
- // Only call onError if it's defined and it's not a cancellation
562
- if (typeof onError === 'function') {
563
- onError(error);
592
+
593
+ // If it's a merchant validation error, log it specifically
594
+ if (error.message && (
595
+ error.message.includes('merchant') ||
596
+ error.message.includes('validation') ||
597
+ error.message.includes('identifier')
598
+ )) {
599
+ console.error("[Apple Pay] Merchant validation failed - this may cause the dialog to close");
600
+ if (typeof onError === 'function') {
601
+ onError(new Error("Merchant validation failed. Please check your Apple Pay configuration and merchant identifier in Payone settings."));
602
+ }
603
+ } else {
604
+ // For other errors, call onError
605
+ if (typeof onError === 'function') {
606
+ onError(error);
607
+ }
564
608
  }
565
609
  return;
566
610
  }
@@ -573,11 +617,11 @@ const ApplePayButton = ({
573
617
 
574
618
  // Extract payment token
575
619
  const paymentToken = response.details?.paymentToken || response.details?.token;
576
-
620
+
577
621
  if (!paymentToken) {
578
622
  console.error("[Apple Pay] Payment token is missing from response");
579
623
  try {
580
- await response.complete("fail");
624
+ await response.complete("fail");
581
625
  } catch (completeError) {
582
626
  console.error("[Apple Pay] Error completing payment with fail:", completeError);
583
627
  }
@@ -619,11 +663,11 @@ const ApplePayButton = ({
619
663
  // The callback should set the token in state and return success immediately
620
664
  // It should NOT process the payment yet - that will happen when user clicks the button
621
665
  const callbackResult = onTokenReceived(tokenString, {
622
- paymentToken: paymentToken,
623
- billingContact: response.payerName || response.details?.billingContact,
624
- shippingContact: response.shippingAddress || response.details?.shippingAddress,
625
- shippingOption: response.shippingOption || response.details?.shippingOption
626
- });
666
+ paymentToken: paymentToken,
667
+ billingContact: response.payerName || response.details?.billingContact,
668
+ shippingContact: response.shippingAddress || response.details?.shippingAddress,
669
+ shippingOption: response.shippingOption || response.details?.shippingOption
670
+ });
627
671
 
628
672
  // If callback returns a promise, wait for it to resolve or reject
629
673
  if (callbackResult && typeof callbackResult.then === 'function') {
@@ -735,12 +779,12 @@ const ApplePayButton = ({
735
779
  }
736
780
  });
737
781
 
738
- console.log("[Apple Pay] Merchant session received from backend:", {
739
- hasData: !!merchantSession.data,
740
- merchantIdentifier: merchantSession.data?.merchantIdentifier
741
- });
782
+ console.log("[Apple Pay] Merchant session received from backend:", {
783
+ hasData: !!merchantSession.data,
784
+ merchantIdentifier: merchantSession.data?.merchantIdentifier
785
+ });
742
786
 
743
- return merchantSession.data || merchantSession;
787
+ return merchantSession.data || merchantSession;
744
788
  } catch (error) {
745
789
  console.error("[Apple Pay] Merchant validation error:", {
746
790
  message: error.message,
@@ -759,10 +803,10 @@ const ApplePayButton = ({
759
803
  console.error("[Apple Pay] Server error - Check server logs for details.");
760
804
  }
761
805
 
762
- // If validation fails, return empty object
763
- // This allows the dialog to continue, though Apple Pay may show an error
764
- // The user can still proceed with payment if they want
765
- return {};
806
+ // If validation fails, we cannot proceed
807
+ // Apple Pay requires a valid merchant session from Payone
808
+ console.error("[Apple Pay] Merchant validation failed - cannot proceed without valid session");
809
+ throw error;
766
810
  }
767
811
  };
768
812
 
@@ -41,16 +41,16 @@ const ApplePayConfig = ({
41
41
  ...config,
42
42
  countryCode: value
43
43
  };
44
-
44
+
45
45
  // Auto-update currency if current currency is not supported in new country
46
46
  const newSupportedCurrencies = getSupportedCurrenciesForCountry(value);
47
47
  if (!newSupportedCurrencies.find(c => c.code === currencyCode)) {
48
48
  newConfig.currencyCode = newSupportedCurrencies[0]?.code || "USD";
49
49
  }
50
-
50
+
51
51
  // Auto-update networks based on country
52
52
  newConfig.supportedNetworks = getSupportedNetworksForCountry(value);
53
-
53
+
54
54
  onConfigChange(newConfig);
55
55
  };
56
56
 
@@ -66,7 +66,7 @@ const ApplePayConfig = ({
66
66
  const newNetworks = currentNetworks.includes(networkCode)
67
67
  ? currentNetworks.filter(n => n !== networkCode)
68
68
  : [...currentNetworks, networkCode];
69
-
69
+
70
70
  onConfigChange({
71
71
  ...config,
72
72
  supportedNetworks: newNetworks
@@ -78,7 +78,7 @@ const ApplePayConfig = ({
78
78
  const newCapabilities = currentCapabilities.includes(capabilityCode)
79
79
  ? currentCapabilities.filter(c => c !== capabilityCode)
80
80
  : [...currentCapabilities, capabilityCode];
81
-
81
+
82
82
  onConfigChange({
83
83
  ...config,
84
84
  merchantCapabilities: newCapabilities
@@ -100,43 +100,43 @@ const ApplePayConfig = ({
100
100
  {/* Country and Currency */}
101
101
  <Flex gap={4} wrap="wrap">
102
102
  <Box style={{ flex: 1, minWidth: "300px" }}>
103
- <Select
103
+ <Select
104
104
  label="Country Code"
105
- name="countryCode"
106
- value={countryCode}
107
- onChange={handleCountryChange}
108
- hint="Select the country where your business operates"
109
- required
110
- >
111
- {APPLE_PAY_SUPPORTED_COUNTRIES.map(country => (
112
- <Option key={country.code} value={country.code}>
113
- {country.name} ({country.code})
114
- </Option>
115
- ))}
116
- </Select>
117
- </Box>
105
+ name="countryCode"
106
+ value={countryCode}
107
+ onChange={handleCountryChange}
108
+ hint="Select the country where your business operates"
109
+ required
110
+ >
111
+ {APPLE_PAY_SUPPORTED_COUNTRIES.map(country => (
112
+ <Option key={country.code} value={country.code}>
113
+ {country.name} ({country.code})
114
+ </Option>
115
+ ))}
116
+ </Select>
117
+ </Box>
118
118
 
119
119
  <Box style={{ flex: 1, minWidth: "300px" }}>
120
- <Select
120
+ <Select
121
121
  label="Currency Code"
122
- name="currencyCode"
123
- value={currencyCode}
124
- onChange={handleCurrencyChange}
122
+ name="currencyCode"
123
+ value={currencyCode}
124
+ onChange={handleCurrencyChange}
125
125
  hint={`Supported currencies for ${countryCode}`}
126
- required
127
- >
128
- {supportedCurrencies.map(currency => (
129
- <Option key={currency.code} value={currency.code}>
130
- {currency.name} ({currency.code}) {currency.symbol}
131
- </Option>
132
- ))}
133
- </Select>
134
- {supportedCurrencies.length === 0 && (
135
- <Typography variant="pi" textColor="danger600" style={{ marginTop: "4px" }}>
136
- No supported currencies for this country. Please select a different country.
137
- </Typography>
138
- )}
139
- </Box>
126
+ required
127
+ >
128
+ {supportedCurrencies.map(currency => (
129
+ <Option key={currency.code} value={currency.code}>
130
+ {currency.name} ({currency.code}) {currency.symbol}
131
+ </Option>
132
+ ))}
133
+ </Select>
134
+ {supportedCurrencies.length === 0 && (
135
+ <Typography variant="pi" textColor="danger600" style={{ marginTop: "4px" }}>
136
+ No supported currencies for this country. Please select a different country.
137
+ </Typography>
138
+ )}
139
+ </Box>
140
140
  </Flex>
141
141
 
142
142
  {/* Button Style and Type */}
@@ -186,23 +186,23 @@ const ApplePayConfig = ({
186
186
  {APPLE_PAY_SUPPORTED_NETWORKS.map(network => {
187
187
  const isSupported = supportedNetworksForCountry.includes(network.code);
188
188
  const isSelected = supportedNetworks?.includes(network.code);
189
-
189
+
190
190
  return (
191
191
  <Box key={network.code} style={{ flex: "0 0 calc(50% - 8px)", minWidth: "250px" }}>
192
- <Checkbox
193
- name={`network-${network.code}`}
194
- checked={isSelected}
195
- onChange={() => handleNetworkToggle(network.code)}
196
- disabled={!isSupported}
197
- hint={!isSupported ? `Not supported in ${countryCode}` : undefined}
198
- >
199
- {network.name} ({network.code})
200
- {!isSupported && (
201
- <Typography variant="sigma" textColor="neutral500" style={{ marginLeft: "8px" }}>
192
+ <Checkbox
193
+ name={`network-${network.code}`}
194
+ checked={isSelected}
195
+ onChange={() => handleNetworkToggle(network.code)}
196
+ disabled={!isSupported}
197
+ hint={!isSupported ? `Not supported in ${countryCode}` : undefined}
198
+ >
199
+ {network.name} ({network.code})
200
+ {!isSupported && (
201
+ <Typography variant="sigma" textColor="neutral500" style={{ marginLeft: "8px" }}>
202
202
  (Not available)
203
- </Typography>
204
- )}
205
- </Checkbox>
203
+ </Typography>
204
+ )}
205
+ </Checkbox>
206
206
  </Box>
207
207
  );
208
208
  })}
@@ -225,16 +225,16 @@ const ApplePayConfig = ({
225
225
  <Flex wrap="wrap" gap={4} style={{ marginTop: "12px" }}>
226
226
  {APPLE_PAY_MERCHANT_CAPABILITIES.map(capability => {
227
227
  const isSelected = merchantCapabilities?.includes(capability.code);
228
-
228
+
229
229
  return (
230
230
  <Box key={capability.code} style={{ flex: "0 0 calc(50% - 8px)", minWidth: "250px" }}>
231
- <Checkbox
232
- name={`capability-${capability.code}`}
233
- checked={isSelected}
234
- onChange={() => handleCapabilityToggle(capability.code)}
235
- >
236
- {capability.name} - {capability.description}
237
- </Checkbox>
231
+ <Checkbox
232
+ name={`capability-${capability.code}`}
233
+ checked={isSelected}
234
+ onChange={() => handleCapabilityToggle(capability.code)}
235
+ >
236
+ {capability.name} - {capability.description}
237
+ </Checkbox>
238
238
  </Box>
239
239
  );
240
240
  })}
@@ -256,49 +256,49 @@ const ApplePayConfig = ({
256
256
  </Typography>
257
257
  <Flex wrap="wrap" gap={4} style={{ marginTop: "12px" }}>
258
258
  <Box style={{ flex: "0 0 calc(33.333% - 11px)", minWidth: "200px" }}>
259
- <Checkbox
260
- name="requestPayerName"
261
- checked={requestPayerName}
262
- onChange={(checked) => onConfigChange({ ...config, requestPayerName: checked })}
263
- >
264
- Request Payer Name
265
- </Checkbox>
259
+ <Checkbox
260
+ name="requestPayerName"
261
+ checked={requestPayerName}
262
+ onChange={(checked) => onConfigChange({ ...config, requestPayerName: checked })}
263
+ >
264
+ Request Payer Name
265
+ </Checkbox>
266
266
  </Box>
267
267
  <Box style={{ flex: "0 0 calc(33.333% - 11px)", minWidth: "200px" }}>
268
- <Checkbox
269
- name="requestBillingAddress"
270
- checked={requestBillingAddress}
271
- onChange={(checked) => onConfigChange({ ...config, requestBillingAddress: checked })}
272
- >
273
- Request Billing Address
274
- </Checkbox>
268
+ <Checkbox
269
+ name="requestBillingAddress"
270
+ checked={requestBillingAddress}
271
+ onChange={(checked) => onConfigChange({ ...config, requestBillingAddress: checked })}
272
+ >
273
+ Request Billing Address
274
+ </Checkbox>
275
275
  </Box>
276
276
  <Box style={{ flex: "0 0 calc(33.333% - 11px)", minWidth: "200px" }}>
277
- <Checkbox
278
- name="requestPayerEmail"
279
- checked={requestPayerEmail}
280
- onChange={(checked) => onConfigChange({ ...config, requestPayerEmail: checked })}
281
- >
282
- Request Payer Email
283
- </Checkbox>
277
+ <Checkbox
278
+ name="requestPayerEmail"
279
+ checked={requestPayerEmail}
280
+ onChange={(checked) => onConfigChange({ ...config, requestPayerEmail: checked })}
281
+ >
282
+ Request Payer Email
283
+ </Checkbox>
284
284
  </Box>
285
285
  <Box style={{ flex: "0 0 calc(33.333% - 11px)", minWidth: "200px" }}>
286
- <Checkbox
287
- name="requestPayerPhone"
288
- checked={requestPayerPhone}
289
- onChange={(checked) => onConfigChange({ ...config, requestPayerPhone: checked })}
290
- >
291
- Request Payer Phone
292
- </Checkbox>
286
+ <Checkbox
287
+ name="requestPayerPhone"
288
+ checked={requestPayerPhone}
289
+ onChange={(checked) => onConfigChange({ ...config, requestPayerPhone: checked })}
290
+ >
291
+ Request Payer Phone
292
+ </Checkbox>
293
293
  </Box>
294
294
  <Box style={{ flex: "0 0 calc(33.333% - 11px)", minWidth: "200px" }}>
295
- <Checkbox
296
- name="requestShipping"
297
- checked={requestShipping}
298
- onChange={(checked) => onConfigChange({ ...config, requestShipping: checked })}
299
- >
300
- Request Shipping Address
301
- </Checkbox>
295
+ <Checkbox
296
+ name="requestShipping"
297
+ checked={requestShipping}
298
+ onChange={(checked) => onConfigChange({ ...config, requestShipping: checked })}
299
+ >
300
+ Request Shipping Address
301
+ </Checkbox>
302
302
  </Box>
303
303
  </Flex>
304
304
  </Box> */}
@@ -309,7 +309,7 @@ const ApplePayConfig = ({
309
309
  Merchant Identifier
310
310
  </Typography>
311
311
  <Typography variant="pi" textColor="neutral600">
312
- {settings?.mid || settings?.portalid
312
+ {settings?.mid || settings?.portalid
313
313
  ? `Using: ${settings.mid || settings.portalid}`
314
314
  : "Merchant identifier will be obtained from Payone after domain verification. See documentation for setup instructions."
315
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-payone-provider",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Strapi plugin for Payone payment gateway integration",
5
5
  "license": "MIT",
6
6
  "maintainers": [
@@ -214,57 +214,202 @@ const validateApplePayMerchant = async (strapi, params) => {
214
214
  strapi.log.info("[Apple Pay] Session initialization result:", {
215
215
  status: sessionResponse.status || sessionResponse.Status,
216
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),
217
220
  fullResponse: JSON.stringify(sessionResponse, null, 2)
218
221
  });
219
222
 
220
- // If session initialization is successful, return merchant session
221
- // Payone will provide the merchant identifier and validation data
223
+ // If session initialization is successful, extract merchant session from Payone response
224
+ // Payone returns: add_paydata[applepay_payment_session] = BASE64 encoded merchant session
222
225
  // Check for both uppercase and lowercase status
223
226
  const responseStatus = sessionResponse.status || sessionResponse.Status;
224
- if (responseStatus === "APPROVED" || responseStatus === "REDIRECT" ||
225
- responseStatus === "approved" || responseStatus === "redirect") {
226
- strapi.log.info("[Apple Pay] Session approved, creating merchant session object");
227
- // Get merchant identifier from Payone response or settings
228
- const merchantIdentifier = sessionResponse.merchantIdentifier ||
229
- sessionResponse.merchantSessionIdentifier ||
230
- settings.merchantIdentifier ||
231
- settings.mid ||
232
- settings.portalid ||
233
- `merchant.${domainName}`;
234
-
235
- // Return merchant session object
236
- // In a real implementation, you would get this from Payone's response
237
- const merchantSession = {
238
- epochTimestamp: Date.now(),
239
- expiresAt: Date.now() + (5 * 60 * 1000), // 5 minutes
240
- merchantSessionIdentifier: sessionResponse.merchantSessionIdentifier || `merchant.${domainName}`,
241
- nonce: sessionResponse.nonce || generateNonce(),
242
- merchantIdentifier: merchantIdentifier,
243
- domainName: domainName,
244
- displayName: merchantName
245
- };
246
-
247
- strapi.log.info("[Apple Pay] Merchant session created:", {
248
- merchantIdentifier: merchantSession.merchantIdentifier,
249
- domainName: merchantSession.domainName,
250
- expiresAt: new Date(merchantSession.expiresAt).toISOString()
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)
251
248
  });
252
249
 
253
- return merchantSession;
250
+ strapi.log.info("[Apple Pay] Extracted Apple Pay session data:", {
251
+ hasBase64Session: !!applePaySessionBase64,
252
+ sessionLength: applePaySessionBase64?.length,
253
+ workorderid: sessionResponse.workorderid,
254
+ allResponseKeys: Object.keys(sessionResponse),
255
+ responseSample: JSON.stringify(sessionResponse).substring(0, 500)
256
+ });
257
+
258
+ if (applePaySessionBase64 && applePaySessionBase64.length > 0) {
259
+ try {
260
+ // Decode BASE64 merchant session
261
+ const merchantSessionJson = Buffer.from(applePaySessionBase64, 'base64').toString('utf-8');
262
+ const merchantSession = JSON.parse(merchantSessionJson);
263
+
264
+ strapi.log.info("[Apple Pay] Decoded merchant session:", {
265
+ merchantIdentifier: merchantSession.merchantIdentifier,
266
+ domainName: merchantSession.domainName,
267
+ displayName: merchantSession.displayName,
268
+ hasEpochTimestamp: !!merchantSession.epochTimestamp,
269
+ hasExpiresAt: !!merchantSession.expiresAt,
270
+ fullSession: merchantSession
271
+ });
272
+
273
+ // Validate decoded merchant session
274
+ if (!merchantSession.merchantIdentifier) {
275
+ strapi.log.warn("[Apple Pay] Decoded merchant session missing merchantIdentifier, using fallback");
276
+ // Use fallback merchant identifier
277
+ merchantSession.merchantIdentifier = settings.merchantIdentifier ||
278
+ settings.mid ||
279
+ settings.portalid ||
280
+ `merchant.${domainName}`;
281
+ }
282
+
283
+ // Ensure epochTimestamp and expiresAt are in seconds (not milliseconds)
284
+ if (merchantSession.epochTimestamp && merchantSession.epochTimestamp > 1000000000000) {
285
+ // If timestamp is in milliseconds, convert to seconds
286
+ merchantSession.epochTimestamp = Math.floor(merchantSession.epochTimestamp / 1000);
287
+ }
288
+ if (merchantSession.expiresAt && merchantSession.expiresAt > 1000000000000) {
289
+ // If timestamp is in milliseconds, convert to seconds
290
+ merchantSession.expiresAt = Math.floor(merchantSession.expiresAt / 1000);
291
+ }
292
+
293
+ // Validate final merchant session
294
+ if (!merchantSession.merchantIdentifier || merchantSession.merchantIdentifier === 'undefined' || merchantSession.merchantIdentifier === 'null') {
295
+ throw new Error("Decoded merchant session has invalid merchantIdentifier");
296
+ }
297
+
298
+ strapi.log.info("[Apple Pay] Validated merchant session:", {
299
+ merchantIdentifier: merchantSession.merchantIdentifier,
300
+ domainName: merchantSession.domainName,
301
+ epochTimestamp: merchantSession.epochTimestamp,
302
+ expiresAt: merchantSession.expiresAt
303
+ });
304
+
305
+ return merchantSession;
306
+ } catch (decodeError) {
307
+ strapi.log.error("[Apple Pay] Failed to decode merchant session:", {
308
+ error: decodeError.message,
309
+ base64Length: applePaySessionBase64?.length,
310
+ base64Preview: applePaySessionBase64?.substring(0, 100)
311
+ });
312
+
313
+ // If decoding fails, we cannot proceed - merchant session is invalid
314
+ throw new Error(`Failed to decode Apple Pay merchant session: ${decodeError.message}`);
315
+ }
316
+ } else {
317
+ // CRITICAL: If Payone doesn't return merchant session, we cannot proceed
318
+ // Apple Pay requires the merchant session to be validated by Apple's servers
319
+ // Payone should return add_paydata[applepay_payment_session] after successful initialization
320
+ strapi.log.error("[Apple Pay] CRITICAL: No Apple Pay session data in Payone response!");
321
+ strapi.log.error("[Apple Pay] This means merchant validation will fail. Possible causes:");
322
+ strapi.log.error("[Apple Pay] 1. Apple Pay not properly configured in Payone PMI");
323
+ strapi.log.error("[Apple Pay] 2. Domain not verified in Payone PMI");
324
+ strapi.log.error("[Apple Pay] 3. Merchant identifier not configured in Payone PMI");
325
+ strapi.log.error("[Apple Pay] 4. Apple Pay onboarding not completed in Payone PMI");
326
+ strapi.log.error("[Apple Pay] Response status:", responseStatus);
327
+ strapi.log.error("[Apple Pay] Full response keys:", Object.keys(sessionResponse));
328
+ strapi.log.error("[Apple Pay] Response sample:", JSON.stringify(sessionResponse).substring(0, 1000));
329
+
330
+ // DO NOT create a fallback session - it will fail validation
331
+ // Instead, throw an error so the frontend knows validation failed
332
+ throw new Error("Payone did not return Apple Pay merchant session. Please ensure Apple Pay is properly configured in Payone Merchant Interface (PMI): CONFIGURATION → PAYMENT PORTALS → [Your Portal] → Apple Pay configuration. The merchant session must come from Payone after successful Apple Pay onboarding.");
333
+
334
+ // Get merchant identifier from settings
335
+ // According to Payone docs, merchant identifier should be visible in PMI after onboarding
336
+ // Path: CONFIGURATION/PAYMENT PORTALS - choose an onboarded Portal - Payment type configuration tab
337
+ let merchantIdentifier = settings.merchantIdentifier ||
338
+ settings.mid ||
339
+ settings.portalid;
340
+
341
+ // If still no merchant identifier, try to construct one from domain
342
+ // But this is not ideal - merchant identifier should come from Payone PMI
343
+ if (!merchantIdentifier) {
344
+ strapi.log.warn("[Apple Pay] No merchant identifier found in settings, using domain-based fallback");
345
+ merchantIdentifier = `merchant.${domainName}`;
346
+ }
347
+
348
+ // Ensure merchant identifier is a string and not empty
349
+ merchantIdentifier = merchantIdentifier.toString().trim();
350
+ if (!merchantIdentifier || merchantIdentifier === 'undefined' || merchantIdentifier === 'null') {
351
+ strapi.log.error("[Apple Pay] Invalid merchant identifier:", merchantIdentifier);
352
+ 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");
353
+ }
354
+
355
+ // Create a valid merchant session object
356
+ // This format is required by Apple Pay Payment Request API
357
+ // IMPORTANT: epochTimestamp and expiresAt must be in seconds (Unix timestamp), not milliseconds
358
+ const merchantSession = {
359
+ epochTimestamp: Math.floor(Date.now() / 1000), // Unix timestamp in seconds
360
+ expiresAt: Math.floor((Date.now() + (5 * 60 * 1000)) / 1000), // 5 minutes from now, in seconds
361
+ merchantSessionIdentifier: `merchant.${domainName}`,
362
+ nonce: generateNonce(),
363
+ merchantIdentifier: merchantIdentifier, // Already validated and converted to string
364
+ domainName: domainName,
365
+ displayName: merchantName
366
+ };
367
+
368
+ // Validate merchant session before returning
369
+ if (!merchantSession.merchantIdentifier || merchantSession.merchantIdentifier === 'undefined' || merchantSession.merchantIdentifier === 'null') {
370
+ strapi.log.error("[Apple Pay] Created merchant session is missing or invalid merchantIdentifier!", {
371
+ merchantIdentifier: merchantSession.merchantIdentifier,
372
+ settings: {
373
+ hasMerchantIdentifier: !!settings.merchantIdentifier,
374
+ hasMid: !!settings.mid,
375
+ hasPortalid: !!settings.portalid,
376
+ mid: settings.mid,
377
+ portalid: settings.portalid
378
+ }
379
+ });
380
+ 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.");
381
+ }
382
+
383
+ strapi.log.info("[Apple Pay] Created merchant session from settings:", {
384
+ merchantIdentifier: merchantSession.merchantIdentifier,
385
+ domainName: merchantSession.domainName,
386
+ displayName: merchantSession.displayName,
387
+ epochTimestamp: merchantSession.epochTimestamp,
388
+ expiresAt: merchantSession.expiresAt
389
+ });
390
+
391
+ return merchantSession;
392
+ }
254
393
  }
255
394
 
256
- // If initialization failed, return empty object
257
- // Payment Request API will handle it
258
- strapi.log.warn("[Apple Pay] Session initialization failed, returning empty object");
259
- return {};
395
+ // If initialization failed, we cannot proceed
396
+ // Payment Request API requires a valid merchant session
397
+ strapi.log.error("[Apple Pay] Session initialization failed - status:", responseStatus);
398
+ strapi.log.error("[Apple Pay] This means merchant validation will fail.");
399
+ strapi.log.error("[Apple Pay] Please check Payone PMI configuration and ensure Apple Pay is properly set up.");
400
+ throw new Error(`Apple Pay session initialization failed with status: ${responseStatus}. Please check your Payone Apple Pay configuration in PMI.`);
260
401
  } catch (error) {
261
402
  strapi.log.error("[Apple Pay] Merchant validation error:", {
262
403
  message: error.message,
263
404
  stack: error.stack,
264
- response: error.response?.data
405
+ response: error.response?.data,
406
+ status: error.response?.status
265
407
  });
266
- // Return empty object on error - Payone will handle validation
267
- return {};
408
+
409
+ // DO NOT return empty object - this causes Apple Pay to close the dialog
410
+ // Instead, re-throw the error so the frontend can handle it properly
411
+ // The error message will help the user understand what went wrong
412
+ throw error;
268
413
  }
269
414
  };
270
415
 
@@ -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
  };