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
package/admin/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import pluginPkg from "../../package.json";
|
|
|
2
2
|
import pluginId from "./pluginId";
|
|
3
3
|
import Initializer from "./components/Initializer";
|
|
4
4
|
import PluginIcon from "./components/PluginIcon";
|
|
5
|
+
import { injectGooglePayScript } from "./pages/utils/injectGooglePayScript";
|
|
5
6
|
|
|
6
7
|
const name = pluginPkg.strapi.name;
|
|
7
8
|
|
|
@@ -29,7 +30,9 @@ export default {
|
|
|
29
30
|
});
|
|
30
31
|
},
|
|
31
32
|
|
|
32
|
-
bootstrap(app) {
|
|
33
|
+
bootstrap(app) {
|
|
34
|
+
injectGooglePayScript();
|
|
35
|
+
},
|
|
33
36
|
|
|
34
37
|
async registerTrads() {
|
|
35
38
|
return Promise.resolve([]);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { HeaderLayout, Box, Typography, Button } from "@strapi/design-system";
|
|
3
|
+
import { Check } from "@strapi/icons";
|
|
4
|
+
|
|
5
|
+
const AppHeader = ({ activeTab, isSaving, onSave }) => {
|
|
6
|
+
return (
|
|
7
|
+
<HeaderLayout
|
|
8
|
+
title={
|
|
9
|
+
<Box>
|
|
10
|
+
<Typography variant="alpha" as="h1" fontWeight="bold" className="payment-title">
|
|
11
|
+
Payone Provider
|
|
12
|
+
</Typography>
|
|
13
|
+
<Typography variant="pi" marginTop={2} className="payment-subtitle">
|
|
14
|
+
Configure your Payone integration and manage payment transactions
|
|
15
|
+
</Typography>
|
|
16
|
+
</Box>
|
|
17
|
+
}
|
|
18
|
+
primaryAction={
|
|
19
|
+
activeTab === 0 ? (
|
|
20
|
+
<Button
|
|
21
|
+
loading={isSaving}
|
|
22
|
+
onClick={onSave}
|
|
23
|
+
startIcon={<Check />}
|
|
24
|
+
size="L"
|
|
25
|
+
variant="default"
|
|
26
|
+
className="payment-button payment-button-success"
|
|
27
|
+
>
|
|
28
|
+
Save Configuration
|
|
29
|
+
</Button>
|
|
30
|
+
) : null
|
|
31
|
+
}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default AppHeader;
|
|
37
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Tabs, Tab, TabGroup, TabPanels, TabPanel } from "@strapi/design-system";
|
|
3
|
+
import ConfigurationPanel from "./ConfigurationPanel";
|
|
4
|
+
import HistoryPanel from "./HistoryPanel";
|
|
5
|
+
import PaymentActionsPanel from "./PaymentActionsPanel";
|
|
6
|
+
|
|
7
|
+
const AppTabs = ({
|
|
8
|
+
activeTab,
|
|
9
|
+
setActiveTab,
|
|
10
|
+
// Settings props
|
|
11
|
+
settings,
|
|
12
|
+
isSaving,
|
|
13
|
+
isTesting,
|
|
14
|
+
testResult,
|
|
15
|
+
onSave,
|
|
16
|
+
onTestConnection,
|
|
17
|
+
onInputChange,
|
|
18
|
+
// Transaction history props
|
|
19
|
+
filters,
|
|
20
|
+
onFilterChange,
|
|
21
|
+
onFilterApply,
|
|
22
|
+
isLoadingHistory,
|
|
23
|
+
transactionHistory,
|
|
24
|
+
paginatedTransactions,
|
|
25
|
+
currentPage,
|
|
26
|
+
totalPages,
|
|
27
|
+
pageSize,
|
|
28
|
+
onRefresh,
|
|
29
|
+
onPageChange,
|
|
30
|
+
selectedTransaction,
|
|
31
|
+
onTransactionSelect,
|
|
32
|
+
// Payment actions props
|
|
33
|
+
paymentActions
|
|
34
|
+
}) => {
|
|
35
|
+
return (
|
|
36
|
+
<TabGroup
|
|
37
|
+
label="Payone Provider Tabs"
|
|
38
|
+
onTabChange={(index) => setActiveTab(index)}
|
|
39
|
+
>
|
|
40
|
+
<Tabs style={{ borderBottom: "2px solid #e8e8ea" }}>
|
|
41
|
+
<Tab
|
|
42
|
+
className={`payment-tab ${activeTab === 0 ? 'payment-tab-active' : ''}`}
|
|
43
|
+
>
|
|
44
|
+
Configuration
|
|
45
|
+
</Tab>
|
|
46
|
+
<Tab
|
|
47
|
+
className={`payment-tab ${activeTab === 1 ? 'payment-tab-active' : ''}`}
|
|
48
|
+
>
|
|
49
|
+
Transaction History
|
|
50
|
+
</Tab>
|
|
51
|
+
<Tab
|
|
52
|
+
className={`payment-tab ${activeTab === 2 ? 'payment-tab-active' : ''}`}
|
|
53
|
+
>
|
|
54
|
+
Payment Actions
|
|
55
|
+
</Tab>
|
|
56
|
+
</Tabs>
|
|
57
|
+
<TabPanels>
|
|
58
|
+
<TabPanel>
|
|
59
|
+
<ConfigurationPanel
|
|
60
|
+
settings={settings}
|
|
61
|
+
isSaving={isSaving}
|
|
62
|
+
isTesting={isTesting}
|
|
63
|
+
testResult={testResult}
|
|
64
|
+
onSave={onSave}
|
|
65
|
+
onTestConnection={onTestConnection}
|
|
66
|
+
onInputChange={onInputChange}
|
|
67
|
+
/>
|
|
68
|
+
</TabPanel>
|
|
69
|
+
|
|
70
|
+
<TabPanel>
|
|
71
|
+
<HistoryPanel
|
|
72
|
+
filters={filters}
|
|
73
|
+
onFilterChange={onFilterChange}
|
|
74
|
+
onFilterApply={onFilterApply}
|
|
75
|
+
isLoadingHistory={isLoadingHistory}
|
|
76
|
+
transactionHistory={transactionHistory}
|
|
77
|
+
paginatedTransactions={paginatedTransactions}
|
|
78
|
+
currentPage={currentPage}
|
|
79
|
+
totalPages={totalPages}
|
|
80
|
+
pageSize={pageSize}
|
|
81
|
+
onRefresh={onRefresh}
|
|
82
|
+
onPageChange={onPageChange}
|
|
83
|
+
selectedTransaction={selectedTransaction}
|
|
84
|
+
onTransactionSelect={onTransactionSelect}
|
|
85
|
+
/>
|
|
86
|
+
</TabPanel>
|
|
87
|
+
|
|
88
|
+
<TabPanel>
|
|
89
|
+
<PaymentActionsPanel
|
|
90
|
+
paymentAmount={paymentActions.paymentAmount}
|
|
91
|
+
setPaymentAmount={paymentActions.setPaymentAmount}
|
|
92
|
+
preauthReference={paymentActions.preauthReference}
|
|
93
|
+
setPreauthReference={paymentActions.setPreauthReference}
|
|
94
|
+
authReference={paymentActions.authReference}
|
|
95
|
+
setAuthReference={paymentActions.setAuthReference}
|
|
96
|
+
captureTxid={paymentActions.captureTxid}
|
|
97
|
+
setCaptureTxid={paymentActions.setCaptureTxid}
|
|
98
|
+
refundTxid={paymentActions.refundTxid}
|
|
99
|
+
setRefundTxid={paymentActions.setRefundTxid}
|
|
100
|
+
refundSequenceNumber={paymentActions.refundSequenceNumber}
|
|
101
|
+
setRefundSequenceNumber={paymentActions.setRefundSequenceNumber}
|
|
102
|
+
refundReference={paymentActions.refundReference}
|
|
103
|
+
setRefundReference={paymentActions.setRefundReference}
|
|
104
|
+
paymentMethod={paymentActions.paymentMethod}
|
|
105
|
+
setPaymentMethod={paymentActions.setPaymentMethod}
|
|
106
|
+
captureMode={paymentActions.captureMode}
|
|
107
|
+
setCaptureMode={paymentActions.setCaptureMode}
|
|
108
|
+
isProcessingPayment={paymentActions.isProcessingPayment}
|
|
109
|
+
paymentError={paymentActions.paymentError}
|
|
110
|
+
paymentResult={paymentActions.paymentResult}
|
|
111
|
+
onPreauthorization={paymentActions.handlePreauthorization}
|
|
112
|
+
onAuthorization={paymentActions.handleAuthorization}
|
|
113
|
+
onCapture={paymentActions.handleCapture}
|
|
114
|
+
onRefund={paymentActions.handleRefund}
|
|
115
|
+
settings={settings}
|
|
116
|
+
googlePayToken={paymentActions.googlePayToken}
|
|
117
|
+
setGooglePayToken={paymentActions.setGooglePayToken}
|
|
118
|
+
cardtype={paymentActions.cardtype}
|
|
119
|
+
setCardtype={paymentActions.setCardtype}
|
|
120
|
+
cardpan={paymentActions.cardpan}
|
|
121
|
+
setCardpan={paymentActions.setCardpan}
|
|
122
|
+
cardexpiredate={paymentActions.cardexpiredate}
|
|
123
|
+
setCardexpiredate={paymentActions.setCardexpiredate}
|
|
124
|
+
cardcvc2={paymentActions.cardcvc2}
|
|
125
|
+
setCardcvc2={paymentActions.setCardcvc2}
|
|
126
|
+
/>
|
|
127
|
+
</TabPanel>
|
|
128
|
+
</TabPanels>
|
|
129
|
+
</TabGroup>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export default AppTabs;
|
|
134
|
+
|
|
@@ -25,30 +25,24 @@ const ConfigurationPanel = ({
|
|
|
25
25
|
}) => {
|
|
26
26
|
return (
|
|
27
27
|
<Box
|
|
28
|
-
|
|
29
|
-
shadow="filterShadow"
|
|
28
|
+
className="payment-container"
|
|
30
29
|
paddingTop={8}
|
|
31
30
|
paddingBottom={8}
|
|
32
31
|
paddingLeft={8}
|
|
33
32
|
paddingRight={8}
|
|
34
|
-
style={{
|
|
35
|
-
borderRadius: "12px",
|
|
36
|
-
boxShadow: "0 4px 20px rgba(0, 0, 0, 0.08)",
|
|
37
|
-
border: "1px solid #f6f6f9"
|
|
38
|
-
}}
|
|
39
33
|
>
|
|
40
34
|
<Flex direction="column" alignItems="stretch" gap={8}>
|
|
41
35
|
<Box>
|
|
42
|
-
<Typography variant="beta" as="h2" fontWeight="bold">
|
|
36
|
+
<Typography variant="beta" as="h2" fontWeight="bold" className="payment-title" style={{ fontSize: '20px', marginBottom: '4px' }}>
|
|
43
37
|
Payone API Configuration
|
|
44
38
|
</Typography>
|
|
45
|
-
<Typography variant="pi" marginTop={2}>
|
|
39
|
+
<Typography variant="pi" textColor="neutral600" marginTop={2} className="payment-subtitle" style={{ fontSize: '14px' }}>
|
|
46
40
|
Configure your Payone payment gateway settings
|
|
47
41
|
</Typography>
|
|
48
42
|
</Box>
|
|
49
43
|
|
|
50
44
|
<Box>
|
|
51
|
-
<Card
|
|
45
|
+
<Card className="payment-card">
|
|
52
46
|
<CardBody padding={6}>
|
|
53
47
|
<Stack spacing={6}>
|
|
54
48
|
<Flex gap={4} wrap="wrap">
|
|
@@ -59,6 +53,7 @@ const ConfigurationPanel = ({
|
|
|
59
53
|
onChange={(e) => onInputChange("aid", e.target.value)}
|
|
60
54
|
required
|
|
61
55
|
hint="Your Payone account ID"
|
|
56
|
+
className="payment-input"
|
|
62
57
|
style={{ flex: 1, minWidth: "300px" }}
|
|
63
58
|
/>
|
|
64
59
|
|
|
@@ -69,6 +64,7 @@ const ConfigurationPanel = ({
|
|
|
69
64
|
onChange={(e) => onInputChange("portalid", e.target.value)}
|
|
70
65
|
required
|
|
71
66
|
hint="Your Payone portal ID"
|
|
67
|
+
className="payment-input"
|
|
72
68
|
style={{ flex: 1, minWidth: "300px" }}
|
|
73
69
|
/>
|
|
74
70
|
</Flex>
|
|
@@ -81,6 +77,7 @@ const ConfigurationPanel = ({
|
|
|
81
77
|
onChange={(e) => onInputChange("mid", e.target.value)}
|
|
82
78
|
required
|
|
83
79
|
hint="Your Payone merchant ID"
|
|
80
|
+
className="payment-input"
|
|
84
81
|
style={{ flex: 1, minWidth: "300px" }}
|
|
85
82
|
/>
|
|
86
83
|
|
|
@@ -92,6 +89,7 @@ const ConfigurationPanel = ({
|
|
|
92
89
|
onChange={(e) => onInputChange("key", e.target.value)}
|
|
93
90
|
required
|
|
94
91
|
hint="Your Payone portal key (will be encrypted)"
|
|
92
|
+
className="payment-input"
|
|
95
93
|
style={{ flex: 1, minWidth: "300px" }}
|
|
96
94
|
/>
|
|
97
95
|
</Flex>
|
|
@@ -103,6 +101,7 @@ const ConfigurationPanel = ({
|
|
|
103
101
|
value={settings.mode || "test"}
|
|
104
102
|
onChange={(value) => onInputChange("mode", value)}
|
|
105
103
|
hint="Select the API mode"
|
|
104
|
+
className="payment-input"
|
|
106
105
|
style={{ flex: 1, minWidth: "300px" }}
|
|
107
106
|
>
|
|
108
107
|
<Option value="test">Test Environment</Option>
|
|
@@ -117,16 +116,36 @@ const ConfigurationPanel = ({
|
|
|
117
116
|
onInputChange("api_version", e.target.value)
|
|
118
117
|
}
|
|
119
118
|
hint="Payone API version"
|
|
119
|
+
className="payment-input"
|
|
120
120
|
style={{ flex: 1, minWidth: "300px" }}
|
|
121
121
|
/>
|
|
122
122
|
</Flex>
|
|
123
|
+
|
|
124
|
+
<Flex direction="column" wrap="wrap" gap={1} alignItems="flex-start">
|
|
125
|
+
<Select
|
|
126
|
+
label="Enable 3D Secure"
|
|
127
|
+
name="enable3DSecure"
|
|
128
|
+
value={settings.enable3DSecure ? "yes" : "no"}
|
|
129
|
+
onChange={(value) =>
|
|
130
|
+
onInputChange("enable3DSecure", value === "yes")
|
|
131
|
+
}
|
|
132
|
+
hint="Enable 3D Secure authentication for credit card payments"
|
|
133
|
+
className="payment-input"
|
|
134
|
+
>
|
|
135
|
+
<Option value="yes">Enabled</Option>
|
|
136
|
+
<Option value="no">Disabled</Option>
|
|
137
|
+
</Select>
|
|
138
|
+
<Typography variant="pi" textColor="neutral600" marginTop={1}>
|
|
139
|
+
When enabled, credit card payments will require 3D Secure authentication (SCA compliance)
|
|
140
|
+
</Typography>
|
|
141
|
+
</Flex>
|
|
123
142
|
</Stack>
|
|
124
143
|
</CardBody>
|
|
125
144
|
</Card>
|
|
126
145
|
</Box>
|
|
127
146
|
|
|
128
147
|
<Box paddingTop={6}>
|
|
129
|
-
<Card
|
|
148
|
+
<Card className="payment-card">
|
|
130
149
|
<CardBody padding={6}>
|
|
131
150
|
<Stack spacing={6}>
|
|
132
151
|
<Box>
|
|
@@ -149,13 +168,7 @@ const ConfigurationPanel = ({
|
|
|
149
168
|
onClick={onTestConnection}
|
|
150
169
|
loading={isTesting}
|
|
151
170
|
startIcon={<Play />}
|
|
152
|
-
|
|
153
|
-
background: "#28a745",
|
|
154
|
-
border: "none",
|
|
155
|
-
color: "white",
|
|
156
|
-
fontWeight: "600",
|
|
157
|
-
borderRadius: "8px"
|
|
158
|
-
}}
|
|
171
|
+
className="payment-button payment-button-success"
|
|
159
172
|
>
|
|
160
173
|
{isTesting ? "Testing Connection..." : "Test Connection"}
|
|
161
174
|
</Button>
|
|
@@ -168,12 +181,7 @@ const ConfigurationPanel = ({
|
|
|
168
181
|
? "Connection Successful"
|
|
169
182
|
: "Connection Failed"
|
|
170
183
|
}
|
|
171
|
-
|
|
172
|
-
borderRadius: "8px",
|
|
173
|
-
border: Boolean(testResult.success)
|
|
174
|
-
? "1px solid #d4edda"
|
|
175
|
-
: "1px solid #f8d7da"
|
|
176
|
-
}}
|
|
184
|
+
className="payment-alert"
|
|
177
185
|
>
|
|
178
186
|
<Typography
|
|
179
187
|
variant="pi"
|
|
@@ -185,11 +193,7 @@ const ConfigurationPanel = ({
|
|
|
185
193
|
{testResult.details && (
|
|
186
194
|
<Box paddingTop={3}>
|
|
187
195
|
{Boolean(testResult.success) ? (
|
|
188
|
-
<Card
|
|
189
|
-
style={{
|
|
190
|
-
border: "1px solid #e9ecef"
|
|
191
|
-
}}
|
|
192
|
-
>
|
|
196
|
+
<Card className="payment-card">
|
|
193
197
|
<CardBody padding={4}>
|
|
194
198
|
<Typography variant="pi">
|
|
195
199
|
<strong>Mode:</strong> {testResult.details.mode}{" "}
|
|
@@ -202,12 +206,7 @@ const ConfigurationPanel = ({
|
|
|
202
206
|
</CardBody>
|
|
203
207
|
</Card>
|
|
204
208
|
) : (
|
|
205
|
-
<Card
|
|
206
|
-
style={{
|
|
207
|
-
background: "#fff5f5",
|
|
208
|
-
border: "1px solid #fed7d7"
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
209
|
+
<Card className="payment-card" style={{ background: "#fff5f5" }}>
|
|
211
210
|
<CardBody padding={4}>
|
|
212
211
|
<Stack spacing={2}>
|
|
213
212
|
{testResult.errorcode && (
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Box, Flex, Typography } from "@strapi/design-system";
|
|
3
|
+
import { injectGooglePayScript } from "../../utils/injectGooglePayScript";
|
|
4
|
+
|
|
5
|
+
const GooglePayButton = ({
|
|
6
|
+
amount,
|
|
7
|
+
currency = "EUR",
|
|
8
|
+
onTokenReceived,
|
|
9
|
+
onError,
|
|
10
|
+
settings
|
|
11
|
+
}) => {
|
|
12
|
+
const [isReady, setIsReady] = useState(false);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14
|
+
const buttonContainerRef = useRef(null);
|
|
15
|
+
const paymentsClientRef = useRef(null);
|
|
16
|
+
|
|
17
|
+
const baseRequest = {
|
|
18
|
+
apiVersion: 2,
|
|
19
|
+
apiVersionMinor: 0
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getGooglePayConfig = (settings) => {
|
|
23
|
+
const gatewayMerchantId = settings?.mid || settings?.portalid || '';
|
|
24
|
+
|
|
25
|
+
const allowedCardNetworks = ["MASTERCARD", "VISA"];
|
|
26
|
+
const allowedAuthMethods = ["PAN_ONLY", "CRYPTOGRAM_3DS"];
|
|
27
|
+
|
|
28
|
+
const tokenizationSpecification = {
|
|
29
|
+
type: 'PAYMENT_GATEWAY',
|
|
30
|
+
parameters: {
|
|
31
|
+
'gateway': 'payonegmbh',
|
|
32
|
+
'gatewayMerchantId': gatewayMerchantId
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const baseCardPaymentMethod = {
|
|
37
|
+
type: "CARD",
|
|
38
|
+
parameters: {
|
|
39
|
+
allowedCardNetworks,
|
|
40
|
+
allowedAuthMethods
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const cardPaymentMethod = {
|
|
45
|
+
...baseCardPaymentMethod,
|
|
46
|
+
tokenizationSpecification
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
baseCardPaymentMethod,
|
|
50
|
+
cardPaymentMethod,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
injectGooglePayScript();
|
|
56
|
+
|
|
57
|
+
const checkGooglePay = () => {
|
|
58
|
+
try {
|
|
59
|
+
return typeof window !== 'undefined' &&
|
|
60
|
+
typeof window.google !== 'undefined' &&
|
|
61
|
+
window.google.payments?.api?.PaymentsClient;
|
|
62
|
+
} catch (e) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (checkGooglePay()) {
|
|
68
|
+
initializeGooglePay();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const handleScriptLoaded = () => {
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
if (checkGooglePay()) {
|
|
75
|
+
initializeGooglePay();
|
|
76
|
+
}
|
|
77
|
+
}, 500);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleScriptError = () => {
|
|
81
|
+
setIsLoading(false);
|
|
82
|
+
if (onError) {
|
|
83
|
+
onError(new Error("Failed to load Google Pay script. Please check CSP settings."));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
window.addEventListener("googlePayScriptLoaded", handleScriptLoaded);
|
|
88
|
+
window.addEventListener("googlePayScriptError", handleScriptError);
|
|
89
|
+
|
|
90
|
+
const checkInterval = setInterval(() => {
|
|
91
|
+
if (checkGooglePay()) {
|
|
92
|
+
clearInterval(checkInterval);
|
|
93
|
+
initializeGooglePay();
|
|
94
|
+
}
|
|
95
|
+
}, 200);
|
|
96
|
+
|
|
97
|
+
const timeout = setTimeout(() => {
|
|
98
|
+
clearInterval(checkInterval);
|
|
99
|
+
if (!checkGooglePay()) {
|
|
100
|
+
setIsLoading(false);
|
|
101
|
+
if (onError) {
|
|
102
|
+
onError(new Error("Google Pay API is not available. Please check if the script is loaded and CSP allows pay.google.com"));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}, 15000);
|
|
106
|
+
|
|
107
|
+
return () => {
|
|
108
|
+
window.removeEventListener("googlePayScriptLoaded", handleScriptLoaded);
|
|
109
|
+
window.removeEventListener("googlePayScriptError", handleScriptError);
|
|
110
|
+
clearInterval(checkInterval);
|
|
111
|
+
clearTimeout(timeout);
|
|
112
|
+
};
|
|
113
|
+
}, [settings, amount, currency]);
|
|
114
|
+
|
|
115
|
+
const initializeGooglePay = () => {
|
|
116
|
+
if (!settings || (!settings.mid && !settings.portalid)) {
|
|
117
|
+
setIsLoading(false);
|
|
118
|
+
if (onError) {
|
|
119
|
+
onError(new Error("Google Pay settings missing. Please configure mid or portalid in settings."));
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
if (typeof window === 'undefined' || typeof window.google === 'undefined' || !window.google.payments?.api?.PaymentsClient) {
|
|
126
|
+
setIsLoading(false);
|
|
127
|
+
if (onError) {
|
|
128
|
+
onError(new Error("Google Pay API is not loaded. Please check browser console for CSP errors."));
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
const environment = settings?.mode === "live" ? "PRODUCTION" : "TEST";
|
|
135
|
+
paymentsClientRef.current = new window.google.payments.api.PaymentsClient({
|
|
136
|
+
environment: environment
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const config = getGooglePayConfig(settings);
|
|
140
|
+
|
|
141
|
+
const isReadyToPayRequest = Object.assign({}, baseRequest);
|
|
142
|
+
isReadyToPayRequest.allowedPaymentMethods = [config.baseCardPaymentMethod];
|
|
143
|
+
|
|
144
|
+
paymentsClientRef.current.isReadyToPay(isReadyToPayRequest)
|
|
145
|
+
.then((response) => {
|
|
146
|
+
if (response.result) {
|
|
147
|
+
setIsReady(true);
|
|
148
|
+
const tryAddButton = () => {
|
|
149
|
+
if (buttonContainerRef.current) {
|
|
150
|
+
addGooglePayButton(config);
|
|
151
|
+
} else {
|
|
152
|
+
setTimeout(tryAddButton, 100);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
setTimeout(tryAddButton, 100);
|
|
156
|
+
} else {
|
|
157
|
+
setIsLoading(false);
|
|
158
|
+
if (onError) {
|
|
159
|
+
onError(new Error("Google Pay is not available on this device"));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.catch((err) => {
|
|
164
|
+
setIsLoading(false);
|
|
165
|
+
if (onError) {
|
|
166
|
+
onError(err);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
setIsLoading(false);
|
|
171
|
+
if (onError) {
|
|
172
|
+
onError(error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const addGooglePayButton = (config) => {
|
|
178
|
+
if (!buttonContainerRef.current) {
|
|
179
|
+
setTimeout(() => {
|
|
180
|
+
if (buttonContainerRef.current) {
|
|
181
|
+
addGooglePayButton(config);
|
|
182
|
+
} else {
|
|
183
|
+
setIsLoading(false);
|
|
184
|
+
}
|
|
185
|
+
}, 500);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!paymentsClientRef.current) {
|
|
190
|
+
setIsLoading(false);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
buttonContainerRef.current.innerHTML = "";
|
|
195
|
+
|
|
196
|
+
const gatewayMerchantId = settings?.mid || settings?.portalid || '';
|
|
197
|
+
const paymentDataRequest = Object.assign({}, baseRequest);
|
|
198
|
+
paymentDataRequest.allowedPaymentMethods = [config.cardPaymentMethod];
|
|
199
|
+
paymentDataRequest.transactionInfo = {
|
|
200
|
+
totalPriceStatus: "FINAL",
|
|
201
|
+
totalPrice: (parseFloat(amount) / 100).toFixed(2),
|
|
202
|
+
currencyCode: currency
|
|
203
|
+
};
|
|
204
|
+
paymentDataRequest.merchantInfo = {
|
|
205
|
+
merchantId: gatewayMerchantId,
|
|
206
|
+
merchantName: settings?.merchantName || 'Demo Shop'
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const button = paymentsClientRef.current.createButton({
|
|
211
|
+
onClick: () => handleGooglePayClick(paymentDataRequest),
|
|
212
|
+
buttonColor: "black",
|
|
213
|
+
buttonType: "pay",
|
|
214
|
+
buttonSizeMode: "fill"
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (buttonContainerRef.current) {
|
|
218
|
+
buttonContainerRef.current.appendChild(button);
|
|
219
|
+
setIsLoading(false);
|
|
220
|
+
setIsReady(true);
|
|
221
|
+
} else {
|
|
222
|
+
setIsLoading(false);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
setIsLoading(false);
|
|
226
|
+
if (onError) {
|
|
227
|
+
onError(error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const handleGooglePayClick = async (paymentDataRequest) => {
|
|
233
|
+
try {
|
|
234
|
+
if (!paymentsClientRef.current) {
|
|
235
|
+
throw new Error("Google Pay client not initialized");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const paymentData = await paymentsClientRef.current.loadPaymentData(paymentDataRequest);
|
|
239
|
+
const rawToken = paymentData.paymentMethodData?.tokenizationData?.token;
|
|
240
|
+
|
|
241
|
+
if (!rawToken) {
|
|
242
|
+
throw new Error("Google Pay token is missing from payment data");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let token = rawToken;
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const tokenObj = JSON.parse(token);
|
|
249
|
+
if (!tokenObj.signature || !tokenObj.protocolVersion || !tokenObj.signedMessage) {
|
|
250
|
+
throw new Error("Google Pay token is missing required fields");
|
|
251
|
+
}
|
|
252
|
+
token = btoa(unescape(encodeURIComponent(rawToken)));
|
|
253
|
+
} catch (e) {
|
|
254
|
+
if (typeof token === 'string') {
|
|
255
|
+
token = btoa(unescape(encodeURIComponent(token)));
|
|
256
|
+
} else {
|
|
257
|
+
throw new Error(`Invalid Google Pay token format: ${e.message}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (onTokenReceived) {
|
|
262
|
+
onTokenReceived(token, paymentData);
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (onError) {
|
|
266
|
+
onError(error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<Box width="100%">
|
|
273
|
+
<Flex direction="column" gap={3} alignItems="stretch">
|
|
274
|
+
{isLoading && (
|
|
275
|
+
<Typography variant="pi" textColor="neutral600" style={{ textAlign: "left" }}>
|
|
276
|
+
Loading Google Pay...
|
|
277
|
+
</Typography>
|
|
278
|
+
)}
|
|
279
|
+
{!isLoading && !isReady && (
|
|
280
|
+
<Typography variant="pi" textColor="neutral600" style={{ textAlign: "left" }}>
|
|
281
|
+
Google Pay is not available
|
|
282
|
+
</Typography>
|
|
283
|
+
)}
|
|
284
|
+
{!isLoading && isReady && (
|
|
285
|
+
<>
|
|
286
|
+
<Typography variant="sigma" textColor="neutral700" fontWeight="semiBold" style={{ textAlign: "left" }}>
|
|
287
|
+
Google Pay Payment
|
|
288
|
+
</Typography>
|
|
289
|
+
<Typography variant="pi" textColor="neutral600" style={{ textAlign: "left" }}>
|
|
290
|
+
Click the button below to pay with Google Pay. The token will be automatically sent to Payone.
|
|
291
|
+
</Typography>
|
|
292
|
+
</>
|
|
293
|
+
)}
|
|
294
|
+
<Box ref={buttonContainerRef} style={{ minHeight: "40px", width: "100%", display: "flex", justifyContent: "flex-start" }} />
|
|
295
|
+
</Flex>
|
|
296
|
+
</Box>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export default GooglePayButton;
|