strapi-plugin-payone-provider 1.1.3 → 1.3.0
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.
- package/README.md +1156 -380
- package/admin/src/index.js +4 -1
- package/admin/src/pages/App/components/AppHeader.js +37 -0
- package/admin/src/pages/App/components/AppTabs.js +134 -0
- package/admin/src/pages/App/components/ConfigurationPanel.js +34 -35
- package/admin/src/pages/App/components/GooglePaybutton.js +300 -0
- package/admin/src/pages/App/components/HistoryPanel.js +25 -38
- package/admin/src/pages/App/components/PaymentActionsPanel.js +119 -280
- package/admin/src/pages/App/components/StatusBadge.js +3 -1
- package/admin/src/pages/App/components/TransactionHistoryItem.js +4 -1
- package/admin/src/pages/App/components/paymentActions/AuthorizationForm.js +122 -0
- package/admin/src/pages/App/components/paymentActions/CaptureForm.js +64 -0
- package/admin/src/pages/App/components/paymentActions/CardDetailsInput.js +189 -0
- package/admin/src/pages/App/components/paymentActions/PaymentMethodSelector.js +52 -0
- package/admin/src/pages/App/components/paymentActions/PaymentResult.js +148 -0
- package/admin/src/pages/App/components/paymentActions/PreauthorizationForm.js +122 -0
- package/admin/src/pages/App/components/paymentActions/RefundForm.js +89 -0
- package/admin/src/pages/App/index.js +41 -465
- package/admin/src/pages/App/styles.css +294 -0
- package/admin/src/pages/constants/paymentConstants.js +37 -0
- package/admin/src/pages/hooks/usePaymentActions.js +456 -0
- package/admin/src/pages/hooks/useSettings.js +111 -0
- package/admin/src/pages/hooks/useTransactionHistory.js +87 -0
- package/admin/src/pages/utils/api.js +10 -0
- package/admin/src/pages/utils/injectGooglePayScript.js +31 -0
- package/admin/src/pages/utils/paymentUtils.js +119 -15
- package/package.json +1 -1
- package/server/controllers/payone.js +71 -64
- package/server/routes/index.js +17 -0
- package/server/services/paymentService.js +271 -0
- package/server/services/payone.js +25 -648
- package/server/services/settingsService.js +59 -0
- package/server/services/testConnectionService.js +190 -0
- package/server/services/transactionService.js +114 -0
- package/server/utils/normalize.js +51 -0
- package/server/utils/paymentMethodParams.js +126 -0
- package/server/utils/requestBuilder.js +121 -0
- package/server/utils/responseParser.js +134 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Flex, Typography, TextInput, Button } from "@strapi/design-system";
|
|
3
|
+
import { Play } from "@strapi/icons";
|
|
4
|
+
import GooglePayButton from "../GooglePaybutton";
|
|
5
|
+
import CardDetailsInput from "./CardDetailsInput";
|
|
6
|
+
|
|
7
|
+
const AuthorizationForm = ({
|
|
8
|
+
paymentAmount,
|
|
9
|
+
setPaymentAmount,
|
|
10
|
+
authReference,
|
|
11
|
+
setAuthReference,
|
|
12
|
+
isProcessingPayment,
|
|
13
|
+
onAuthorization,
|
|
14
|
+
paymentMethod,
|
|
15
|
+
settings,
|
|
16
|
+
googlePayToken,
|
|
17
|
+
setGooglePayToken,
|
|
18
|
+
cardtype,
|
|
19
|
+
setCardtype,
|
|
20
|
+
cardpan,
|
|
21
|
+
setCardpan,
|
|
22
|
+
cardexpiredate,
|
|
23
|
+
setCardexpiredate,
|
|
24
|
+
cardcvc2,
|
|
25
|
+
setCardcvc2
|
|
26
|
+
}) => {
|
|
27
|
+
const handleGooglePayToken = (token, paymentData) => {
|
|
28
|
+
if (!token) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
setGooglePayToken(token);
|
|
32
|
+
onAuthorization(token);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleGooglePayError = (error) => {
|
|
36
|
+
if (onError) {
|
|
37
|
+
onError(error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return (
|
|
41
|
+
<Flex direction="column" alignItems="stretch" gap={4}>
|
|
42
|
+
<Flex direction="row" gap={2}>
|
|
43
|
+
<Typography variant="omega" fontWeight="semiBold" textColor="neutral800" className="payment-form-title">
|
|
44
|
+
Authorization
|
|
45
|
+
</Typography>
|
|
46
|
+
<Typography variant="pi" textColor="neutral600" className="payment-form-description">
|
|
47
|
+
Authorize and capture an amount immediately.
|
|
48
|
+
</Typography>
|
|
49
|
+
</Flex>
|
|
50
|
+
|
|
51
|
+
<Flex gap={4} wrap="wrap">
|
|
52
|
+
<TextInput
|
|
53
|
+
label="Amount (in cents) *"
|
|
54
|
+
name="authAmount"
|
|
55
|
+
value={paymentAmount}
|
|
56
|
+
onChange={(e) => setPaymentAmount(e.target.value)}
|
|
57
|
+
placeholder="Enter amount (e.g., 1000 for €10.00)"
|
|
58
|
+
hint="Amount in cents (e.g., 1000 = €10.00)"
|
|
59
|
+
required
|
|
60
|
+
className="payment-input"
|
|
61
|
+
style={{ flex: 1, minWidth: "250px" }}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
<TextInput
|
|
65
|
+
label="Reference *"
|
|
66
|
+
name="authReference"
|
|
67
|
+
value={authReference}
|
|
68
|
+
onChange={(e) => setAuthReference(e.target.value)}
|
|
69
|
+
placeholder="Auto-generated if empty"
|
|
70
|
+
hint="Reference will be auto-generated if left empty"
|
|
71
|
+
className="payment-input"
|
|
72
|
+
style={{ flex: 1, minWidth: "250px" }}
|
|
73
|
+
/>
|
|
74
|
+
</Flex>
|
|
75
|
+
|
|
76
|
+
{/* Show card details input if 3DS is enabled and payment method is credit card */}
|
|
77
|
+
{paymentMethod === "cc" && settings?.enable3DSecure !== false && (
|
|
78
|
+
<Box marginTop={4}>
|
|
79
|
+
<CardDetailsInput
|
|
80
|
+
cardtype={cardtype}
|
|
81
|
+
setCardtype={setCardtype}
|
|
82
|
+
cardpan={cardpan}
|
|
83
|
+
setCardpan={setCardpan}
|
|
84
|
+
cardexpiredate={cardexpiredate}
|
|
85
|
+
setCardexpiredate={setCardexpiredate}
|
|
86
|
+
cardcvc2={cardcvc2}
|
|
87
|
+
setCardcvc2={setCardcvc2}
|
|
88
|
+
/>
|
|
89
|
+
</Box>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{paymentMethod === "gpp" ? (
|
|
93
|
+
<GooglePayButton
|
|
94
|
+
amount={paymentAmount}
|
|
95
|
+
currency="EUR"
|
|
96
|
+
onTokenReceived={handleGooglePayToken}
|
|
97
|
+
onError={handleGooglePayError}
|
|
98
|
+
settings={settings}
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<Button
|
|
102
|
+
variant="default"
|
|
103
|
+
onClick={onAuthorization}
|
|
104
|
+
loading={isProcessingPayment}
|
|
105
|
+
startIcon={<Play />}
|
|
106
|
+
className="payment-button payment-button-primary"
|
|
107
|
+
disabled={
|
|
108
|
+
!paymentAmount.trim() ||
|
|
109
|
+
(paymentMethod === "cc" &&
|
|
110
|
+
settings?.enable3DSecure !== false &&
|
|
111
|
+
(!cardtype || !cardpan || !cardexpiredate || !cardcvc2))
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
Process Authorization
|
|
115
|
+
</Button>
|
|
116
|
+
)}
|
|
117
|
+
</Flex>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default AuthorizationForm;
|
|
122
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Flex, Typography, TextInput, Button } from "@strapi/design-system";
|
|
3
|
+
import { Play } from "@strapi/icons";
|
|
4
|
+
|
|
5
|
+
const CaptureForm = ({
|
|
6
|
+
paymentAmount,
|
|
7
|
+
setPaymentAmount,
|
|
8
|
+
captureTxid,
|
|
9
|
+
setCaptureTxid,
|
|
10
|
+
isProcessingPayment,
|
|
11
|
+
onCapture
|
|
12
|
+
}) => {
|
|
13
|
+
return (
|
|
14
|
+
<Flex direction="column" alignItems="stretch" gap={4}>
|
|
15
|
+
<Flex direction="row" gap={2}>
|
|
16
|
+
<Typography variant="omega" fontWeight="semiBold" textColor="neutral800" className="payment-form-title">
|
|
17
|
+
Capture
|
|
18
|
+
</Typography>
|
|
19
|
+
<Typography variant="pi" textColor="neutral600" className="payment-form-description">
|
|
20
|
+
Capture a previously authorized amount. Note: Reference parameter is
|
|
21
|
+
not supported by Payone capture.
|
|
22
|
+
</Typography>
|
|
23
|
+
</Flex>
|
|
24
|
+
|
|
25
|
+
<Flex gap={4} wrap="wrap">
|
|
26
|
+
<TextInput
|
|
27
|
+
label="Transaction ID"
|
|
28
|
+
name="captureTxid"
|
|
29
|
+
value={captureTxid}
|
|
30
|
+
onChange={(e) => setCaptureTxid(e.target.value)}
|
|
31
|
+
placeholder="Enter TxId from preauthorization"
|
|
32
|
+
hint="Transaction ID from a previous preauthorization"
|
|
33
|
+
className="payment-input"
|
|
34
|
+
style={{ flex: 1, minWidth: "250px" }}
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<TextInput
|
|
38
|
+
label="Amount (in cents)"
|
|
39
|
+
name="captureAmount"
|
|
40
|
+
value={paymentAmount}
|
|
41
|
+
onChange={(e) => setPaymentAmount(e.target.value)}
|
|
42
|
+
placeholder="1000"
|
|
43
|
+
hint="Amount in cents to capture"
|
|
44
|
+
className="payment-input"
|
|
45
|
+
style={{ flex: 1, minWidth: "250px" }}
|
|
46
|
+
/>
|
|
47
|
+
</Flex>
|
|
48
|
+
|
|
49
|
+
<Button
|
|
50
|
+
variant="default"
|
|
51
|
+
onClick={onCapture}
|
|
52
|
+
loading={isProcessingPayment}
|
|
53
|
+
startIcon={<Play />}
|
|
54
|
+
className="payment-button payment-button-primary"
|
|
55
|
+
disabled={!captureTxid.trim() || !paymentAmount.trim()}
|
|
56
|
+
>
|
|
57
|
+
Process Capture
|
|
58
|
+
</Button>
|
|
59
|
+
</Flex>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default CaptureForm;
|
|
64
|
+
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Flex,
|
|
5
|
+
Typography,
|
|
6
|
+
TextInput,
|
|
7
|
+
Select,
|
|
8
|
+
Option,
|
|
9
|
+
Link
|
|
10
|
+
} from "@strapi/design-system";
|
|
11
|
+
|
|
12
|
+
// 3DS Test Cards that require redirect (challenge workflow)
|
|
13
|
+
const TEST_3DS_CARDS = [
|
|
14
|
+
{
|
|
15
|
+
name: "VISA - 3DS 2.0 (Challenge)",
|
|
16
|
+
cardtype: "V",
|
|
17
|
+
cardpan: "4716971940353559",
|
|
18
|
+
cardexpiredate: "2512",
|
|
19
|
+
cardcvc2: "123",
|
|
20
|
+
description: "3DS 2.0 with challenge - Password: 12345"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "Mastercard - 3DS 2.0 (Challenge)",
|
|
24
|
+
cardtype: "M",
|
|
25
|
+
cardpan: "5404127720739582",
|
|
26
|
+
cardexpiredate: "2512",
|
|
27
|
+
cardcvc2: "123",
|
|
28
|
+
description: "3DS 2.0 with challenge - Password: 12345"
|
|
29
|
+
},
|
|
30
|
+
// {
|
|
31
|
+
// name: "AMEX - 3DS 2.0 (Challenge)",
|
|
32
|
+
// cardtype: "A",
|
|
33
|
+
// cardpan: "375144726036141",
|
|
34
|
+
// cardexpiredate: "2512",
|
|
35
|
+
// cardcvc2: "1234",
|
|
36
|
+
// description: "3DS 2.0 with challenge - Password: 12345"
|
|
37
|
+
// }
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const CardDetailsInput = ({
|
|
41
|
+
cardtype,
|
|
42
|
+
setCardtype,
|
|
43
|
+
cardpan,
|
|
44
|
+
setCardpan,
|
|
45
|
+
cardexpiredate,
|
|
46
|
+
setCardexpiredate,
|
|
47
|
+
cardcvc2,
|
|
48
|
+
setCardcvc2
|
|
49
|
+
}) => {
|
|
50
|
+
const [selectedTestCard, setSelectedTestCard] = React.useState("");
|
|
51
|
+
const isUpdatingFromTestCard = useRef(false);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (isUpdatingFromTestCard.current) {
|
|
55
|
+
isUpdatingFromTestCard.current = false;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const matchingCard = TEST_3DS_CARDS.find(
|
|
60
|
+
card => card.cardtype === cardtype && card.cardpan === cardpan
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (matchingCard) {
|
|
64
|
+
const testCardValue = `${matchingCard.cardtype}-${matchingCard.cardpan}`;
|
|
65
|
+
if (selectedTestCard !== testCardValue) {
|
|
66
|
+
setSelectedTestCard(testCardValue);
|
|
67
|
+
}
|
|
68
|
+
} else if (selectedTestCard) {
|
|
69
|
+
setSelectedTestCard("");
|
|
70
|
+
}
|
|
71
|
+
}, [cardtype, cardpan, selectedTestCard]);
|
|
72
|
+
|
|
73
|
+
const handleTestCardSelect = (value) => {
|
|
74
|
+
if (!value || value === "") {
|
|
75
|
+
setSelectedTestCard("");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const selectedCard = TEST_3DS_CARDS.find(card =>
|
|
80
|
+
`${card.cardtype}-${card.cardpan}` === value
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (selectedCard) {
|
|
84
|
+
isUpdatingFromTestCard.current = true;
|
|
85
|
+
|
|
86
|
+
setCardtype(selectedCard.cardtype);
|
|
87
|
+
setCardpan(selectedCard.cardpan);
|
|
88
|
+
setCardexpiredate(selectedCard.cardexpiredate);
|
|
89
|
+
setCardcvc2(selectedCard.cardcvc2);
|
|
90
|
+
setSelectedTestCard(value);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Box>
|
|
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>
|
|
113
|
+
|
|
114
|
+
<Flex gap={4} wrap="wrap" alignItems="flex-start">
|
|
115
|
+
<Select
|
|
116
|
+
label="Card Type *"
|
|
117
|
+
name="cardtype"
|
|
118
|
+
value={cardtype || ""}
|
|
119
|
+
onChange={(value) => setCardtype(value)}
|
|
120
|
+
required
|
|
121
|
+
hint="Select credit card type"
|
|
122
|
+
className="payment-input"
|
|
123
|
+
style={{ flex: 1, minWidth: "200px" }}
|
|
124
|
+
>
|
|
125
|
+
<Option value="V">VISA</Option>
|
|
126
|
+
<Option value="M">Mastercard</Option>
|
|
127
|
+
<Option value="A">American Express</Option>
|
|
128
|
+
<Option value="J">JCB</Option>
|
|
129
|
+
<Option value="O">Maestro International</Option>
|
|
130
|
+
<Option value="D">Diners Club</Option>
|
|
131
|
+
</Select>
|
|
132
|
+
|
|
133
|
+
<TextInput
|
|
134
|
+
label="Card Number (PAN) *"
|
|
135
|
+
name="cardpan"
|
|
136
|
+
value={cardpan || ""}
|
|
137
|
+
onChange={(e) => setCardpan(e.target.value)}
|
|
138
|
+
placeholder="Enter card number"
|
|
139
|
+
hint="Credit card number (PAN)"
|
|
140
|
+
required
|
|
141
|
+
className="payment-input"
|
|
142
|
+
style={{ flex: 2, minWidth: "300px" }}
|
|
143
|
+
/>
|
|
144
|
+
</Flex>
|
|
145
|
+
|
|
146
|
+
<Flex gap={4} wrap="wrap" alignItems="flex-start">
|
|
147
|
+
<TextInput
|
|
148
|
+
label="Expiry Date *"
|
|
149
|
+
name="cardexpiredate"
|
|
150
|
+
value={cardexpiredate || ""}
|
|
151
|
+
onChange={(e) => setCardexpiredate(e.target.value)}
|
|
152
|
+
placeholder="YYMM (e.g., 2512)"
|
|
153
|
+
hint="Format: YYMM (e.g., 2512 = December 2025)"
|
|
154
|
+
required
|
|
155
|
+
maxLength={4}
|
|
156
|
+
className="payment-input"
|
|
157
|
+
style={{ flex: 1, minWidth: "150px" }}
|
|
158
|
+
/>
|
|
159
|
+
|
|
160
|
+
<TextInput
|
|
161
|
+
label="CVC/CVV *"
|
|
162
|
+
name="cardcvc2"
|
|
163
|
+
value={cardcvc2 || ""}
|
|
164
|
+
onChange={(e) => setCardcvc2(e.target.value)}
|
|
165
|
+
placeholder="123 or 1234"
|
|
166
|
+
hint={cardtype === "A" ? "4 digits for AMEX" : "3 digits for other cards"}
|
|
167
|
+
required
|
|
168
|
+
maxLength={4}
|
|
169
|
+
className="payment-input"
|
|
170
|
+
style={{ flex: 1, minWidth: "150px" }}
|
|
171
|
+
/>
|
|
172
|
+
</Flex>
|
|
173
|
+
|
|
174
|
+
<Box paddingTop={2}>
|
|
175
|
+
<Typography variant="pi" textColor="neutral600" style={{ textAlign: "left" }}>
|
|
176
|
+
For all test card numbers (positive, negative, frictionless 3DS), 3D Secure test data, and detailed documentation, please refer to the{" "}
|
|
177
|
+
<Link href="https://docs.payone.com/security-risk-management/3d-secure#/" target="_blank" rel="noopener noreferrer">
|
|
178
|
+
Payone 3D Secure Documentation
|
|
179
|
+
</Link>
|
|
180
|
+
.
|
|
181
|
+
</Typography>
|
|
182
|
+
</Box>
|
|
183
|
+
</Flex>
|
|
184
|
+
</Box>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export default CardDetailsInput;
|
|
189
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Flex, Select, Option } from "@strapi/design-system";
|
|
3
|
+
import {
|
|
4
|
+
getPaymentMethodOptions,
|
|
5
|
+
supportsCaptureMode,
|
|
6
|
+
getCaptureModeOptions,
|
|
7
|
+
getPaymentMethodDisplayName
|
|
8
|
+
} from "../../../utils/paymentUtils";
|
|
9
|
+
|
|
10
|
+
const PaymentMethodSelector = ({
|
|
11
|
+
paymentMethod,
|
|
12
|
+
setPaymentMethod,
|
|
13
|
+
captureMode,
|
|
14
|
+
setCaptureMode
|
|
15
|
+
}) => {
|
|
16
|
+
return (
|
|
17
|
+
<Box>
|
|
18
|
+
<Flex direction="column" alignItems="stretch" gap={4}>
|
|
19
|
+
<Select
|
|
20
|
+
label="Select Payment Method"
|
|
21
|
+
name="paymentMethod"
|
|
22
|
+
value={paymentMethod}
|
|
23
|
+
onChange={(value) => setPaymentMethod(value)}
|
|
24
|
+
hint={`Current: ${getPaymentMethodDisplayName(paymentMethod)}`}
|
|
25
|
+
>
|
|
26
|
+
{getPaymentMethodOptions().map((option) => (
|
|
27
|
+
<Option key={option.value} value={option.value}>
|
|
28
|
+
{option.label}
|
|
29
|
+
</Option>
|
|
30
|
+
))}
|
|
31
|
+
</Select>
|
|
32
|
+
{supportsCaptureMode(paymentMethod) && (
|
|
33
|
+
<Select
|
|
34
|
+
label="Capture Mode"
|
|
35
|
+
name="captureMode"
|
|
36
|
+
value={captureMode}
|
|
37
|
+
onChange={(value) => setCaptureMode(value)}
|
|
38
|
+
hint="Select capture mode for wallet payments"
|
|
39
|
+
>
|
|
40
|
+
{getCaptureModeOptions().map((option) => (
|
|
41
|
+
<Option key={option.value} value={option.value}>
|
|
42
|
+
{option.label}
|
|
43
|
+
</Option>
|
|
44
|
+
))}
|
|
45
|
+
</Select>
|
|
46
|
+
)}
|
|
47
|
+
</Flex>
|
|
48
|
+
</Box>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default PaymentMethodSelector;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Card,
|
|
5
|
+
CardBody,
|
|
6
|
+
Divider,
|
|
7
|
+
Flex,
|
|
8
|
+
Stack,
|
|
9
|
+
Typography,
|
|
10
|
+
Alert
|
|
11
|
+
} from "@strapi/design-system";
|
|
12
|
+
import StatusBadge from "../StatusBadge";
|
|
13
|
+
import { formatTransactionData } from "../../../utils/formatTransactionData";
|
|
14
|
+
|
|
15
|
+
const PaymentResult = ({ paymentError, paymentResult }) => {
|
|
16
|
+
if (!paymentError && !paymentResult) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const status = paymentResult?.status || paymentResult?.Status || "";
|
|
21
|
+
const errorCode = paymentResult?.errorcode || paymentResult?.errorCode || paymentResult?.ErrorCode;
|
|
22
|
+
const errorMessage = paymentResult?.errormessage || paymentResult?.errorMessage || paymentResult?.ErrorMessage;
|
|
23
|
+
const customerMessage = paymentResult?.customermessage || paymentResult?.customerMessage || paymentResult?.CustomerMessage;
|
|
24
|
+
const isError = status === "ERROR" || status === "INVALID" || errorCode;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
{paymentError && (
|
|
29
|
+
<Alert
|
|
30
|
+
variant="danger"
|
|
31
|
+
title="Error"
|
|
32
|
+
className="payment-alert"
|
|
33
|
+
>
|
|
34
|
+
{paymentError}
|
|
35
|
+
</Alert>
|
|
36
|
+
)}
|
|
37
|
+
|
|
38
|
+
{paymentResult && (
|
|
39
|
+
<Card className="payment-result-card">
|
|
40
|
+
<CardBody>
|
|
41
|
+
<Stack spacing={4}>
|
|
42
|
+
<Flex justifyContent="space-between" alignItems="center">
|
|
43
|
+
<Typography variant="delta" as="h3" className="payment-section-title">
|
|
44
|
+
Payment Result
|
|
45
|
+
</Typography>
|
|
46
|
+
{(status || paymentResult.Status) && (
|
|
47
|
+
<StatusBadge status={status || paymentResult.Status} />
|
|
48
|
+
)}
|
|
49
|
+
</Flex>
|
|
50
|
+
|
|
51
|
+
<hr className="payment-divider" style={{ margin: '16px 0' }} />
|
|
52
|
+
|
|
53
|
+
{/* Show error information prominently if error */}
|
|
54
|
+
{isError && (
|
|
55
|
+
<Alert variant="danger" title="Transaction Failed">
|
|
56
|
+
<Stack spacing={2}>
|
|
57
|
+
{errorCode && (
|
|
58
|
+
<Typography variant="pi">
|
|
59
|
+
<strong>Error Code:</strong> {errorCode}
|
|
60
|
+
</Typography>
|
|
61
|
+
)}
|
|
62
|
+
{errorMessage && (
|
|
63
|
+
<Typography variant="pi">
|
|
64
|
+
<strong>Error Message:</strong> {errorMessage}
|
|
65
|
+
</Typography>
|
|
66
|
+
)}
|
|
67
|
+
{customerMessage && (
|
|
68
|
+
<Typography variant="pi">
|
|
69
|
+
<strong>Customer Message:</strong> {customerMessage}
|
|
70
|
+
</Typography>
|
|
71
|
+
)}
|
|
72
|
+
</Stack>
|
|
73
|
+
</Alert>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<Box>
|
|
77
|
+
<Typography variant="omega" fontWeight="semiBold" marginBottom={2}>
|
|
78
|
+
Full Response Details:
|
|
79
|
+
</Typography>
|
|
80
|
+
<Stack spacing={3}>
|
|
81
|
+
{formatTransactionData(paymentResult).map((item, index) => (
|
|
82
|
+
<Flex
|
|
83
|
+
key={index}
|
|
84
|
+
justifyContent="space-between"
|
|
85
|
+
alignItems="start"
|
|
86
|
+
style={{
|
|
87
|
+
padding: '8px 0',
|
|
88
|
+
borderBottom: index < formatTransactionData(paymentResult).length - 1 ? '1px solid #e8e8ea' : 'none'
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<Typography
|
|
92
|
+
variant="pi"
|
|
93
|
+
textColor="neutral600"
|
|
94
|
+
style={{ minWidth: "200px", fontWeight: '500' }}
|
|
95
|
+
>
|
|
96
|
+
{item.key}:
|
|
97
|
+
</Typography>
|
|
98
|
+
<Typography
|
|
99
|
+
variant="pi"
|
|
100
|
+
style={{
|
|
101
|
+
flex: 1,
|
|
102
|
+
textAlign: "right",
|
|
103
|
+
fontWeight: '400',
|
|
104
|
+
wordBreak: 'break-word',
|
|
105
|
+
fontFamily: item.key.toLowerCase().includes('raw') ? 'monospace' : 'inherit',
|
|
106
|
+
fontSize: item.key.toLowerCase().includes('raw') ? '11px' : 'inherit'
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{item.value}
|
|
110
|
+
</Typography>
|
|
111
|
+
</Flex>
|
|
112
|
+
))}
|
|
113
|
+
</Stack>
|
|
114
|
+
</Box>
|
|
115
|
+
|
|
116
|
+
{/* 3DS Required Warning */}
|
|
117
|
+
{paymentResult?.is3DSRequired && !paymentResult?.redirectUrl && (
|
|
118
|
+
<Alert variant="warning" title="3D Secure Authentication Required">
|
|
119
|
+
<Stack spacing={2}>
|
|
120
|
+
<Typography variant="pi">
|
|
121
|
+
Payone requires 3D Secure authentication, but no redirect URL was provided in the response.
|
|
122
|
+
</Typography>
|
|
123
|
+
<Typography variant="pi" fontWeight="semiBold">
|
|
124
|
+
Possible solutions:
|
|
125
|
+
</Typography>
|
|
126
|
+
<Typography variant="pi" component="ul" style={{ marginLeft: '20px' }}>
|
|
127
|
+
<li>Check Payone portal configuration for 3DS settings</li>
|
|
128
|
+
<li>Verify that redirect URLs (successurl, errorurl, backurl) are properly configured</li>
|
|
129
|
+
<li>Ensure you're using test mode with proper test credentials</li>
|
|
130
|
+
<li>Check if 3dscheck request is needed before authorization</li>
|
|
131
|
+
</Typography>
|
|
132
|
+
<Typography variant="pi" textColor="neutral600" marginTop={2}>
|
|
133
|
+
<strong>Error Code:</strong> {paymentResult?.errorCode || paymentResult?.ErrorCode || "4219"}
|
|
134
|
+
</Typography>
|
|
135
|
+
</Stack>
|
|
136
|
+
</Alert>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
</Stack>
|
|
140
|
+
</CardBody>
|
|
141
|
+
</Card>
|
|
142
|
+
)}
|
|
143
|
+
</>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default PaymentResult;
|
|
148
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Flex, Typography, TextInput, Button } from "@strapi/design-system";
|
|
3
|
+
import { Play } from "@strapi/icons";
|
|
4
|
+
import GooglePayButton from "../GooglePaybutton";
|
|
5
|
+
import CardDetailsInput from "./CardDetailsInput";
|
|
6
|
+
|
|
7
|
+
const PreauthorizationForm = ({
|
|
8
|
+
paymentAmount,
|
|
9
|
+
setPaymentAmount,
|
|
10
|
+
preauthReference,
|
|
11
|
+
setPreauthReference,
|
|
12
|
+
isProcessingPayment,
|
|
13
|
+
onPreauthorization,
|
|
14
|
+
paymentMethod,
|
|
15
|
+
settings,
|
|
16
|
+
googlePayToken,
|
|
17
|
+
setGooglePayToken,
|
|
18
|
+
cardtype,
|
|
19
|
+
setCardtype,
|
|
20
|
+
cardpan,
|
|
21
|
+
setCardpan,
|
|
22
|
+
cardexpiredate,
|
|
23
|
+
setCardexpiredate,
|
|
24
|
+
cardcvc2,
|
|
25
|
+
setCardcvc2
|
|
26
|
+
}) => {
|
|
27
|
+
const handleGooglePayToken = (token, paymentData) => {
|
|
28
|
+
if (!token) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
setGooglePayToken(token);
|
|
32
|
+
onPreauthorization(token);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleGooglePayError = (error) => {
|
|
36
|
+
if (onError) {
|
|
37
|
+
onError(error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return (
|
|
41
|
+
<Flex direction="column" alignItems="stretch" gap={4}>
|
|
42
|
+
<Flex direction="row" gap={2}>
|
|
43
|
+
<Typography variant="omega" fontWeight="semiBold" textColor="neutral800" className="payment-form-title">
|
|
44
|
+
Preauthorization
|
|
45
|
+
</Typography>
|
|
46
|
+
<Typography variant="pi" textColor="neutral600" className="payment-form-description">
|
|
47
|
+
Reserve an amount on a credit card without capturing it immediately.
|
|
48
|
+
</Typography>
|
|
49
|
+
</Flex>
|
|
50
|
+
|
|
51
|
+
<Flex gap={4} wrap="wrap">
|
|
52
|
+
<TextInput
|
|
53
|
+
label="Amount (in cents) *"
|
|
54
|
+
name="paymentAmount"
|
|
55
|
+
value={paymentAmount}
|
|
56
|
+
onChange={(e) => setPaymentAmount(e.target.value)}
|
|
57
|
+
placeholder="Enter amount (e.g., 1000 for €10.00)"
|
|
58
|
+
hint="Amount in cents (e.g., 1000 = €10.00)"
|
|
59
|
+
required
|
|
60
|
+
className="payment-input"
|
|
61
|
+
style={{ flex: 1, minWidth: "250px" }}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
<TextInput
|
|
65
|
+
label="Reference *"
|
|
66
|
+
name="preauthReference"
|
|
67
|
+
value={preauthReference}
|
|
68
|
+
onChange={(e) => setPreauthReference(e.target.value)}
|
|
69
|
+
placeholder="Auto-generated if empty"
|
|
70
|
+
hint="Reference will be auto-generated if left empty"
|
|
71
|
+
className="payment-input"
|
|
72
|
+
style={{ flex: 1, minWidth: "250px" }}
|
|
73
|
+
/>
|
|
74
|
+
</Flex>
|
|
75
|
+
|
|
76
|
+
{/* Show card details input if 3DS is enabled and payment method is credit card */}
|
|
77
|
+
{paymentMethod === "cc" && settings?.enable3DSecure !== false && (
|
|
78
|
+
<Box marginTop={4}>
|
|
79
|
+
<CardDetailsInput
|
|
80
|
+
cardtype={cardtype}
|
|
81
|
+
setCardtype={setCardtype}
|
|
82
|
+
cardpan={cardpan}
|
|
83
|
+
setCardpan={setCardpan}
|
|
84
|
+
cardexpiredate={cardexpiredate}
|
|
85
|
+
setCardexpiredate={setCardexpiredate}
|
|
86
|
+
cardcvc2={cardcvc2}
|
|
87
|
+
setCardcvc2={setCardcvc2}
|
|
88
|
+
/>
|
|
89
|
+
</Box>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{paymentMethod === "gpp" ? (
|
|
93
|
+
<GooglePayButton
|
|
94
|
+
amount={paymentAmount}
|
|
95
|
+
currency="EUR"
|
|
96
|
+
onTokenReceived={handleGooglePayToken}
|
|
97
|
+
onError={handleGooglePayError}
|
|
98
|
+
settings={settings}
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<Button
|
|
102
|
+
variant="default"
|
|
103
|
+
onClick={onPreauthorization}
|
|
104
|
+
loading={isProcessingPayment}
|
|
105
|
+
startIcon={<Play />}
|
|
106
|
+
className="payment-button payment-button-primary"
|
|
107
|
+
disabled={
|
|
108
|
+
!paymentAmount.trim() ||
|
|
109
|
+
(paymentMethod === "cc" &&
|
|
110
|
+
settings?.enable3DSecure !== false &&
|
|
111
|
+
(!cardtype || !cardpan || !cardexpiredate || !cardcvc2))
|
|
112
|
+
}
|
|
113
|
+
>
|
|
114
|
+
Process Preauthorization
|
|
115
|
+
</Button>
|
|
116
|
+
)}
|
|
117
|
+
</Flex>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default PreauthorizationForm;
|
|
122
|
+
|