@tagadapay/plugin-sdk 3.1.8 → 3.1.10
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 +1129 -1129
- package/build-cdn.js +223 -113
- package/dist/external-tracker.js +135 -81
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/tagada-sdk.js +10164 -0
- package/dist/tagada-sdk.min.js +45 -0
- package/dist/tagada-sdk.min.js.map +7 -0
- package/dist/v2/core/funnelClient.d.ts +91 -4
- package/dist/v2/core/funnelClient.js +42 -3
- package/dist/v2/core/resources/funnel.d.ts +10 -0
- package/dist/v2/core/resources/payments.d.ts +21 -1
- package/dist/v2/core/resources/payments.js +34 -0
- package/dist/v2/core/utils/currency.d.ts +14 -0
- package/dist/v2/core/utils/currency.js +40 -0
- package/dist/v2/core/utils/index.d.ts +1 -0
- package/dist/v2/core/utils/index.js +2 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
- package/dist/v2/core/utils/pluginConfig.js +28 -0
- package/dist/v2/core/utils/previewMode.d.ts +4 -0
- package/dist/v2/core/utils/previewMode.js +28 -0
- package/dist/v2/core/utils/previewModeIndicator.js +101 -101
- package/dist/v2/index.d.ts +7 -6
- package/dist/v2/index.js +6 -6
- package/dist/v2/react/components/ApplePayButton.d.ts +1 -2
- package/dist/v2/react/components/ApplePayButton.js +57 -58
- package/dist/v2/react/components/FunnelScriptInjector.js +161 -172
- package/dist/v2/react/components/GooglePayButton.d.ts +2 -0
- package/dist/v2/react/components/GooglePayButton.js +80 -64
- package/dist/v2/react/hooks/useFunnel.d.ts +8 -2
- package/dist/v2/react/hooks/useFunnel.js +2 -2
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +10 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +48 -0
- package/dist/v2/react/hooks/useGooglePayCheckout.d.ts +21 -0
- package/dist/v2/react/hooks/useGooglePayCheckout.js +198 -0
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +7 -1
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +2 -0
- package/dist/v2/react/hooks/usePaymentQuery.js +435 -8
- package/dist/v2/react/hooks/usePixelTracking.d.ts +56 -0
- package/dist/v2/react/hooks/usePixelTracking.js +508 -0
- package/dist/v2/react/hooks/useStepConfig.d.ts +8 -6
- package/dist/v2/react/hooks/useStepConfig.js +3 -2
- package/dist/v2/react/index.d.ts +6 -2
- package/dist/v2/react/index.js +3 -1
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +1 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +33 -13
- package/dist/v2/react/providers/TagadaProvider.js +22 -21
- package/dist/v2/standalone/index.js +1 -1
- package/package.json +112 -112
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
3
3
|
import { getAssignedStepConfig } from '../../core';
|
|
4
4
|
/**
|
|
5
5
|
* FunnelScriptInjector - Handles injection of funnel scripts into the page.
|
|
@@ -26,158 +26,144 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
26
26
|
}
|
|
27
27
|
return scripts;
|
|
28
28
|
}, [isInitialized]); // Re-compute when funnel initializes (local config should be loaded by then)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Set up Tagada on window object - must be available before any scripts run
|
|
30
|
+
// Memoized to prevent unnecessary recreations across re-renders
|
|
31
|
+
const setupTagada = useCallback(() => {
|
|
32
|
+
if (typeof window === 'undefined')
|
|
32
33
|
return;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (window.Tagada) {
|
|
38
|
-
// Update pageType if context is available
|
|
39
|
-
if (context?.currentStepId) {
|
|
40
|
-
// @ts-expect-error - Updating window property
|
|
41
|
-
window.Tagada.pageType = context.currentStepId;
|
|
42
|
-
}
|
|
43
|
-
// Update isInitialized
|
|
44
|
-
// @ts-expect-error - Updating window property
|
|
45
|
-
window.Tagada.isInitialized = isInitialized;
|
|
46
|
-
// Update ressources
|
|
34
|
+
// @ts-expect-error - Adding utilities to window
|
|
35
|
+
if (window.Tagada) {
|
|
36
|
+
// Update properties if Tagada already exists
|
|
37
|
+
if (context?.currentStepId) {
|
|
47
38
|
// @ts-expect-error - Updating window property
|
|
48
|
-
window.Tagada.
|
|
49
|
-
return; // Utils already exist, just update properties
|
|
39
|
+
window.Tagada.pageType = context.currentStepId;
|
|
50
40
|
}
|
|
51
|
-
// @ts-expect-error -
|
|
52
|
-
window.Tagada =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
// @ts-expect-error - Updating window property
|
|
42
|
+
window.Tagada.isInitialized = isInitialized;
|
|
43
|
+
// @ts-expect-error - Updating window property
|
|
44
|
+
window.Tagada.ressources = context?.resources || null;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// @ts-expect-error - Adding utilities to window
|
|
48
|
+
window.Tagada = {
|
|
49
|
+
// Wait for DOM to be ready
|
|
50
|
+
ready: (callback) => {
|
|
51
|
+
if (document.readyState === 'loading') {
|
|
52
|
+
document.addEventListener('DOMContentLoaded', callback);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
callback();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
// Wait for window to be fully loaded AND funnel to be initialized
|
|
59
|
+
loaded: (callback) => {
|
|
60
|
+
const checkBothConditions = () => {
|
|
61
|
+
const pageLoaded = document.readyState === 'complete';
|
|
62
|
+
// @ts-expect-error - Accessing window property
|
|
63
|
+
const funnelInitialized = window.Tagada?.isInitialized === true;
|
|
64
|
+
if (pageLoaded && funnelInitialized) {
|
|
59
65
|
callback();
|
|
66
|
+
return true;
|
|
60
67
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
74
|
-
// Check immediately
|
|
75
|
-
if (checkBothConditions()) {
|
|
76
|
-
return;
|
|
68
|
+
return false;
|
|
69
|
+
};
|
|
70
|
+
if (checkBothConditions()) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let loadListener = null;
|
|
74
|
+
let initCheckInterval = null;
|
|
75
|
+
let hasCalled = false;
|
|
76
|
+
const cleanup = () => {
|
|
77
|
+
if (loadListener) {
|
|
78
|
+
window.removeEventListener('load', loadListener);
|
|
79
|
+
loadListener = null;
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
initCheckInterval = setInterval(() => {
|
|
102
|
-
if (checkBothConditions() && !hasCalled) {
|
|
103
|
-
hasCalled = true;
|
|
104
|
-
cleanup();
|
|
105
|
-
}
|
|
106
|
-
}, 100);
|
|
107
|
-
// Timeout fallback (10 seconds max wait)
|
|
108
|
-
setTimeout(() => {
|
|
109
|
-
if (!hasCalled) {
|
|
110
|
-
hasCalled = true;
|
|
111
|
-
cleanup();
|
|
112
|
-
// Call anyway if page is loaded (graceful degradation)
|
|
113
|
-
if (document.readyState === 'complete') {
|
|
114
|
-
callback();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}, 10000);
|
|
118
|
-
},
|
|
119
|
-
// Execute with delay
|
|
120
|
-
delay: (callback, ms = 1000) => {
|
|
121
|
-
setTimeout(callback, ms);
|
|
122
|
-
},
|
|
123
|
-
// Retry until condition is met
|
|
124
|
-
retry: (condition, callback, maxAttempts = 10, interval = 500) => {
|
|
125
|
-
let attempts = 0;
|
|
126
|
-
const check = () => {
|
|
127
|
-
attempts++;
|
|
128
|
-
if (condition() || attempts >= maxAttempts) {
|
|
81
|
+
if (initCheckInterval) {
|
|
82
|
+
clearInterval(initCheckInterval);
|
|
83
|
+
initCheckInterval = null;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
loadListener = () => {
|
|
87
|
+
if (checkBothConditions() && !hasCalled) {
|
|
88
|
+
hasCalled = true;
|
|
89
|
+
cleanup();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
window.addEventListener('load', loadListener);
|
|
93
|
+
initCheckInterval = setInterval(() => {
|
|
94
|
+
if (checkBothConditions() && !hasCalled) {
|
|
95
|
+
hasCalled = true;
|
|
96
|
+
cleanup();
|
|
97
|
+
}
|
|
98
|
+
}, 100);
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
if (!hasCalled) {
|
|
101
|
+
hasCalled = true;
|
|
102
|
+
cleanup();
|
|
103
|
+
if (document.readyState === 'complete') {
|
|
129
104
|
callback();
|
|
130
105
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
106
|
+
}
|
|
107
|
+
}, 10000);
|
|
108
|
+
},
|
|
109
|
+
delay: (callback, ms = 1000) => {
|
|
110
|
+
setTimeout(callback, ms);
|
|
111
|
+
},
|
|
112
|
+
retry: (condition, callback, maxAttempts = 10, interval = 500) => {
|
|
113
|
+
let attempts = 0;
|
|
114
|
+
const check = () => {
|
|
115
|
+
attempts++;
|
|
116
|
+
if (condition() || attempts >= maxAttempts) {
|
|
117
|
+
callback();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
setTimeout(check, interval);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
check();
|
|
124
|
+
},
|
|
125
|
+
waitForElement: (selector, callback, timeout = 10000) => {
|
|
126
|
+
const element = document.querySelector(selector);
|
|
127
|
+
if (element) {
|
|
128
|
+
callback(element);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const observer = new MutationObserver(() => {
|
|
139
132
|
const element = document.querySelector(selector);
|
|
140
133
|
if (element) {
|
|
141
|
-
callback(element);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const observer = new MutationObserver(() => {
|
|
145
|
-
const element = document.querySelector(selector);
|
|
146
|
-
if (element) {
|
|
147
|
-
observer.disconnect();
|
|
148
|
-
callback(element);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
observer.observe(document.body, {
|
|
152
|
-
childList: true,
|
|
153
|
-
subtree: true,
|
|
154
|
-
});
|
|
155
|
-
// Timeout fallback
|
|
156
|
-
setTimeout(() => {
|
|
157
134
|
observer.disconnect();
|
|
158
|
-
|
|
159
|
-
},
|
|
160
|
-
// Page type helper (current step ID)
|
|
161
|
-
pageType: context?.currentStepId || null,
|
|
162
|
-
// Funnel initialization status
|
|
163
|
-
isInitialized: isInitialized,
|
|
164
|
-
// Expose resources directly (convenience access)
|
|
165
|
-
ressources: context?.resources || null,
|
|
166
|
-
// Expose funnel context data
|
|
167
|
-
funnel: context
|
|
168
|
-
? {
|
|
169
|
-
sessionId: context.sessionId,
|
|
170
|
-
funnelId: context.funnelId,
|
|
171
|
-
currentStepId: context.currentStepId,
|
|
172
|
-
previousStepId: context.previousStepId,
|
|
173
|
-
ressources: context.resources,
|
|
135
|
+
callback(element);
|
|
174
136
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
137
|
+
});
|
|
138
|
+
observer.observe(document.body, {
|
|
139
|
+
childList: true,
|
|
140
|
+
subtree: true,
|
|
141
|
+
});
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
observer.disconnect();
|
|
144
|
+
}, timeout);
|
|
145
|
+
},
|
|
146
|
+
pageType: context?.currentStepId || null,
|
|
147
|
+
isInitialized: isInitialized,
|
|
148
|
+
ressources: context?.resources || null,
|
|
149
|
+
funnel: context
|
|
150
|
+
? {
|
|
151
|
+
sessionId: context.sessionId,
|
|
152
|
+
funnelId: context.funnelId,
|
|
153
|
+
currentStepId: context.currentStepId,
|
|
154
|
+
previousStepId: context.previousStepId,
|
|
155
|
+
ressources: context.resources,
|
|
156
|
+
}
|
|
157
|
+
: null,
|
|
158
|
+
stepConfig: getAssignedStepConfig() || null,
|
|
179
159
|
};
|
|
180
|
-
|
|
160
|
+
}, [context, isInitialized]);
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
// Only run in browser environment
|
|
163
|
+
if (typeof document === 'undefined') {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Set up Tagada utilities before injecting legacy script
|
|
181
167
|
setupTagada();
|
|
182
168
|
const scriptContent = context?.script;
|
|
183
169
|
const scriptId = 'tagada-funnel-script';
|
|
@@ -213,27 +199,27 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
213
199
|
existingScript.remove();
|
|
214
200
|
}
|
|
215
201
|
// Wrap script content with error handling and context checks
|
|
216
|
-
const wrappedScript = `
|
|
217
|
-
(function() {
|
|
218
|
-
try {
|
|
219
|
-
// Check if we have basic DOM access
|
|
220
|
-
if (typeof document === 'undefined') {
|
|
221
|
-
console.error('[TagadaPay] Document not available');
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Check if we have Tagada
|
|
226
|
-
if (!window.Tagada) {
|
|
227
|
-
console.error('[TagadaPay] Tagada not available');
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Execute the original script
|
|
232
|
-
${scriptBody}
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error('[TagadaPay] Script execution error:', error);
|
|
235
|
-
}
|
|
236
|
-
})();
|
|
202
|
+
const wrappedScript = `
|
|
203
|
+
(function() {
|
|
204
|
+
try {
|
|
205
|
+
// Check if we have basic DOM access
|
|
206
|
+
if (typeof document === 'undefined') {
|
|
207
|
+
console.error('[TagadaPay] Document not available');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if we have Tagada
|
|
212
|
+
if (!window.Tagada) {
|
|
213
|
+
console.error('[TagadaPay] Tagada not available');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Execute the original script
|
|
218
|
+
${scriptBody}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error('[TagadaPay] Script execution error:', error);
|
|
221
|
+
}
|
|
222
|
+
})();
|
|
237
223
|
`;
|
|
238
224
|
// Create and inject new script element
|
|
239
225
|
const scriptElement = document.createElement('script');
|
|
@@ -252,11 +238,14 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
252
238
|
// This prevents React StrictMode from re-injecting the same script on the second run
|
|
253
239
|
// The ref will be cleared when script content actually changes (next effect run)
|
|
254
240
|
};
|
|
255
|
-
}, [context?.script, context?.currentStepId, isInitialized]);
|
|
241
|
+
}, [context?.script, context?.currentStepId, isInitialized, setupTagada]);
|
|
256
242
|
// Effect for NEW stepConfig.scripts (from HTML injection, supports A/B variants)
|
|
257
243
|
useEffect(() => {
|
|
258
244
|
if (typeof document === 'undefined')
|
|
259
245
|
return;
|
|
246
|
+
// IMPORTANT: Set up Tagada BEFORE injecting any scripts
|
|
247
|
+
// This ensures Tagada.ready() is always available when scripts run
|
|
248
|
+
setupTagada();
|
|
260
249
|
if (stepConfigScripts.length === 0)
|
|
261
250
|
return;
|
|
262
251
|
// Create a hash of current scripts to detect changes
|
|
@@ -284,15 +273,15 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
284
273
|
if (!scriptBody)
|
|
285
274
|
return;
|
|
286
275
|
// Wrap script content with error handling
|
|
287
|
-
const wrappedScript = `
|
|
288
|
-
(function() {
|
|
289
|
-
try {
|
|
290
|
-
// Script: ${script.name}
|
|
291
|
-
${scriptBody}
|
|
292
|
-
} catch (error) {
|
|
293
|
-
console.error('[TagadaPay] StepConfig script "${script.name}" error:', error);
|
|
294
|
-
}
|
|
295
|
-
})();
|
|
276
|
+
const wrappedScript = `
|
|
277
|
+
(function() {
|
|
278
|
+
try {
|
|
279
|
+
// Script: ${script.name}
|
|
280
|
+
${scriptBody}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('[TagadaPay] StepConfig script "${script.name}" error:', error);
|
|
283
|
+
}
|
|
284
|
+
})();
|
|
296
285
|
`;
|
|
297
286
|
// Create script element
|
|
298
287
|
const scriptElement = document.createElement('script');
|
|
@@ -340,7 +329,7 @@ export function FunnelScriptInjector({ context, isInitialized }) {
|
|
|
340
329
|
return () => {
|
|
341
330
|
document.querySelectorAll('[data-tagada-stepconfig-script]').forEach(el => el.remove());
|
|
342
331
|
};
|
|
343
|
-
}, [stepConfigScripts]);
|
|
332
|
+
}, [stepConfigScripts, setupTagada]);
|
|
344
333
|
// This component doesn't render anything
|
|
345
334
|
return null;
|
|
346
335
|
}
|
|
@@ -14,6 +14,8 @@ export interface GooglePayButtonProps {
|
|
|
14
14
|
size?: 'sm' | 'md' | 'lg';
|
|
15
15
|
buttonColor?: 'default' | 'black' | 'white';
|
|
16
16
|
buttonType?: 'buy' | 'plain' | 'donate' | 'pay';
|
|
17
|
+
/** Override shipping requirement. If undefined, auto-detects based on shippingMethods. */
|
|
18
|
+
requiresShipping?: boolean;
|
|
17
19
|
}
|
|
18
20
|
export declare const GooglePayButton: React.FC<GooglePayButtonProps>;
|
|
19
21
|
export default GooglePayButton;
|
|
@@ -4,13 +4,32 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
* Uses v2 useExpressPaymentMethods hook and follows clean architecture principles
|
|
5
5
|
*/
|
|
6
6
|
import GooglePayButtonReact from '@google-pay/button-react';
|
|
7
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
7
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
8
8
|
import { useExpressPaymentMethods } from '../hooks/useExpressPaymentMethods';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export const GooglePayButton = ({ className = '', disabled = false, onSuccess, onError, onCancel, checkout, size = 'lg', buttonColor = 'black', buttonType = 'plain', }) => {
|
|
9
|
+
import { usePaymentQuery } from '../hooks/usePaymentQuery';
|
|
10
|
+
import { useShippingRatesQuery } from '../hooks/useShippingRatesQuery';
|
|
11
|
+
import { getBasisTheoryKeys } from '../../../config/basisTheory';
|
|
12
|
+
export const GooglePayButton = ({ className = '', disabled = false, onSuccess, onError, onCancel, checkout, size = 'lg', buttonColor = 'black', buttonType = 'plain', requiresShipping: requiresShippingProp, }) => {
|
|
13
13
|
const { googlePayPaymentMethod, shippingMethods, lineItems, handleAddExpressId, updateCheckoutSessionValues, updateCustomerEmail, reComputeOrderSummary, setError: setContextError, } = useExpressPaymentMethods();
|
|
14
|
+
// Use payment query hook for processing
|
|
15
|
+
const { processGooglePayPayment } = usePaymentQuery();
|
|
16
|
+
// Use shipping rates hook for selecting shipping rates
|
|
17
|
+
const { selectRate } = useShippingRatesQuery({ checkout });
|
|
18
|
+
// Get Basis Theory credentials based on sandboxed flag from payment method config
|
|
19
|
+
// When sandboxed is true (even on production hostname), use test keys
|
|
20
|
+
const { basistheoryPublicKey, basistheoryTenantId } = useMemo(() => {
|
|
21
|
+
const useProductionKeys = googlePayPaymentMethod?.metadata?.sandboxed === false;
|
|
22
|
+
const keys = getBasisTheoryKeys(useProductionKeys);
|
|
23
|
+
console.log('[GooglePayButton] Using Basis Theory keys:', {
|
|
24
|
+
sandboxed: googlePayPaymentMethod?.metadata?.sandboxed,
|
|
25
|
+
useProductionKeys,
|
|
26
|
+
tenantId: keys.tenantId,
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
basistheoryPublicKey: keys.apiKey,
|
|
30
|
+
basistheoryTenantId: keys.tenantId,
|
|
31
|
+
};
|
|
32
|
+
}, [googlePayPaymentMethod?.metadata?.sandboxed]);
|
|
14
33
|
const [processingPayment, setProcessingPayment] = useState(false);
|
|
15
34
|
const [pendingPaymentData, setPendingPaymentData] = useState(null);
|
|
16
35
|
const [googlePayError, setGooglePayError] = useState(null);
|
|
@@ -65,46 +84,32 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
65
84
|
throw error;
|
|
66
85
|
}
|
|
67
86
|
}, []);
|
|
68
|
-
// Process Google Pay payment
|
|
69
|
-
const
|
|
70
|
-
if (!checkout.checkoutSession.id) {
|
|
71
|
-
throw new Error('Checkout session ID is not available');
|
|
72
|
-
}
|
|
87
|
+
// Process Google Pay payment using SDK hook
|
|
88
|
+
const handleGooglePayPayment = useCallback(async (token) => {
|
|
73
89
|
setProcessingPayment(true);
|
|
74
90
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
const result = await processGooglePayPayment(checkout.checkoutSession.id, token, {
|
|
92
|
+
onPaymentSuccess: (response) => {
|
|
93
|
+
// Keep processing state true during navigation
|
|
94
|
+
},
|
|
95
|
+
onPaymentFailed: (err) => {
|
|
96
|
+
setProcessingPayment(false);
|
|
97
|
+
setGooglePayError(err.message);
|
|
98
|
+
setContextError(err.message);
|
|
99
|
+
throw new Error(err.message);
|
|
80
100
|
},
|
|
81
|
-
body: JSON.stringify({
|
|
82
|
-
checkoutSessionId: checkout.checkoutSession.id,
|
|
83
|
-
paymentToken: token,
|
|
84
|
-
}),
|
|
85
101
|
});
|
|
86
|
-
|
|
87
|
-
throw new Error('Failed to process Google Pay payment');
|
|
88
|
-
}
|
|
89
|
-
const paymentResult = await response.json();
|
|
90
|
-
if (onSuccess) {
|
|
91
|
-
onSuccess(paymentResult);
|
|
92
|
-
}
|
|
102
|
+
return result;
|
|
93
103
|
}
|
|
94
104
|
catch (error) {
|
|
95
|
-
console.error('
|
|
105
|
+
console.error('Payment processing failed:', error);
|
|
106
|
+
setProcessingPayment(false);
|
|
96
107
|
const errorMessage = error instanceof Error ? error.message : 'Google Pay payment failed';
|
|
97
108
|
setGooglePayError(errorMessage);
|
|
98
109
|
setContextError(errorMessage);
|
|
99
|
-
if (onError) {
|
|
100
|
-
onError(errorMessage);
|
|
101
|
-
}
|
|
102
110
|
throw error;
|
|
103
111
|
}
|
|
104
|
-
|
|
105
|
-
setProcessingPayment(false);
|
|
106
|
-
}
|
|
107
|
-
}, [checkout.checkoutSession.id, onSuccess, onError, setContextError]);
|
|
112
|
+
}, [processGooglePayPayment, checkout.checkoutSession.id, setContextError]);
|
|
108
113
|
// Process payment data
|
|
109
114
|
const onGooglePaymentData = useCallback(async (paymentData) => {
|
|
110
115
|
setProcessingPayment(true);
|
|
@@ -137,7 +142,11 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
137
142
|
});
|
|
138
143
|
}
|
|
139
144
|
const payToken = await tokenizeGooglePayTokenWithBasisTheory(paymentData);
|
|
140
|
-
await
|
|
145
|
+
const result = await handleGooglePayPayment(payToken);
|
|
146
|
+
// Call success callback
|
|
147
|
+
if (onSuccess) {
|
|
148
|
+
onSuccess(result);
|
|
149
|
+
}
|
|
141
150
|
}
|
|
142
151
|
catch (error) {
|
|
143
152
|
console.error('Error processing Google Pay payment:', error);
|
|
@@ -154,7 +163,8 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
154
163
|
updateCheckoutSessionValues,
|
|
155
164
|
updateCustomerEmail,
|
|
156
165
|
tokenizeGooglePayTokenWithBasisTheory,
|
|
157
|
-
|
|
166
|
+
handleGooglePayPayment,
|
|
167
|
+
onSuccess,
|
|
158
168
|
onError,
|
|
159
169
|
setContextError,
|
|
160
170
|
]);
|
|
@@ -200,8 +210,12 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
200
210
|
});
|
|
201
211
|
const newOrderSummary = await reComputeOrderSummary();
|
|
202
212
|
if (newOrderSummary) {
|
|
213
|
+
// Use selected shipping rate ID if available, otherwise fall back to first one
|
|
214
|
+
const defaultSelectedId = newOrderSummary.selectedShippingRateId ||
|
|
215
|
+
newOrderSummary.shippingMethods[0]?.identifier ||
|
|
216
|
+
'';
|
|
203
217
|
paymentDataRequestUpdate.newShippingOptionParameters = {
|
|
204
|
-
defaultSelectedOptionId:
|
|
218
|
+
defaultSelectedOptionId: defaultSelectedId,
|
|
205
219
|
shippingOptions: newOrderSummary.shippingMethods.map((method) => ({
|
|
206
220
|
id: method.identifier,
|
|
207
221
|
label: method.label,
|
|
@@ -218,17 +232,8 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
218
232
|
else if (intermediatePaymentData.callbackTrigger === 'SHIPPING_OPTION') {
|
|
219
233
|
// Update shipping rate
|
|
220
234
|
if (intermediatePaymentData.shippingOptionData?.id) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
headers: {
|
|
224
|
-
'Content-Type': 'application/json',
|
|
225
|
-
},
|
|
226
|
-
body: JSON.stringify({
|
|
227
|
-
checkoutSessionId: checkout.checkoutSession.id,
|
|
228
|
-
shippingRateId: intermediatePaymentData.shippingOptionData.id,
|
|
229
|
-
}),
|
|
230
|
-
});
|
|
231
|
-
if (response.ok) {
|
|
235
|
+
try {
|
|
236
|
+
await selectRate(intermediatePaymentData.shippingOptionData.id);
|
|
232
237
|
const newOrderSummary = await reComputeOrderSummary();
|
|
233
238
|
if (newOrderSummary) {
|
|
234
239
|
paymentDataRequestUpdate.newTransactionInfo = {
|
|
@@ -238,13 +243,17 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
238
243
|
};
|
|
239
244
|
}
|
|
240
245
|
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error('Error selecting shipping rate:', error);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
241
250
|
}
|
|
242
251
|
}
|
|
243
252
|
else if (intermediatePaymentData.callbackTrigger === 'OFFER') {
|
|
244
|
-
|
|
253
|
+
// No action needed for OFFER callback
|
|
245
254
|
}
|
|
246
255
|
else if (intermediatePaymentData.callbackTrigger === 'INITIALIZE') {
|
|
247
|
-
|
|
256
|
+
// No action needed for INITIALIZE callback
|
|
248
257
|
}
|
|
249
258
|
resolve(paymentDataRequestUpdate);
|
|
250
259
|
}
|
|
@@ -261,7 +270,7 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
261
270
|
};
|
|
262
271
|
void processCallback();
|
|
263
272
|
});
|
|
264
|
-
}, [updateCheckoutSessionValues, reComputeOrderSummary, checkout]);
|
|
273
|
+
}, [updateCheckoutSessionValues, reComputeOrderSummary, checkout, selectRate]);
|
|
265
274
|
// Handle payment authorization
|
|
266
275
|
const handleGooglePayAuthorized = useCallback((paymentData) => {
|
|
267
276
|
setPendingPaymentData(paymentData);
|
|
@@ -295,6 +304,8 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
295
304
|
gatewayMerchantId: basistheoryTenantId,
|
|
296
305
|
},
|
|
297
306
|
};
|
|
307
|
+
// Determine if shipping is required: use prop if provided, otherwise auto-detect from shippingMethods
|
|
308
|
+
const requiresShipping = requiresShippingProp ?? shippingMethods.length > 0;
|
|
298
309
|
const paymentRequest = {
|
|
299
310
|
apiVersion: 2,
|
|
300
311
|
apiVersionMinor: 0,
|
|
@@ -313,22 +324,28 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
313
324
|
merchantName: googlePayPaymentMethod?.metadata?.merchantName ||
|
|
314
325
|
checkout.checkoutSession?.store?.name ||
|
|
315
326
|
'Store',
|
|
327
|
+
// Use test merchant ID for sandbox, actual merchant ID for production
|
|
316
328
|
merchantId: googlePayPaymentMethod?.metadata?.sandboxed
|
|
317
|
-
? '12345678901234567890'
|
|
329
|
+
? '12345678901234567890' // Google's test merchant ID for sandbox
|
|
318
330
|
: googlePayPaymentMethod?.metadata?.merchantId || '12345678901234567890',
|
|
319
331
|
},
|
|
320
|
-
shippingAddressRequired:
|
|
321
|
-
shippingOptionRequired:
|
|
332
|
+
shippingAddressRequired: requiresShipping,
|
|
333
|
+
shippingOptionRequired: requiresShipping,
|
|
322
334
|
emailRequired: true,
|
|
323
|
-
callbackIntents:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
335
|
+
callbackIntents: requiresShipping
|
|
336
|
+
? ['SHIPPING_OPTION', 'SHIPPING_ADDRESS', 'PAYMENT_AUTHORIZATION']
|
|
337
|
+
: ['PAYMENT_AUTHORIZATION'],
|
|
338
|
+
...(requiresShipping && {
|
|
339
|
+
shippingOptionParameters: {
|
|
340
|
+
defaultSelectedOptionId: checkout.checkoutSession?.shippingRate?.id ||
|
|
341
|
+
(shippingMethods.length > 0 ? shippingMethods[0].identifier : ''),
|
|
342
|
+
shippingOptions: shippingMethods.map((method) => ({
|
|
343
|
+
id: method.identifier,
|
|
344
|
+
label: method.label,
|
|
345
|
+
description: method.amount + ': ' + method.detail || '',
|
|
346
|
+
})),
|
|
347
|
+
},
|
|
348
|
+
}),
|
|
332
349
|
};
|
|
333
350
|
const environment = googlePayPaymentMethod?.metadata?.sandboxed ? 'TEST' : 'PRODUCTION';
|
|
334
351
|
// Button size classes
|
|
@@ -347,7 +364,6 @@ export const GooglePayButton = ({ className = '', disabled = false, onSuccess, o
|
|
|
347
364
|
onError(errorMessage);
|
|
348
365
|
}
|
|
349
366
|
}, onCancel: () => {
|
|
350
|
-
console.log('Google Pay payment cancelled');
|
|
351
367
|
if (onCancel) {
|
|
352
368
|
onCancel();
|
|
353
369
|
}
|