@tagadapay/plugin-sdk 2.6.4 → 2.6.6
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/dist/react/components/GooglePayButton.d.ts +12 -0
- package/dist/react/components/GooglePayButton.js +340 -0
- package/dist/react/components/index.d.ts +3 -2
- package/dist/react/components/index.js +3 -2
- package/dist/react/hooks/useApplePay.js +38 -10
- package/dist/react/hooks/useGoogleAutocomplete.d.ts +2 -0
- package/dist/react/hooks/useGoogleAutocomplete.js +18 -2
- package/dist/react/hooks/useGooglePay.d.ts +22 -0
- package/dist/react/hooks/useGooglePay.js +32 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +3 -1
- package/dist/react/types/apple-pay.d.ts +0 -25
- package/dist/v2/core/googleAutocomplete.d.ts +2 -0
- package/dist/v2/core/googleAutocomplete.js +21 -8
- package/dist/v2/core/resources/checkout.d.ts +70 -2
- package/dist/v2/core/resources/discounts.d.ts +53 -0
- package/dist/v2/core/resources/discounts.js +29 -0
- package/dist/v2/core/resources/expressPaymentMethods.d.ts +56 -0
- package/dist/v2/core/resources/expressPaymentMethods.js +27 -0
- package/dist/v2/core/resources/index.d.ts +7 -4
- package/dist/v2/core/resources/index.js +7 -4
- package/dist/v2/core/resources/shippingRates.d.ts +36 -0
- package/dist/v2/core/resources/shippingRates.js +23 -0
- package/dist/v2/core/resources/vipOffers.d.ts +37 -0
- package/dist/v2/core/resources/vipOffers.js +27 -0
- package/dist/v2/core/utils/order.d.ts +1 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +6 -6
- package/dist/v2/index.d.ts +12 -9
- package/dist/v2/index.js +3 -3
- package/dist/v2/react/components/ApplePayButton.d.ts +141 -0
- package/dist/v2/react/components/ApplePayButton.js +320 -0
- package/dist/v2/react/components/GooglePayButton.d.ts +19 -0
- package/dist/v2/react/components/GooglePayButton.js +355 -0
- package/dist/v2/react/hooks/useApiQuery.d.ts +4 -1
- package/dist/v2/react/hooks/useApiQuery.js +4 -1
- package/dist/v2/react/hooks/useDiscountsQuery.d.ts +30 -0
- package/dist/v2/react/hooks/useDiscountsQuery.js +175 -0
- package/dist/v2/react/hooks/useExpressPaymentMethods.d.ts +12 -0
- package/dist/v2/react/hooks/useExpressPaymentMethods.js +17 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +18 -2
- package/dist/v2/react/hooks/useShippingRatesQuery.d.ts +22 -0
- package/dist/v2/react/hooks/useShippingRatesQuery.js +134 -0
- package/dist/v2/react/hooks/useVipOffersQuery.d.ts +72 -0
- package/dist/v2/react/hooks/useVipOffersQuery.js +140 -0
- package/dist/v2/react/index.d.ts +30 -17
- package/dist/v2/react/index.js +18 -10
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +59 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +165 -0
- package/package.json +1 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Google Pay Button Component for v2 Architecture
|
|
4
|
+
* Uses v2 useExpressPaymentMethods hook and follows clean architecture principles
|
|
5
|
+
*/
|
|
6
|
+
import GooglePayButtonReact from '@google-pay/button-react';
|
|
7
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
8
|
+
import { useExpressPaymentMethods } from '../hooks/useExpressPaymentMethods';
|
|
9
|
+
const basistheoryPublicKey = process.env.NEXT_PUBLIC_BASIS_THEORY_PUBLIC_API_KEY || '';
|
|
10
|
+
const basistheoryTenantId = process.env.NEXT_PUBLIC_BASIS_THEORY_TENANT_ID || '0b283fa3-44a1-4535-adff-e99ad0a58a47';
|
|
11
|
+
export const GooglePayButton = ({ className = '', disabled = false, onSuccess, onError, onCancel, checkout, size = 'lg', buttonColor = 'black', buttonType = 'plain', }) => {
|
|
12
|
+
const { googlePayPaymentMethod, shippingMethods, lineItems, handleAddExpressId, updateCheckoutSessionValues, updateCustomerEmail, reComputeOrderSummary, setError: setContextError, } = useExpressPaymentMethods();
|
|
13
|
+
const [processingPayment, setProcessingPayment] = useState(false);
|
|
14
|
+
const [pendingPaymentData, setPendingPaymentData] = useState(null);
|
|
15
|
+
const [googlePayError, setGooglePayError] = useState(null);
|
|
16
|
+
// Don't render if no Google Pay payment method is enabled
|
|
17
|
+
if (!googlePayPaymentMethod) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// Register express payment method
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
handleAddExpressId('google_pay');
|
|
23
|
+
}, [handleAddExpressId]);
|
|
24
|
+
// Convert Google Pay address to internal Address format
|
|
25
|
+
const googlePayAddressToAddress = useCallback((googlePayAddress) => {
|
|
26
|
+
return {
|
|
27
|
+
address1: googlePayAddress.address1 || '',
|
|
28
|
+
address2: googlePayAddress.address2 || '',
|
|
29
|
+
lastName: googlePayAddress.name?.split(' ').slice(-1)[0] || '',
|
|
30
|
+
firstName: googlePayAddress.name?.split(' ').slice(0, -1).join(' ') || '',
|
|
31
|
+
city: googlePayAddress.locality || '',
|
|
32
|
+
state: googlePayAddress.administrativeArea || '',
|
|
33
|
+
country: googlePayAddress.countryCode || '',
|
|
34
|
+
postal: googlePayAddress.postalCode || '',
|
|
35
|
+
phone: googlePayAddress.phoneNumber || '',
|
|
36
|
+
email: '',
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
// Tokenize Google Pay data using Basis Theory
|
|
40
|
+
const tokenizeGooglePayTokenWithBasisTheory = useCallback(async (paymentData) => {
|
|
41
|
+
try {
|
|
42
|
+
const googlePayTokenString = paymentData.paymentMethodData.tokenizationData.token;
|
|
43
|
+
const googlePayToken = JSON.parse(googlePayTokenString);
|
|
44
|
+
const response = await fetch('https://api.basistheory.com/google-pay', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
'BT-API-KEY': basistheoryPublicKey,
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
google_payment_data: googlePayToken,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const errorData = await response.json().catch(() => ({}));
|
|
56
|
+
console.error('Basis Theory API error:', errorData);
|
|
57
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
58
|
+
}
|
|
59
|
+
const jsonResponse = await response.json();
|
|
60
|
+
return jsonResponse?.token_intent || jsonResponse?.google_pay;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error('Error tokenizing Google Pay data:', error);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}, []);
|
|
67
|
+
// Process Google Pay payment
|
|
68
|
+
const processGooglePayPayment = useCallback(async (token) => {
|
|
69
|
+
if (!checkout.checkoutSession.id) {
|
|
70
|
+
throw new Error('Checkout session ID is not available');
|
|
71
|
+
}
|
|
72
|
+
setProcessingPayment(true);
|
|
73
|
+
try {
|
|
74
|
+
// Process payment with backend API
|
|
75
|
+
const response = await fetch('/api/v1/google-pay/process-payment', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
checkoutSessionId: checkout.checkoutSession.id,
|
|
82
|
+
paymentToken: token,
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error('Failed to process Google Pay payment');
|
|
87
|
+
}
|
|
88
|
+
const paymentResult = await response.json();
|
|
89
|
+
if (onSuccess) {
|
|
90
|
+
onSuccess(paymentResult);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error('Error processing Google Pay payment:', error);
|
|
95
|
+
const errorMessage = error instanceof Error ? error.message : 'Google Pay payment failed';
|
|
96
|
+
setGooglePayError(errorMessage);
|
|
97
|
+
setContextError(errorMessage);
|
|
98
|
+
if (onError) {
|
|
99
|
+
onError(errorMessage);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
setProcessingPayment(false);
|
|
105
|
+
}
|
|
106
|
+
}, [checkout.checkoutSession.id, onSuccess, onError, setContextError]);
|
|
107
|
+
// Process payment data
|
|
108
|
+
const onGooglePaymentData = useCallback(async (paymentData) => {
|
|
109
|
+
setProcessingPayment(true);
|
|
110
|
+
try {
|
|
111
|
+
// Extract billing address if available
|
|
112
|
+
let billingAddress;
|
|
113
|
+
if (paymentData.paymentMethodData.info?.billingAddress) {
|
|
114
|
+
billingAddress = googlePayAddressToAddress(paymentData.paymentMethodData.info.billingAddress);
|
|
115
|
+
}
|
|
116
|
+
// Extract shipping address if available
|
|
117
|
+
let shippingAddress;
|
|
118
|
+
if (paymentData.shippingAddress) {
|
|
119
|
+
shippingAddress = googlePayAddressToAddress(paymentData.shippingAddress);
|
|
120
|
+
}
|
|
121
|
+
// Update checkout session with addresses before processing payment
|
|
122
|
+
if (shippingAddress) {
|
|
123
|
+
await updateCheckoutSessionValues({
|
|
124
|
+
data: {
|
|
125
|
+
shippingAddress,
|
|
126
|
+
billingAddress: billingAddress ?? null,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// Update customer email if provided
|
|
131
|
+
if (paymentData.email) {
|
|
132
|
+
await updateCustomerEmail({
|
|
133
|
+
data: {
|
|
134
|
+
email: paymentData.email,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const payToken = await tokenizeGooglePayTokenWithBasisTheory(paymentData);
|
|
139
|
+
await processGooglePayPayment(payToken);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error('Error processing Google Pay payment:', error);
|
|
143
|
+
const errorMessage = error instanceof Error ? error.message : 'Payment Failed';
|
|
144
|
+
setProcessingPayment(false);
|
|
145
|
+
setGooglePayError(errorMessage);
|
|
146
|
+
setContextError(errorMessage);
|
|
147
|
+
if (onError) {
|
|
148
|
+
onError(errorMessage);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [
|
|
152
|
+
googlePayAddressToAddress,
|
|
153
|
+
updateCheckoutSessionValues,
|
|
154
|
+
updateCustomerEmail,
|
|
155
|
+
tokenizeGooglePayTokenWithBasisTheory,
|
|
156
|
+
processGooglePayPayment,
|
|
157
|
+
onError,
|
|
158
|
+
setContextError,
|
|
159
|
+
]);
|
|
160
|
+
// Effect to process payment data in background
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (pendingPaymentData && !processingPayment) {
|
|
163
|
+
const processPaymentInBackground = async () => {
|
|
164
|
+
try {
|
|
165
|
+
await onGooglePaymentData(pendingPaymentData);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error('Background payment processing failed:', error);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
setPendingPaymentData(null);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
void processPaymentInBackground();
|
|
175
|
+
}
|
|
176
|
+
}, [pendingPaymentData, processingPayment, onGooglePaymentData]);
|
|
177
|
+
// Handle payment data changes during the flow
|
|
178
|
+
const handleGooglePayDataChanged = useCallback((intermediatePaymentData) => {
|
|
179
|
+
return new Promise((resolve) => {
|
|
180
|
+
const processCallback = async () => {
|
|
181
|
+
try {
|
|
182
|
+
const paymentDataRequestUpdate = {};
|
|
183
|
+
if (intermediatePaymentData.callbackTrigger === 'SHIPPING_ADDRESS') {
|
|
184
|
+
const address = intermediatePaymentData.shippingAddress;
|
|
185
|
+
const shippingAddress = {
|
|
186
|
+
address1: address?.addressLines?.[0] || '',
|
|
187
|
+
address2: address?.addressLines?.[1] || '',
|
|
188
|
+
lastName: address?.name?.split(' ').slice(-1)[0] || '',
|
|
189
|
+
firstName: address?.name?.split(' ').slice(0, -1).join(' ') || '',
|
|
190
|
+
city: address?.locality || '',
|
|
191
|
+
state: address?.administrativeArea || '',
|
|
192
|
+
country: address?.countryCode || '',
|
|
193
|
+
postal: address?.postalCode || '',
|
|
194
|
+
phone: address?.phoneNumber || '',
|
|
195
|
+
email: '',
|
|
196
|
+
};
|
|
197
|
+
await updateCheckoutSessionValues({
|
|
198
|
+
data: { shippingAddress },
|
|
199
|
+
});
|
|
200
|
+
const newOrderSummary = await reComputeOrderSummary();
|
|
201
|
+
if (newOrderSummary) {
|
|
202
|
+
paymentDataRequestUpdate.newShippingOptionParameters = {
|
|
203
|
+
defaultSelectedOptionId: newOrderSummary.shippingMethods[0]?.identifier || '',
|
|
204
|
+
shippingOptions: newOrderSummary.shippingMethods.map((method) => ({
|
|
205
|
+
id: method.identifier,
|
|
206
|
+
label: method.label,
|
|
207
|
+
description: method.amount + ': ' + method.detail || '',
|
|
208
|
+
})),
|
|
209
|
+
};
|
|
210
|
+
paymentDataRequestUpdate.newTransactionInfo = {
|
|
211
|
+
totalPriceStatus: 'FINAL',
|
|
212
|
+
totalPrice: newOrderSummary.total.amount,
|
|
213
|
+
currencyCode: checkout.summary?.currency || 'USD',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (intermediatePaymentData.callbackTrigger === 'SHIPPING_OPTION') {
|
|
218
|
+
// Update shipping rate
|
|
219
|
+
if (intermediatePaymentData.shippingOptionData?.id) {
|
|
220
|
+
const response = await fetch('/api/v1/shipping-rates/select', {
|
|
221
|
+
method: 'POST',
|
|
222
|
+
headers: {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
},
|
|
225
|
+
body: JSON.stringify({
|
|
226
|
+
checkoutSessionId: checkout.checkoutSession.id,
|
|
227
|
+
shippingRateId: intermediatePaymentData.shippingOptionData.id,
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
if (response.ok) {
|
|
231
|
+
const newOrderSummary = await reComputeOrderSummary();
|
|
232
|
+
if (newOrderSummary) {
|
|
233
|
+
paymentDataRequestUpdate.newTransactionInfo = {
|
|
234
|
+
totalPriceStatus: 'FINAL',
|
|
235
|
+
totalPrice: newOrderSummary.total.amount,
|
|
236
|
+
currencyCode: checkout.summary?.currency || 'USD',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else if (intermediatePaymentData.callbackTrigger === 'OFFER') {
|
|
243
|
+
console.log('OFFER callback triggered, no action needed');
|
|
244
|
+
}
|
|
245
|
+
else if (intermediatePaymentData.callbackTrigger === 'INITIALIZE') {
|
|
246
|
+
console.log('INITIALIZE callback triggered, no action needed');
|
|
247
|
+
}
|
|
248
|
+
resolve(paymentDataRequestUpdate);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.error('Error in onPaymentDataChanged:', error);
|
|
252
|
+
resolve({
|
|
253
|
+
error: {
|
|
254
|
+
reason: 'SHIPPING_ADDRESS_UNSERVICEABLE',
|
|
255
|
+
message: 'Unable to calculate shipping for this address',
|
|
256
|
+
intent: intermediatePaymentData.callbackTrigger,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
void processCallback();
|
|
262
|
+
});
|
|
263
|
+
}, [updateCheckoutSessionValues, reComputeOrderSummary, checkout]);
|
|
264
|
+
// Handle payment authorization
|
|
265
|
+
const handleGooglePayAuthorized = useCallback((paymentData) => {
|
|
266
|
+
setPendingPaymentData(paymentData);
|
|
267
|
+
return Promise.resolve({ transactionState: 'SUCCESS' });
|
|
268
|
+
}, []);
|
|
269
|
+
// Don't render if no order summary
|
|
270
|
+
if (!checkout.summary) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
const minorUnitsToCurrencyString = (amountMinor, currency) => {
|
|
274
|
+
return (amountMinor / 100).toFixed(2);
|
|
275
|
+
};
|
|
276
|
+
const allowedCardNetworks = ['AMEX', 'DISCOVER', 'INTERAC', 'JCB', 'MASTERCARD', 'VISA'];
|
|
277
|
+
const allowedCardAuthMethods = ['PAN_ONLY', 'CRYPTOGRAM_3DS'];
|
|
278
|
+
const baseCardPaymentMethod = {
|
|
279
|
+
type: 'CARD',
|
|
280
|
+
parameters: {
|
|
281
|
+
allowedAuthMethods: allowedCardAuthMethods,
|
|
282
|
+
allowedCardNetworks: allowedCardNetworks,
|
|
283
|
+
billingAddressRequired: true,
|
|
284
|
+
billingAddressParameters: {
|
|
285
|
+
format: 'FULL',
|
|
286
|
+
phoneNumberRequired: true,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
const tokenizationSpecification = {
|
|
291
|
+
type: 'PAYMENT_GATEWAY',
|
|
292
|
+
parameters: {
|
|
293
|
+
gateway: 'basistheory',
|
|
294
|
+
gatewayMerchantId: basistheoryTenantId,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
const paymentRequest = {
|
|
298
|
+
apiVersion: 2,
|
|
299
|
+
apiVersionMinor: 0,
|
|
300
|
+
allowedPaymentMethods: [
|
|
301
|
+
{
|
|
302
|
+
...baseCardPaymentMethod,
|
|
303
|
+
tokenizationSpecification,
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
transactionInfo: {
|
|
307
|
+
totalPriceStatus: 'FINAL',
|
|
308
|
+
totalPrice: minorUnitsToCurrencyString(checkout.summary.totalAdjustedAmount, checkout.summary.currency),
|
|
309
|
+
currencyCode: checkout.summary.currency,
|
|
310
|
+
},
|
|
311
|
+
merchantInfo: {
|
|
312
|
+
merchantName: googlePayPaymentMethod?.metadata?.merchantName ||
|
|
313
|
+
checkout.checkoutSession?.store?.name ||
|
|
314
|
+
'Store',
|
|
315
|
+
merchantId: googlePayPaymentMethod?.metadata?.sandboxed
|
|
316
|
+
? '12345678901234567890'
|
|
317
|
+
: googlePayPaymentMethod?.metadata?.merchantId || '12345678901234567890',
|
|
318
|
+
},
|
|
319
|
+
shippingAddressRequired: true,
|
|
320
|
+
shippingOptionRequired: true,
|
|
321
|
+
emailRequired: true,
|
|
322
|
+
callbackIntents: ['SHIPPING_OPTION', 'SHIPPING_ADDRESS', 'PAYMENT_AUTHORIZATION'],
|
|
323
|
+
shippingOptionParameters: {
|
|
324
|
+
defaultSelectedOptionId: shippingMethods.length > 0 ? shippingMethods[0].identifier : '',
|
|
325
|
+
shippingOptions: shippingMethods.map((method) => ({
|
|
326
|
+
id: method.identifier,
|
|
327
|
+
label: method.label,
|
|
328
|
+
description: method.amount + ': ' + method.detail || '',
|
|
329
|
+
})),
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
const environment = googlePayPaymentMethod?.metadata?.sandboxed ? 'TEST' : 'PRODUCTION';
|
|
333
|
+
// Button size classes
|
|
334
|
+
const sizeClasses = {
|
|
335
|
+
sm: 'h-8',
|
|
336
|
+
md: 'h-10',
|
|
337
|
+
lg: 'h-12',
|
|
338
|
+
};
|
|
339
|
+
const Button = ({ children }) => (_jsx("button", { type: "button", disabled: true, className: `w-full rounded-md bg-black text-base text-white shadow-sm hover:bg-black/80 disabled:cursor-not-allowed disabled:opacity-50 ${sizeClasses[size]} `, children: _jsxs("div", { className: "flex items-center justify-center gap-3", children: [_jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent" }), children] }) }));
|
|
340
|
+
return (_jsxs("div", { className: "w-full", children: [!processingPayment ? (_jsx(GooglePayButtonReact, { environment: environment, paymentRequest: paymentRequest, onPaymentAuthorized: handleGooglePayAuthorized, onPaymentDataChanged: handleGooglePayDataChanged, onError: (error) => {
|
|
341
|
+
console.error('Google Pay error:', error);
|
|
342
|
+
const errorMessage = 'Google Pay error: ' + error.statusMessage;
|
|
343
|
+
setGooglePayError(errorMessage);
|
|
344
|
+
setContextError(errorMessage);
|
|
345
|
+
if (onError) {
|
|
346
|
+
onError(errorMessage);
|
|
347
|
+
}
|
|
348
|
+
}, onCancel: () => {
|
|
349
|
+
console.log('Google Pay payment cancelled');
|
|
350
|
+
if (onCancel) {
|
|
351
|
+
onCancel();
|
|
352
|
+
}
|
|
353
|
+
}, existingPaymentMethodRequired: false, buttonColor: buttonColor, buttonType: buttonType, buttonSizeMode: "fill", buttonLocale: "en", className: `m-0 w-full rounded-sm p-0 text-base text-white ${sizeClasses[size]} ${className}` })) : (_jsx(Button, { children: _jsx("span", { className: "font-medium", children: "Processing..." }) })), googlePayError && (_jsx("div", { className: "mt-2 rounded border border-red-200 bg-red-50 p-2 text-sm text-red-600", children: googlePayError }))] }));
|
|
354
|
+
};
|
|
355
|
+
export default GooglePayButton;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* API Query Hook using TanStack Query + Axios
|
|
3
3
|
* Facade pattern for React SDK
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';
|
|
6
6
|
import { ApiClient } from '../../core/resources/apiClient';
|
|
7
7
|
export declare function setGlobalApiClient(client: ApiClient): void;
|
|
8
8
|
export declare function getGlobalApiClient(): ApiClient;
|
|
@@ -10,7 +10,10 @@ export declare function getGlobalApiClientOrNull(): ApiClient | null;
|
|
|
10
10
|
export declare const queryKeys: {
|
|
11
11
|
readonly checkout: (token: string, currency?: string) => (string | undefined)[];
|
|
12
12
|
readonly promotions: (sessionId: string) => string[];
|
|
13
|
+
readonly discounts: (sessionId: string) => string[];
|
|
13
14
|
readonly orderBumps: (sessionId: string) => string[];
|
|
15
|
+
readonly shippingRates: (sessionId: string) => string[];
|
|
16
|
+
readonly vipPreview: (sessionId: string, offerIds: string[]) => (string | string[])[];
|
|
14
17
|
readonly products: (productIds: string[], options?: Record<string, any>) => (string | string[] | Record<string, any> | undefined)[];
|
|
15
18
|
readonly store: (storeId: string) => string[];
|
|
16
19
|
readonly order: (orderId: string) => string[];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* API Query Hook using TanStack Query + Axios
|
|
3
3
|
* Facade pattern for React SDK
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
6
|
// Global API client instance
|
|
7
7
|
let globalApiClient = null;
|
|
8
8
|
export function setGlobalApiClient(client) {
|
|
@@ -21,7 +21,10 @@ export function getGlobalApiClientOrNull() {
|
|
|
21
21
|
export const queryKeys = {
|
|
22
22
|
checkout: (token, currency) => ['checkout', token, currency].filter(Boolean),
|
|
23
23
|
promotions: (sessionId) => ['promotions', sessionId],
|
|
24
|
+
discounts: (sessionId) => ['discounts', sessionId],
|
|
24
25
|
orderBumps: (sessionId) => ['order-bumps', sessionId],
|
|
26
|
+
shippingRates: (sessionId) => ['shipping-rates', sessionId],
|
|
27
|
+
vipPreview: (sessionId, offerIds) => ['vip-preview', sessionId, offerIds],
|
|
25
28
|
products: (productIds, options) => ['products', productIds, options],
|
|
26
29
|
store: (storeId) => ['store', storeId],
|
|
27
30
|
order: (orderId) => ['order', orderId],
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discounts Hook using TanStack Query
|
|
3
|
+
* Simplified discount code management with automatic cache invalidation
|
|
4
|
+
*/
|
|
5
|
+
import { Discount } from '../../core/resources/discounts';
|
|
6
|
+
export interface UseDiscountsQueryOptions {
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
onApplySuccess?: (discount?: Discount) => void;
|
|
10
|
+
onRemoveSuccess?: () => void;
|
|
11
|
+
}
|
|
12
|
+
export interface UseDiscountsQueryResult {
|
|
13
|
+
appliedDiscounts: Discount[] | undefined;
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
isApplying: boolean;
|
|
16
|
+
isRemoving: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
applyDiscountCode: (code: string) => Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
discount?: Discount;
|
|
22
|
+
}>;
|
|
23
|
+
removeDiscount: (discountId: string) => Promise<{
|
|
24
|
+
success: boolean;
|
|
25
|
+
error?: string;
|
|
26
|
+
}>;
|
|
27
|
+
refresh: () => Promise<void>;
|
|
28
|
+
clearError: () => void;
|
|
29
|
+
}
|
|
30
|
+
export declare function useDiscountsQuery(options?: UseDiscountsQueryOptions): UseDiscountsQueryResult;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discounts Hook using TanStack Query
|
|
3
|
+
* Simplified discount code management with automatic cache invalidation
|
|
4
|
+
*/
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
|
+
import { useCallback, useMemo } from 'react';
|
|
7
|
+
import { DiscountsResource } from '../../core/resources/discounts';
|
|
8
|
+
import { getGlobalApiClient } from './useApiQuery';
|
|
9
|
+
export function useDiscountsQuery(options = {}) {
|
|
10
|
+
const { sessionId, enabled = true, onApplySuccess, onRemoveSuccess } = options;
|
|
11
|
+
const queryClient = useQueryClient();
|
|
12
|
+
// Create discounts resource client
|
|
13
|
+
const discountsResource = useMemo(() => {
|
|
14
|
+
try {
|
|
15
|
+
return new DiscountsResource(getGlobalApiClient());
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error('Failed to initialize discounts resource: ' +
|
|
19
|
+
(error instanceof Error ? error.message : 'Unknown error'));
|
|
20
|
+
}
|
|
21
|
+
}, []);
|
|
22
|
+
// Main discounts query
|
|
23
|
+
const { data: appliedDiscounts, isLoading: isFetching, error: fetchError, refetch: refetchDiscounts, } = useQuery({
|
|
24
|
+
queryKey: ['discounts', sessionId],
|
|
25
|
+
queryFn: () => discountsResource.getAppliedDiscounts(sessionId),
|
|
26
|
+
enabled: enabled && !!sessionId,
|
|
27
|
+
staleTime: 30000, // 30 seconds
|
|
28
|
+
refetchOnWindowFocus: false,
|
|
29
|
+
});
|
|
30
|
+
// Apply discount code mutation
|
|
31
|
+
const applyMutation = useMutation({
|
|
32
|
+
mutationFn: ({ code }) => {
|
|
33
|
+
if (!sessionId) {
|
|
34
|
+
throw new Error('No checkout session available');
|
|
35
|
+
}
|
|
36
|
+
if (!code || code.trim() === '') {
|
|
37
|
+
throw new Error('Discount code is required');
|
|
38
|
+
}
|
|
39
|
+
return discountsResource.applyDiscountCode(sessionId, code);
|
|
40
|
+
},
|
|
41
|
+
onSuccess: async (response) => {
|
|
42
|
+
if (response.success) {
|
|
43
|
+
// Invalidate both discounts and checkout queries
|
|
44
|
+
if (sessionId) {
|
|
45
|
+
await Promise.all([
|
|
46
|
+
queryClient.invalidateQueries({ queryKey: ['discounts', sessionId] }),
|
|
47
|
+
queryClient.invalidateQueries({ queryKey: ['checkout'] }),
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
// Call onSuccess callback if provided
|
|
51
|
+
if (onApplySuccess) {
|
|
52
|
+
onApplySuccess(response.promotion);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
// Remove discount mutation
|
|
58
|
+
const removeMutation = useMutation({
|
|
59
|
+
mutationFn: ({ discountId }) => {
|
|
60
|
+
if (!sessionId) {
|
|
61
|
+
throw new Error('No checkout session available');
|
|
62
|
+
}
|
|
63
|
+
if (!discountId) {
|
|
64
|
+
throw new Error('Discount ID is required');
|
|
65
|
+
}
|
|
66
|
+
return discountsResource.removeDiscount(sessionId, discountId);
|
|
67
|
+
},
|
|
68
|
+
onSuccess: async (response) => {
|
|
69
|
+
if (response.success) {
|
|
70
|
+
// Invalidate both discounts and checkout queries
|
|
71
|
+
if (sessionId) {
|
|
72
|
+
await Promise.all([
|
|
73
|
+
queryClient.invalidateQueries({ queryKey: ['discounts', sessionId] }),
|
|
74
|
+
queryClient.invalidateQueries({ queryKey: ['checkout'] }),
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
// Call onSuccess callback if provided
|
|
78
|
+
if (onRemoveSuccess) {
|
|
79
|
+
onRemoveSuccess();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
// Apply discount code function
|
|
85
|
+
const applyDiscountCode = useCallback(async (code) => {
|
|
86
|
+
try {
|
|
87
|
+
const response = await applyMutation.mutateAsync({ code });
|
|
88
|
+
if (response.success) {
|
|
89
|
+
return { success: true, discount: response.promotion };
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
error: response.error?.message || 'Failed to apply discount code',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// Extract error message from API response
|
|
100
|
+
let errorMessage = 'Failed to apply discount code';
|
|
101
|
+
if (err && typeof err === 'object') {
|
|
102
|
+
const error = err;
|
|
103
|
+
if (error.response?.data?.error?.message) {
|
|
104
|
+
errorMessage = error.response.data.error.message;
|
|
105
|
+
}
|
|
106
|
+
else if (error.response?.data?.message) {
|
|
107
|
+
errorMessage = error.response.data.message;
|
|
108
|
+
}
|
|
109
|
+
else if (error.message) {
|
|
110
|
+
errorMessage = error.message;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { success: false, error: errorMessage };
|
|
114
|
+
}
|
|
115
|
+
}, [applyMutation]);
|
|
116
|
+
// Remove discount function
|
|
117
|
+
const removeDiscount = useCallback(async (discountId) => {
|
|
118
|
+
try {
|
|
119
|
+
const response = await removeMutation.mutateAsync({ discountId });
|
|
120
|
+
if (response.success) {
|
|
121
|
+
return { success: true };
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: response.error?.message || 'Failed to remove discount',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
// Extract error message from API response
|
|
132
|
+
let errorMessage = 'Failed to remove discount';
|
|
133
|
+
if (err && typeof err === 'object') {
|
|
134
|
+
const error = err;
|
|
135
|
+
if (error.response?.data?.error?.message) {
|
|
136
|
+
errorMessage = error.response.data.error.message;
|
|
137
|
+
}
|
|
138
|
+
else if (error.response?.data?.message) {
|
|
139
|
+
errorMessage = error.response.data.message;
|
|
140
|
+
}
|
|
141
|
+
else if (error.message) {
|
|
142
|
+
errorMessage = error.message;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { success: false, error: errorMessage };
|
|
146
|
+
}
|
|
147
|
+
}, [removeMutation]);
|
|
148
|
+
// Refetch function
|
|
149
|
+
const refresh = useCallback(async () => {
|
|
150
|
+
await refetchDiscounts();
|
|
151
|
+
}, [refetchDiscounts]);
|
|
152
|
+
// Clear error function
|
|
153
|
+
const clearError = useCallback(() => {
|
|
154
|
+
// TanStack Query doesn't provide a direct way to clear errors
|
|
155
|
+
// We can trigger a refetch which will clear the error
|
|
156
|
+
void refresh();
|
|
157
|
+
}, [refresh]);
|
|
158
|
+
// Combine loading states
|
|
159
|
+
const isLoading = isFetching;
|
|
160
|
+
const isApplying = applyMutation.isPending;
|
|
161
|
+
const isRemoving = removeMutation.isPending;
|
|
162
|
+
// Combine errors
|
|
163
|
+
const error = (fetchError || applyMutation.error || removeMutation.error);
|
|
164
|
+
return {
|
|
165
|
+
appliedDiscounts,
|
|
166
|
+
isLoading,
|
|
167
|
+
isApplying,
|
|
168
|
+
isRemoving,
|
|
169
|
+
error,
|
|
170
|
+
applyDiscountCode,
|
|
171
|
+
removeDiscount,
|
|
172
|
+
refresh,
|
|
173
|
+
clearError,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express Payment Methods Hook for v2 Architecture
|
|
3
|
+
* Uses the ExpressPaymentMethodsProvider context
|
|
4
|
+
*/
|
|
5
|
+
import { ExpressPaymentMethodsContextType } from '../providers/ExpressPaymentMethodsProvider';
|
|
6
|
+
/**
|
|
7
|
+
* Hook to access Express Payment Methods context
|
|
8
|
+
* Must be used within an ExpressPaymentMethodsProvider
|
|
9
|
+
*/
|
|
10
|
+
export declare const useExpressPaymentMethods: () => ExpressPaymentMethodsContextType;
|
|
11
|
+
export type { ExpressPaymentMethodsContextType, ExpressPaymentMethodsProviderProps } from '../providers/ExpressPaymentMethodsProvider';
|
|
12
|
+
export type { Address, PaymentMethod } from '../../core/resources/expressPaymentMethods';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express Payment Methods Hook for v2 Architecture
|
|
3
|
+
* Uses the ExpressPaymentMethodsProvider context
|
|
4
|
+
*/
|
|
5
|
+
import { useContext } from 'react';
|
|
6
|
+
import { ExpressPaymentMethodsContext } from '../providers/ExpressPaymentMethodsProvider';
|
|
7
|
+
/**
|
|
8
|
+
* Hook to access Express Payment Methods context
|
|
9
|
+
* Must be used within an ExpressPaymentMethodsProvider
|
|
10
|
+
*/
|
|
11
|
+
export const useExpressPaymentMethods = () => {
|
|
12
|
+
const context = useContext(ExpressPaymentMethodsContext);
|
|
13
|
+
if (context === undefined) {
|
|
14
|
+
throw new Error('useExpressPaymentMethods must be used within an ExpressPaymentMethodsProvider');
|
|
15
|
+
}
|
|
16
|
+
return context;
|
|
17
|
+
};
|
|
@@ -48,6 +48,8 @@ export interface ExtractedAddress {
|
|
|
48
48
|
locality: string;
|
|
49
49
|
administrativeAreaLevel1: string;
|
|
50
50
|
administrativeAreaLevel1Long: string;
|
|
51
|
+
administrativeAreaLevel2: string;
|
|
52
|
+
administrativeAreaLevel2Long: string;
|
|
51
53
|
country: string;
|
|
52
54
|
postalCode: string;
|
|
53
55
|
}
|