strapi-plugin-payone-provider 1.5.0 → 1.5.3

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.
@@ -0,0 +1,217 @@
1
+ import React from "react";
2
+ import { Box, Flex, Typography, Select, Option, Checkbox, Stack } from "@strapi/design-system";
3
+ import {
4
+ GOOGLE_PAY_SUPPORTED_COUNTRIES,
5
+ GOOGLE_PAY_SUPPORTED_CURRENCIES,
6
+ GOOGLE_PAY_SUPPORTED_NETWORKS,
7
+ GOOGLE_PAY_AUTH_METHODS,
8
+ DEFAULT_GOOGLE_PAY_CONFIG
9
+ } from "../../utils/googlePayConstants";
10
+
11
+ const GooglePayConfig = ({
12
+ config,
13
+ onConfigChange,
14
+ settings
15
+ }) => {
16
+ const {
17
+ countryCode = DEFAULT_GOOGLE_PAY_CONFIG.countryCode,
18
+ currencyCode = DEFAULT_GOOGLE_PAY_CONFIG.currencyCode,
19
+ allowedCardNetworks = DEFAULT_GOOGLE_PAY_CONFIG.allowedCardNetworks,
20
+ allowedAuthMethods = DEFAULT_GOOGLE_PAY_CONFIG.allowedAuthMethods,
21
+ merchantName = DEFAULT_GOOGLE_PAY_CONFIG.merchantName
22
+ } = config || {};
23
+
24
+ const handleCountryChange = (value) => {
25
+ onConfigChange({
26
+ ...config,
27
+ countryCode: value
28
+ });
29
+ };
30
+
31
+ const handleCurrencyChange = (value) => {
32
+ onConfigChange({
33
+ ...config,
34
+ currencyCode: value
35
+ });
36
+ };
37
+
38
+ const handleNetworkToggle = (networkCode) => {
39
+ const currentNetworks = allowedCardNetworks || [];
40
+ const newNetworks = currentNetworks.includes(networkCode)
41
+ ? currentNetworks.filter(n => n !== networkCode)
42
+ : [...currentNetworks, networkCode];
43
+
44
+ onConfigChange({
45
+ ...config,
46
+ allowedCardNetworks: newNetworks
47
+ });
48
+ };
49
+
50
+ const handleAuthMethodToggle = (authMethodCode) => {
51
+ const currentMethods = allowedAuthMethods || [];
52
+ const newMethods = currentMethods.includes(authMethodCode)
53
+ ? currentMethods.filter(m => m !== authMethodCode)
54
+ : [...currentMethods, authMethodCode];
55
+
56
+ onConfigChange({
57
+ ...config,
58
+ allowedAuthMethods: newMethods
59
+ });
60
+ };
61
+
62
+ return (
63
+ <Box>
64
+ <Stack spacing={6}>
65
+ <Box>
66
+ <Typography variant="delta" as="h3" fontWeight="bold" style={{ marginBottom: "6px" }}>
67
+ Google Pay Configuration
68
+ </Typography>
69
+ <Typography variant="pi" textColor="neutral600">
70
+ Configure Google Pay settings for your payment gateway
71
+ </Typography>
72
+ </Box>
73
+
74
+ {/* Country and Currency */}
75
+ <Flex gap={4} wrap="wrap">
76
+ <Box style={{ flex: 1, minWidth: "300px" }}>
77
+ <Select
78
+ label="Country Code"
79
+ name="countryCode"
80
+ value={countryCode}
81
+ onChange={handleCountryChange}
82
+ hint="Select the country where your business operates"
83
+ required
84
+ >
85
+ {GOOGLE_PAY_SUPPORTED_COUNTRIES.map(country => (
86
+ <Option key={country.code} value={country.code}>
87
+ {country.name} ({country.code})
88
+ </Option>
89
+ ))}
90
+ </Select>
91
+ </Box>
92
+
93
+ <Box style={{ flex: 1, minWidth: "300px" }}>
94
+ <Select
95
+ label="Currency Code"
96
+ name="currencyCode"
97
+ value={currencyCode}
98
+ onChange={handleCurrencyChange}
99
+ hint="Select the currency for transactions"
100
+ required
101
+ >
102
+ {GOOGLE_PAY_SUPPORTED_CURRENCIES.map(currency => (
103
+ <Option key={currency.code} value={currency.code}>
104
+ {currency.name} ({currency.code}) {currency.symbol}
105
+ </Option>
106
+ ))}
107
+ </Select>
108
+ </Box>
109
+ </Flex>
110
+
111
+ {/* Merchant Name */}
112
+ <Box>
113
+ <Typography variant="pi" fontWeight="semiBold" style={{ marginLeft: "2px" }}>
114
+ Merchant Name
115
+ </Typography>
116
+ <Typography variant="pi" textColor="neutral600" style={{ marginLeft: "2px" }}>
117
+ The name of your business as it will appear in Google Pay
118
+ </Typography>
119
+ <input
120
+ type="text"
121
+ value={merchantName}
122
+ onChange={(e) => onConfigChange({ ...config, merchantName: e.target.value })}
123
+ style={{
124
+ width: "100%",
125
+ padding: "8px 12px",
126
+ marginTop: "8px",
127
+ border: "1px solid #dcdce4",
128
+ borderRadius: "4px",
129
+ fontSize: "14px"
130
+ }}
131
+ placeholder="Your Store Name"
132
+ />
133
+ </Box>
134
+
135
+ {/* Allowed Card Networks */}
136
+ <Box>
137
+ <Typography variant="pi" fontWeight="semiBold" style={{ marginLeft: "2px" }}>
138
+ Allowed Card Networks
139
+ </Typography>
140
+ <Typography variant="pi" textColor="neutral600" style={{ marginLeft: "2px" }}>
141
+ Select payment card networks to accept
142
+ </Typography>
143
+ <Flex wrap="wrap" gap={4} style={{ marginTop: "12px" }}>
144
+ {GOOGLE_PAY_SUPPORTED_NETWORKS.map(network => {
145
+ const isSelected = allowedCardNetworks?.includes(network.code);
146
+
147
+ return (
148
+ <Box key={network.code} style={{ flex: "0 0 calc(50% - 8px)", minWidth: "250px" }}>
149
+ <Checkbox
150
+ name={`network-${network.code}`}
151
+ checked={isSelected}
152
+ onChange={() => handleNetworkToggle(network.code)}
153
+ >
154
+ {network.name} ({network.code})
155
+ </Checkbox>
156
+ </Box>
157
+ );
158
+ })}
159
+ </Flex>
160
+ {allowedCardNetworks?.length === 0 && (
161
+ <Typography variant="pi" textColor="danger600" style={{ marginTop: "8px" }}>
162
+ At least one card network must be selected
163
+ </Typography>
164
+ )}
165
+ </Box>
166
+
167
+ {/* Allowed Authentication Methods */}
168
+ <Box>
169
+ <Typography variant="pi" fontWeight="semiBold" style={{ marginLeft: "2px" }}>
170
+ Allowed Authentication Methods
171
+ </Typography>
172
+ <Typography variant="pi" textColor="neutral600" style={{ marginLeft: "2px" }}>
173
+ Select authentication methods for card payments
174
+ </Typography>
175
+ <Flex wrap="wrap" gap={4} style={{ marginTop: "12px" }}>
176
+ {GOOGLE_PAY_AUTH_METHODS.map(method => {
177
+ const isSelected = allowedAuthMethods?.includes(method.code);
178
+
179
+ return (
180
+ <Box key={method.code} style={{ flex: "0 0 calc(50% - 8px)", minWidth: "250px" }}>
181
+ <Checkbox
182
+ name={`auth-method-${method.code}`}
183
+ checked={isSelected}
184
+ onChange={() => handleAuthMethodToggle(method.code)}
185
+ >
186
+ {method.name} - {method.description}
187
+ </Checkbox>
188
+ </Box>
189
+ );
190
+ })}
191
+ </Flex>
192
+ {allowedAuthMethods?.length === 0 && (
193
+ <Typography variant="pi" textColor="danger600" style={{ marginTop: "8px" }}>
194
+ At least one authentication method must be selected
195
+ </Typography>
196
+ )}
197
+ </Box>
198
+
199
+ {/* Gateway Merchant ID Info */}
200
+ <Box>
201
+ <Typography variant="pi" fontWeight="semiBold" style={{ marginLeft: "2px" }}>
202
+ Gateway Merchant ID
203
+ </Typography>
204
+ <Typography variant="pi" textColor="neutral600">
205
+ {settings?.mid || settings?.portalid
206
+ ? `Using: ${settings.mid || settings.portalid}`
207
+ : "Gateway merchant ID will be obtained from your Payone Merchant ID (MID) or Portal ID. Make sure these are configured in the main Configuration tab."
208
+ }
209
+ </Typography>
210
+ </Box>
211
+ </Stack>
212
+ </Box>
213
+ );
214
+ };
215
+
216
+ export default GooglePayConfig;
217
+
@@ -0,0 +1,82 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import {
3
+ Box,
4
+ Card,
5
+ CardBody,
6
+ Flex,
7
+ Typography,
8
+ Button
9
+ } from "@strapi/design-system";
10
+ import { Check } from "@strapi/icons";
11
+ import GooglePayConfig from "./GooglePayConfig";
12
+
13
+ const GooglePayConfigPanel = ({
14
+ settings,
15
+ onInputChange,
16
+ isSaving,
17
+ onSave,
18
+ onBack
19
+ }) => {
20
+ const [googlePayConfig, setGooglePayConfig] = useState(settings?.googlePayConfig || {});
21
+
22
+ useEffect(() => {
23
+ setGooglePayConfig(settings?.googlePayConfig || {});
24
+ }, [settings?.googlePayConfig]);
25
+
26
+ return (
27
+ <Box
28
+ className="payment-container"
29
+ paddingTop={8}
30
+ paddingBottom={8}
31
+ paddingLeft={8}
32
+ paddingRight={8}
33
+ >
34
+ <Flex direction="column" alignItems="stretch" gap={8}>
35
+ <Box>
36
+ <Typography variant="beta" as="h2" fontWeight="bold" className="payment-title" style={{ fontSize: '20px', marginBottom: '4px' }}>
37
+ Google Pay Configuration
38
+ </Typography>
39
+ <Typography variant="pi" textColor="neutral600" marginTop={2} className="payment-subtitle" style={{ fontSize: '14px' }}>
40
+ Configure Google Pay settings for your payment gateway
41
+ </Typography>
42
+ </Box>
43
+
44
+ <Box>
45
+ <Card className="payment-card">
46
+ <CardBody padding={6}>
47
+ <GooglePayConfig
48
+ config={googlePayConfig}
49
+ onConfigChange={(newConfig) => {
50
+ setGooglePayConfig(newConfig);
51
+ onInputChange("googlePayConfig", newConfig);
52
+ }}
53
+ settings={settings}
54
+ />
55
+ </CardBody>
56
+ </Card>
57
+ </Box>
58
+
59
+ <Box paddingTop={4}>
60
+ <Flex direction="row" gap={4} alignItems="center">
61
+ <Button
62
+ loading={isSaving}
63
+ onClick={onSave}
64
+ startIcon={<Check />}
65
+ size="L"
66
+ variant="default"
67
+ className="payment-button payment-button-success"
68
+ >
69
+ Save Google Pay Configuration
70
+ </Button>
71
+ <Typography variant="sigma" textColor="neutral600">
72
+ Note: Google Pay configuration is used for Google Pay payment requests. Make sure to configure the correct card networks, authentication methods, and merchant information for your region.
73
+ </Typography>
74
+ </Flex>
75
+ </Box>
76
+ </Flex>
77
+ </Box>
78
+ );
79
+ };
80
+
81
+ export default GooglePayConfigPanel;
82
+
@@ -291,7 +291,7 @@ const GooglePayButton = ({
291
291
  </Typography>
292
292
  </>
293
293
  )}
