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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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,
|
|
172
|
-
//
|
|
173
|
-
|
|
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 (
|
|
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
|
-
//
|
|
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 &&
|
|
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
|
-
|
|
526
|
+
// Only call onError if it's defined
|
|
527
|
+
if (typeof onError === 'function') {
|
|
470
528
|
onError(error);
|
|
471
529
|
}
|
|
472
|
-
// Complete with
|
|
473
|
-
|
|
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:",
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
664
|
-
|
|
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
|
-
|
|
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]
|
|
775
|
+
console.log("[Apple Pay] Merchant session received from backend:", {
|
|
776
|
+
hasData: !!merchantSession.data,
|
|
777
|
+
merchantIdentifier: merchantSession.data?.merchantIdentifier
|
|
778
|
+
});
|
|
676
779
|
|
|
677
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
60
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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,
|
|
185
|
-
// Payone
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|