strapi-plugin-payone-provider 1.4.2 → 1.5.2

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.
Files changed (32) hide show
  1. package/README.md +179 -22
  2. package/admin/src/pages/App/components/AppHeader.js +22 -4
  3. package/admin/src/pages/App/components/AppTabs.js +25 -1
  4. package/admin/src/pages/App/components/ApplePayButton.js +737 -0
  5. package/admin/src/pages/App/components/ApplePayConfig.js +364 -0
  6. package/admin/src/pages/App/components/ApplePayConfigPanel.js +81 -0
  7. package/admin/src/pages/App/components/ConfigurationPanel.js +19 -3
  8. package/admin/src/pages/App/components/DocsPanel.js +1057 -0
  9. package/admin/src/pages/App/components/GooglePayConfig.js +217 -0
  10. package/admin/src/pages/App/components/GooglePayConfigPanel.js +82 -0
  11. package/admin/src/pages/App/components/GooglePaybutton.js +1 -1
  12. package/admin/src/pages/App/components/PaymentActionsPanel.js +24 -6
  13. package/admin/src/pages/App/components/paymentActions/AuthorizationForm.js +60 -4
  14. package/admin/src/pages/App/components/paymentActions/CaptureForm.js +1 -0
  15. package/admin/src/pages/App/components/paymentActions/CardDetailsInput.js +18 -16
  16. package/admin/src/pages/App/components/paymentActions/PaymentMethodSelector.js +106 -2
  17. package/admin/src/pages/App/components/paymentActions/PreauthorizationForm.js +64 -4
  18. package/admin/src/pages/App/components/paymentActions/RefundForm.js +1 -0
  19. package/admin/src/pages/App/index.js +70 -1
  20. package/admin/src/pages/hooks/usePaymentActions.js +13 -2
  21. package/admin/src/pages/hooks/useSettings.js +2 -0
  22. package/admin/src/pages/utils/applePayConstants.js +222 -0
  23. package/admin/src/pages/utils/googlePayConstants.js +79 -0
  24. package/admin/src/pages/utils/paymentUtils.js +22 -74
  25. package/package.json +1 -1
  26. package/server/bootstrap.js +5 -1
  27. package/server/config/index.js +5 -1
  28. package/server/controllers/payone.js +10 -0
  29. package/server/routes/index.js +17 -0
  30. package/server/services/applePayService.js +261 -0
  31. package/server/services/payone.js +10 -0
  32. package/server/utils/paymentMethodParams.js +19 -2
@@ -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
  );
