strapi-plugin-payone-provider 1.1.3 → 1.2.4

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 (36) hide show
  1. package/README.md +1041 -377
  2. package/admin/src/index.js +4 -1
  3. package/admin/src/pages/App/components/AppHeader.js +37 -0
  4. package/admin/src/pages/App/components/AppTabs.js +126 -0
  5. package/admin/src/pages/App/components/ConfigurationPanel.js +34 -35
  6. package/admin/src/pages/App/components/GooglePaybutton.js +300 -0
  7. package/admin/src/pages/App/components/HistoryPanel.js +25 -38
  8. package/admin/src/pages/App/components/PaymentActionsPanel.js +95 -280
  9. package/admin/src/pages/App/components/TransactionHistoryItem.js +4 -1
  10. package/admin/src/pages/App/components/paymentActions/AuthorizationForm.js +93 -0
  11. package/admin/src/pages/App/components/paymentActions/CaptureForm.js +64 -0
  12. package/admin/src/pages/App/components/paymentActions/PaymentMethodSelector.js +52 -0
  13. package/admin/src/pages/App/components/paymentActions/PaymentResult.js +85 -0
  14. package/admin/src/pages/App/components/paymentActions/PreauthorizationForm.js +93 -0
  15. package/admin/src/pages/App/components/paymentActions/RefundForm.js +89 -0
  16. package/admin/src/pages/App/index.js +41 -465
  17. package/admin/src/pages/App/styles.css +294 -0
  18. package/admin/src/pages/constants/paymentConstants.js +37 -0
  19. package/admin/src/pages/hooks/usePaymentActions.js +271 -0
  20. package/admin/src/pages/hooks/useSettings.js +111 -0
  21. package/admin/src/pages/hooks/useTransactionHistory.js +87 -0
  22. package/admin/src/pages/utils/api.js +10 -0
  23. package/admin/src/pages/utils/injectGooglePayScript.js +31 -0
  24. package/admin/src/pages/utils/paymentUtils.js +113 -13
  25. package/package.json +1 -1
  26. package/server/controllers/payone.js +71 -64
  27. package/server/routes/index.js +17 -0
  28. package/server/services/paymentService.js +214 -0
  29. package/server/services/payone.js +25 -648
  30. package/server/services/settingsService.js +59 -0
  31. package/server/services/testConnectionService.js +190 -0
  32. package/server/services/transactionService.js +114 -0
  33. package/server/utils/normalize.js +51 -0
  34. package/server/utils/paymentMethodParams.js +126 -0
  35. package/server/utils/requestBuilder.js +110 -0
  36. package/server/utils/responseParser.js +80 -0
@@ -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,126 @@
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
+ />
119
+ </TabPanel>
120
+ </TabPanels>
121
+ </TabGroup>
122
+ );
123
+ };
124
+
125
+ export default AppTabs;
126
+
@@ -25,30 +25,24 @@ const ConfigurationPanel = ({
25
25
  }) => {
26
26
  return (
27
27
  <Box
28
- hasRadius
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 style={{ borderRadius: "8px", border: "1px solid #e4e2e7" }}>
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 style={{ borderRadius: "8px", border: "1px solid #e4e2e7" }}>
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
- style={{
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
- style={{
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 padding={4}>
273
+ <Flex direction="column" gap={3}>
274
+ {isLoading && (
275
+ <Typography variant="pi" textColor="neutral600">
276
+ Loading Google Pay...
277
+ </Typography>
278
+ )}
279
+ {!isLoading && !isReady && (
280
+ <Typography variant="pi" textColor="neutral600">
281
+ Google Pay is not available
282
+ </Typography>
283
+ )}
284
+ {!isLoading && isReady && (
285
+ <>
286
+ <Typography variant="sigma" textColor="neutral700" fontWeight="semiBold">
287
+ Google Pay Payment
288
+ </Typography>
289
+ <Typography variant="pi" textColor="neutral600">
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" }} />
295
+ </Flex>
296
+ </Box>
297
+ );
298
+ };
299
+
300
+ export default GooglePayButton;