294
- <Box ref={buttonContainerRef} style={{ minHeight: "40px", width: "100%", display: "flex", justifyContent: "flex-start" }} />
294
+ <Box ref={buttonContainerRef} style={{ minHeight: "40px", width: "100%", maxWidth: "200px", display: "flex", justifyContent: "flex-start", }} />
295
295
  </Flex>
296
296
  </Box>
297
297
  );
@@ -45,8 +45,12 @@ const PaymentActionsPanel = ({
45
45
  cardexpiredate,
46
46
  setCardexpiredate,
47
47
  cardcvc2,
48
- setCardcvc2
48
+ setCardcvc2,
49
+ onNavigateToConfig
49
50
  }) => {
51
+ const mode = (settings?.mode || 'test').toLowerCase();
52
+ const isLiveMode = mode === 'live';
53
+
50
54
  return (
51
55
  <Box
52
56
  className="payment-container"
@@ -56,13 +60,18 @@ const PaymentActionsPanel = ({
56
60
  paddingRight={8}
57
61
  >
58
62
  <Flex direction="column" alignItems="stretch" gap={6}>
59
- <Box>
63
+ <Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '8px' }}>
60
64
  <Typography variant="beta" as="h2" className="payment-title" style={{ fontSize: '20px', marginBottom: '4px' }}>
61
65
  Payment Actions
62
66
  </Typography>
63
67
  <Typography variant="pi" textColor="neutral600" className="payment-subtitle" style={{ fontSize: '14px' }}>
64
68
  Process payments, captures, and refunds with multiple payment methods
65
69
  </Typography>
70
+ {isLiveMode && (
71
+ <Typography variant="pi" textColor="danger600" style={{ fontSize: '14px', marginTop: '8px', fontWeight: 'bold' }}>
72
+ ⚠️ Payment Actions are disabled in live mode for security reasons. Please use test mode for testing.
73
+ </Typography>
74
+ )}
66
75
  </Box>
67
76
 
68
77
  <PaymentMethodSelector
@@ -70,11 +79,12 @@ const PaymentActionsPanel = ({
70
79
  setPaymentMethod={setPaymentMethod}
71
80
  captureMode={captureMode}
72
81
  setCaptureMode={setCaptureMode}
82
+ onNavigateToConfig={onNavigateToConfig}
73
83
  />
74
84
 
75
85
  <hr className="payment-divider" />
76
86
 
77
- <Box className="payment-form-section">
87
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
78
88
  <PreauthorizationForm
79
89
  paymentAmount={paymentAmount}
80
90
  setPaymentAmount={setPaymentAmount}
@@ -96,12 +106,13 @@ const PaymentActionsPanel = ({
96
106
  setCardexpiredate={setCardexpiredate}
97
107
  cardcvc2={cardcvc2}
98
108
  setCardcvc2={setCardcvc2}
109
+ isLiveMode={isLiveMode}
99
110
  />
100
111
  </Box>
101
112
 
102
113
  <hr className="payment-divider" />
103
114
 
104
- <Box className="payment-form-section">
115
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
105
116
  <AuthorizationForm
106
117
  paymentAmount={paymentAmount}
107
118
  setPaymentAmount={setPaymentAmount}
@@ -123,12 +134,13 @@ const PaymentActionsPanel = ({
123
134
  setCardexpiredate={setCardexpiredate}
124
135
  cardcvc2={cardcvc2}
125
136
  setCardcvc2={setCardcvc2}
137
+ isLiveMode={isLiveMode}
126
138
  />
127
139
  </Box>
128
140
 
129
141
  <hr className="payment-divider" />
130
142
 
131
- <Box className="payment-form-section">
143
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
132
144
  <CaptureForm
133
145
  paymentAmount={paymentAmount}
134
146
  setPaymentAmount={setPaymentAmount}
@@ -141,7 +153,7 @@ const PaymentActionsPanel = ({
141
153
 
142
154
  <hr className="payment-divider" />
143
155
 
144
- <Box className="payment-form-section">
156
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
145
157
  <RefundForm
146
158
  paymentAmount={paymentAmount}
147
159
  setPaymentAmount={setPaymentAmount}
@@ -25,7 +25,8 @@ const AuthorizationForm = ({
25
25
  cardexpiredate,
26
26
  setCardexpiredate,
27
27
  cardcvc2,
28
- setCardcvc2
28
+ setCardcvc2,
29
+ isLiveMode = false
29
30
  }) => {
30
31
  const handleGooglePayToken = (token, paymentData) => {
31
32
  if (!token) {
@@ -41,12 +42,35 @@ const AuthorizationForm = ({
41
42
  }
42
43
  };
43
44
 
44
- const handleApplePayToken = (token, paymentData) => {
45
+ const handleApplePayToken = async (token, paymentData) => {
45
46
  if (!token) {
46
- return;
47
+ console.error("[Apple Pay] Token is missing in handleApplePayToken");
48
+ return Promise.reject(new Error("Token is missing"));
47
49
  }
50
+
51
+ console.log("[Apple Pay] handleApplePayToken called with token:", {
52
+ hasToken: !!token,
53
+ tokenLength: token?.length,
54
+ paymentData: !!paymentData
55
+ });
56
+
57
+ // IMPORTANT: Set token in state immediately (synchronously)
58
+ // This ensures the token is saved before the dialog closes
48
59
  setApplePayToken(token);
49
- onAuthorization(token);
60
+
61
+ console.log("[Apple Pay] Token saved to state successfully");
62
+
63
+ // Don't call onAuthorization immediately
64
+ // Let the user manually trigger the payment using the button
65
+ // This prevents the dialog from closing prematurely if there's an error
66
+ // The dialog will close with success, and the user will see the "Process Authorization" button
67
+
68
+ // Return success immediately so the dialog closes properly
69
+ // The actual payment processing will happen when the user clicks the button
70
+ return Promise.resolve({
71
+ success: true,
72
+ message: "Token received successfully. Please click 'Process Authorization' to complete the payment."
73
+ });
50
74
  };
51
75
 
52
76
  const handleApplePayError = (error) => {
@@ -116,13 +140,33 @@ const AuthorizationForm = ({
116
140
  settings={settings}
117
141
  />
118
142
  ) : paymentMethod === "apl" ? (
119
- <ApplePayButton
120
- amount={paymentAmount}
121
- currency="EUR"
122
- onTokenReceived={handleApplePayToken}
123
- onError={handleApplePayError}
124
- settings={settings}
125
- />
143
+ <Box>
144
+ <ApplePayButton
145
+ amount={paymentAmount}
146
+ currency="EUR"
147
+ onTokenReceived={handleApplePayToken}
148
+ onError={handleApplePayError}
149
+ settings={settings}
150
+ />
151
+ {applePayToken && (
152
+ <Box marginTop={3} style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "8px" }}>
153
+ <Typography variant="pi" textColor="success600" style={{ marginBottom: "8px", fontWeight: "bold" }}>
154
+ ✓ Apple Pay token received. You can now process the authorization:
155
+ </Typography>
156
+ <Button
157
+ variant="default"
158
+ onClick={() => onAuthorization(applePayToken)}
159
+ loading={isProcessingPayment}
160
+ startIcon={<Play />}
161
+ style={{ maxWidth: '200px' }}
162
+ disabled={!paymentAmount.trim() || !authReference.trim() || isLiveMode}
163
+ className="payment-button payment-button-primary"
164
+ >
165
+ Process Authorization
166
+ </Button>
167
+ </Box>
168
+ )}
169
+ </Box>
126
170
  ) : (
127
171
  <Button
128
172
  variant="default"
@@ -135,7 +179,9 @@ const AuthorizationForm = ({
135
179
  !paymentAmount.trim() ||
136
180
  (paymentMethod === "cc" &&
137
181
  settings?.enable3DSecure !== false &&
138
- (!cardtype || !cardpan || !cardexpiredate || !cardcvc2))
182
+ (!cardtype || !cardpan || !cardexpiredate || !cardcvc2)) ||
183
+ (paymentMethod === "apl" && !applePayToken) ||
184
+ isLiveMode
139
185
  }
140
186
  >
141
187
  Process Authorization
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
- import { Box, Flex, Select, Option } from "@strapi/design-system";
2
+ import { Box, Flex, Select, Option, Typography, Link, Alert } from "@strapi/design-system";
3
+ import pluginId from "../../../../pluginId";
3
4
  import {
4
5
  getPaymentMethodOptions,
5
6
  supportsCaptureMode,
@@ -11,7 +12,8 @@ const PaymentMethodSelector = ({
11
12
  paymentMethod,
12
13
  setPaymentMethod,
13
14
  captureMode,
14
- setCaptureMode
15
+ setCaptureMode,
16
+ onNavigateToConfig
15
17
  }) => {
16
18
  return (
17
19
  <Box>
@@ -29,6 +31,108 @@ const PaymentMethodSelector = ({
29
31
  </Option>
30
32
  ))}
31
33
  </Select>
34
+ {paymentMethod === "apl" && onNavigateToConfig && (
35
+ <>
36
+ <Alert closeLabel="Close" title="⚠️ Important: Middleware Configuration Required" variant="warning">
37
+ <Typography variant="pi" marginTop={2}>
38
+ <strong>Apple Pay requires middleware configuration</strong> to work properly. You must configure Content Security Policy (CSP) in <code>config/middlewares.js</code> to allow Apple Pay scripts, otherwise Apple Pay will NOT work.
39
+ </Typography>
40
+ <Typography variant="pi" marginTop={2}>
41
+ Required CSP directives for Apple Pay:
42
+ </Typography>
43
+ <Box marginTop={2} padding={2} background="neutral100" borderRadius="4px">
44
+ <Typography variant="pi" style={{ fontFamily: "monospace", fontSize: "12px" }}>
45
+ 'script-src': ['https://applepay.cdn-apple.com', 'https://www.apple.com']<br/>
46
+ 'connect-src': ['https://applepay.cdn-apple.com', 'https://www.apple.com']<br/>
47
+ 'frame-src': ['https://applepay.cdn-apple.com']
48
+ </Typography>
49
+ </Box>
50
+ <Typography variant="pi" marginTop={2} fontWeight="bold">
51
+ ⚠️ Without this configuration, Apple Pay will NOT work!
52
+ </Typography>
53
+ </Alert>
54
+ <Alert closeLabel="Close" title="📥 Apple Pay Domain Verification File Required" variant="default">
55
+ <Typography variant="pi" marginTop={2}>
56
+ <strong>Download the Apple Pay domain verification file</strong> from your Payone merchant portal and place it in:
57
+ </Typography>
58
+ <Box marginTop={2} padding={2} background="neutral100" borderRadius="4px">
59
+ <Typography variant="pi" style={{ fontFamily: "monospace", fontSize: "12px" }}>
60
+ <strong>Strapi:</strong> <code>public/.well-known/apple-developer-merchantid-domain-association</code><br/>
61
+ <strong>Frontend:</strong> <code>public/.well-known/apple-developer-merchantid-domain-association</code>
62
+ </Typography>
63
+ </Box>
64
+ <Typography variant="pi" marginTop={2}>
65
+ <strong>Download URL:</strong> Download the domain verification file from Payone documentation:{" "}
66
+ <Link
67
+ href="https://docs.payone.com/payment-methods/apple-pay/apple-pay-without-dev"
68
+ target="_blank"
69
+ rel="noopener noreferrer"
70
+ style={{ color: "#0066ff", textDecoration: "underline" }}
71
+ >
72
+ https://docs.payone.com/payment-methods/apple-pay/apple-pay-without-dev
73
+ </Link>
74
+ </Typography>
75
+ <Typography variant="pi" marginTop={2}>
76
+ <strong>Alternative:</strong> Log into your Payone Merchant Interface (PMI) → Configuration → Payment Portals → Apple Pay → Download domain verification file
77
+ </Typography>
78
+ <Typography variant="pi" marginTop={2} fontWeight="bold" textColor="danger600">
79
+ ⚠️ Without this file, Apple Pay will NOT work on your domain!
80
+ </Typography>
81
+ </Alert>
82
+ <Box padding={3} background="neutral100" borderRadius="4px">
83
+ <Typography variant="pi" textColor="neutral600">
84
+ Configure Apple Pay settings:{" "}
85
+ <Link
86
+ href={`/plugins/${pluginId}/apple-pay-config`}
87
+ onClick={(e) => {
88
+ e.preventDefault();
89
+ onNavigateToConfig("apple-pay");
90
+ }}
91
+ style={{ cursor: "pointer", textDecoration: "underline", color: "#0066ff" }}
92
+ >
93
+ /plugins/{pluginId}/apple-pay-config
94
+ </Link>
95
+ </Typography>
96
+ </Box>
97
+ </>
98
+ )}
99
+ {paymentMethod === "gpp" && onNavigateToConfig && (
100
+ <>
101
+ <Alert closeLabel="Close" title="⚠️ Important: Middleware Configuration Required" variant="warning">
102
+ <Typography variant="pi" marginTop={2}>
103
+ <strong>Google Pay requires middleware configuration</strong> to work properly. You must configure Content Security Policy (CSP) in <code>config/middlewares.js</code> to allow Google Pay scripts, otherwise Google Pay will NOT work.
104
+ </Typography>
105
+ <Typography variant="pi" marginTop={2}>
106
+ Required CSP directives for Google Pay:
107
+ </Typography>
108
+ <Box marginTop={2} padding={2} background="neutral100" borderRadius="4px">
109
+ <Typography variant="pi" style={{ fontFamily: "monospace", fontSize: "12px" }}>
110
+ 'script-src': ['https://pay.google.com']<br/>
111
+ 'connect-src': ['https://pay.google.com']<br/>
112
+ 'frame-src': ['https://pay.google.com']
113
+ </Typography>
114
+ </Box>
115
+ <Typography variant="pi" marginTop={2} fontWeight="bold">
116
+ ⚠️ Without this configuration, Google Pay will NOT work!
117
+ </Typography>
118
+ </Alert>
119
+ <Box padding={3} background="neutral100" borderRadius="4px">
120
+ <Typography variant="pi" textColor="neutral600">
121
+ Configure Google Pay settings:{" "}
122
+ <Link
123
+ href={`/plugins/${pluginId}/google-pay-config`}
124
+ onClick={(e) => {
125
+ e.preventDefault();
126
+ onNavigateToConfig("google-pay");
127
+ }}
128
+ style={{ cursor: "pointer", textDecoration: "underline", color: "#0066ff" }}
129
+ >
130
+ /plugins/{pluginId}/google-pay-config
131
+ </Link>
132
+ </Typography>
133
+ </Box>
134
+ </>
135
+ )}
32
136
  {supportsCaptureMode(paymentMethod) && (
33
137
  <Select
34
138
  label="Capture Mode"