@@ -36,6 +36,8 @@ const PaymentActionsPanel = ({
36
36
  settings,
37
37
  googlePayToken,
38
38
  setGooglePayToken,
39
+ applePayToken,
40
+ setApplePayToken,
39
41
  cardtype,
40
42
  setCardtype,
41
43
  cardpan,
@@ -43,8 +45,12 @@ const PaymentActionsPanel = ({
43
45
  cardexpiredate,
44
46
  setCardexpiredate,
45
47
  cardcvc2,
46
- setCardcvc2
48
+ setCardcvc2,
49
+ onNavigateToConfig
47
50
  }) => {
51
+ const mode = (settings?.mode || 'test').toLowerCase();
52
+ const isLiveMode = mode === 'live';
53
+
48
54
  return (
49
55
  <Box
50
56
  className="payment-container"
@@ -54,13 +60,18 @@ const PaymentActionsPanel = ({
54
60
  paddingRight={8}
55
61
  >
56
62
  <Flex direction="column" alignItems="stretch" gap={6}>
57
- <Box>
63
+ <Box style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: '8px' }}>
58
64
  <Typography variant="beta" as="h2" className="payment-title" style={{ fontSize: '20px', marginBottom: '4px' }}>
59
65
  Payment Actions
60
66
  </Typography>
61
67
  <Typography variant="pi" textColor="neutral600" className="payment-subtitle" style={{ fontSize: '14px' }}>
62
68
  Process payments, captures, and refunds with multiple payment methods
63
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
+ )}
64
75
  </Box>
65
76
 
66
77
  <PaymentMethodSelector
@@ -68,11 +79,12 @@ const PaymentActionsPanel = ({
68
79
  setPaymentMethod={setPaymentMethod}
69
80
  captureMode={captureMode}
70
81
  setCaptureMode={setCaptureMode}
82
+ onNavigateToConfig={onNavigateToConfig}
71
83
  />
72
84
 
73
85
  <hr className="payment-divider" />
74
86
 
75
- <Box className="payment-form-section">
87
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
76
88
  <PreauthorizationForm
77
89
  paymentAmount={paymentAmount}
78
90
  setPaymentAmount={setPaymentAmount}
@@ -84,6 +96,8 @@ const PaymentActionsPanel = ({
84
96
  settings={settings}
85
97
  googlePayToken={googlePayToken}
86
98
  setGooglePayToken={setGooglePayToken}
99
+ applePayToken={applePayToken}
100
+ setApplePayToken={setApplePayToken}
87
101
  cardtype={cardtype}
88
102
  setCardtype={setCardtype}
89
103
  cardpan={cardpan}
@@ -92,12 +106,13 @@ const PaymentActionsPanel = ({
92
106
  setCardexpiredate={setCardexpiredate}
93
107
  cardcvc2={cardcvc2}
94
108
  setCardcvc2={setCardcvc2}
109
+ isLiveMode={isLiveMode}
95
110
  />
96
111
  </Box>
97
112
 
98
113
  <hr className="payment-divider" />
99
114
 
100
- <Box className="payment-form-section">
115
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
101
116
  <AuthorizationForm
102
117
  paymentAmount={paymentAmount}
103
118
  setPaymentAmount={setPaymentAmount}
@@ -109,6 +124,8 @@ const PaymentActionsPanel = ({
109
124
  settings={settings}
110
125
  googlePayToken={googlePayToken}
111
126
  setGooglePayToken={setGooglePayToken}
127
+ applePayToken={applePayToken}
128
+ setApplePayToken={setApplePayToken}
112
129
  cardtype={cardtype}
113
130
  setCardtype={setCardtype}
114
131
  cardpan={cardpan}
@@ -117,12 +134,13 @@ const PaymentActionsPanel = ({
117
134
  setCardexpiredate={setCardexpiredate}
118
135
  cardcvc2={cardcvc2}
119
136
  setCardcvc2={setCardcvc2}
137
+ isLiveMode={isLiveMode}
120
138
  />
121
139
  </Box>
122
140
 
123
141
  <hr className="payment-divider" />
124
142
 
125
- <Box className="payment-form-section">
143
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
126
144
  <CaptureForm
127
145
  paymentAmount={paymentAmount}
128
146
  setPaymentAmount={setPaymentAmount}
@@ -135,7 +153,7 @@ const PaymentActionsPanel = ({
135
153
 
136
154
  <hr className="payment-divider" />
137
155
 
138
- <Box className="payment-form-section">
156
+ <Box className="payment-form-section" style={{ opacity: isLiveMode ? 0.5 : 1, pointerEvents: isLiveMode ? 'none' : 'auto' }}>
139
157
  <RefundForm
140
158
  paymentAmount={paymentAmount}
141
159
  setPaymentAmount={setPaymentAmount}
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { Box, Flex, Typography, TextInput, Button } from "@strapi/design-system";
3
3
  import { Play } from "@strapi/icons";
4
4
  import GooglePayButton from "../GooglePaybutton";
5
+ import ApplePayButton from "../ApplePayButton";
5
6
  import CardDetailsInput from "./CardDetailsInput";
6
7
 
7
8
  const AuthorizationForm = ({
@@ -15,6 +16,8 @@ const AuthorizationForm = ({
15
16
  settings,
16
17
  googlePayToken,
17
18
  setGooglePayToken,
19
+ applePayToken,
20
+ setApplePayToken,
18
21
  cardtype,
19
22
  setCardtype,
20
23
  cardpan,
@@ -22,7 +25,8 @@ const AuthorizationForm = ({
22
25
  cardexpiredate,
23
26
  setCardexpiredate,
24
27
  cardcvc2,
25
- setCardcvc2
28
+ setCardcvc2,
29
+ isLiveMode = false
26
30
  }) => {
27
31
  const handleGooglePayToken = (token, paymentData) => {
28
32
  if (!token) {
@@ -37,6 +41,28 @@ const AuthorizationForm = ({
37
41
  onError(error);
38
42
  }
39
43
  };
44
+
45
+ const handleApplePayToken = async (token, paymentData) => {
46
+ if (!token) {
47
+ return Promise.reject(new Error("Token is missing"));
48
+ }
49
+ setApplePayToken(token);
50
+ try {
51
+ await onAuthorization(token);
52
+ return Promise.resolve();
53
+ } catch (error) {
54
+ console.error("[Apple Pay] Error in authorization:", error);
55
+ return Promise.reject(error);
56
+ }
57
+ };
58
+
59
+ const handleApplePayError = (error) => {
60
+ if (onError) {
61
+ onError(error);
62
+ }
63
+ };
64
+
65
+
40
66
  return (
41
67
  <Flex direction="column" alignItems="stretch" gap={4}>
42
68
  <Flex direction="row" gap={2}>
@@ -73,8 +99,7 @@ const AuthorizationForm = ({
73
99
  />
74
100
  </Flex>
75
101
 
76
- {/* Show card details input if 3DS is enabled and payment method is credit card */}
77
- {paymentMethod === "cc" && settings?.enable3DSecure !== false && (
102
+ {paymentMethod === "cc" && settings?.enable3DSecure && (
78
103
  <Box marginTop={4}>
79
104
  <CardDetailsInput
80
105
  cardtype={cardtype}
@@ -97,18 +122,49 @@ const AuthorizationForm = ({
97
122
  onError={handleGooglePayError}
98
123
  settings={settings}
99
124
  />
125
+ ) : paymentMethod === "apl" ? (
126
+ <Box>
127
+ <ApplePayButton
128
+ amount={paymentAmount}
129
+ currency="EUR"
130
+ onTokenReceived={handleApplePayToken}
131
+ onError={handleApplePayError}
132
+ settings={settings}
133
+ />
134
+ {applePayToken && (
135
+ <Box marginTop={3} style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "8px" }}>
136
+ <Typography variant="pi" textColor="success600" style={{ marginBottom: "8px", fontWeight: "bold" }}>
137
+ ✓ Apple Pay token received. You can now process the authorization:
138
+ </Typography>
139
+ <Button
140
+ variant="default"
141
+ onClick={() => onAuthorization(applePayToken)}
142
+ loading={isProcessingPayment}
143
+ startIcon={<Play />}
144
+ style={{ maxWidth: '200px' }}
145
+ disabled={!paymentAmount.trim() || !authReference.trim() || isLiveMode}
146
+ className="payment-button payment-button-primary"
147
+ >
148
+ Process Authorization
149
+ </Button>
150
+ </Box>
151
+ )}
152
+ </Box>
100
153
  ) : (
101
154
  <Button
102
155
  variant="default"
103
156
  onClick={onAuthorization}
104
157
  loading={isProcessingPayment}
105
158
  startIcon={<Play />}
159
+ style={{ maxWidth: '200px' }}
106
160
  className="payment-button payment-button-primary"
107
161
  disabled={
108
162
  !paymentAmount.trim() ||
109
163
  (paymentMethod === "cc" &&
110
164
  settings?.enable3DSecure !== false &&
111
- (!cardtype || !cardpan || !cardexpiredate || !cardcvc2))
165
+ (!cardtype || !cardpan || !cardexpiredate || !cardcvc2)) ||
166
+ (paymentMethod === "apl" && !applePayToken) ||
167
+ isLiveMode
112
168
  }
113
169
  >
114
170
  Process Authorization
@@ -51,6 +51,7 @@ const CaptureForm = ({
51
51
  onClick={onCapture}
52
52
  loading={isProcessingPayment}
53
53
  startIcon={<Play />}
54
+ style={{ maxWidth: '200px' }}
54
55
  className="payment-button payment-button-primary"
55
56
  disabled={!captureTxid.trim() || !paymentAmount.trim()}
56
57
  >
@@ -94,22 +94,24 @@ const CardDetailsInput = ({
94
94
  return (
95
95
  <Box>
96
96
  <Flex direction="column" alignItems="stretch" gap={4}>
97
- <Select
98
- label="3D Secure Test Cards (Requires Redirect)"
99
- name="testCard"
100
- value={selectedTestCard}
101
- placeholder="Select a 3DS test card to auto-fill"
102
- hint="These cards will trigger 3DS authentication redirect. Password: 12345"
103
- onChange={handleTestCardSelect}
104
- className="payment-input"
105
- >
106
- <Option value="">-- Select a test card --</Option>
107
- {TEST_3DS_CARDS.map((card, index) => (
108
- <Option key={index} value={`${card.cardtype}-${card.cardpan}`}>
109
- {card.name} - {card.description}
110
- </Option>
111
- ))}
112
- </Select>
97
+ <Flex direction="row" gap={2} alignItems="flex-start">
98
+ <Select
99
+ label="3D Secure Test Cards"
100
+ name="testCard"
101
+ value={selectedTestCard}
102
+ placeholder="Select a 3DS test card to auto-fill"
103
+ hint="These cards will trigger 3DS authentication redirect. Password: 12345"
104
+ onChange={handleTestCardSelect}
105
+ className="payment-input"
106
+ >
107
+ <Option value="">-- Select a test card --</Option>
108
+ {TEST_3DS_CARDS.map((card, index) => (
109
+ <Option key={index} value={`${card.cardtype}-${card.cardpan}`}>
110
+ {card.name} - {card.description}
111
+ </Option>
112
+ ))}
113
+ </Select>
114
+ </Flex>
113
115
 
114
116
  <Flex gap={4} wrap="wrap" alignItems="flex-start">
115
117
  <Select