@tagadapay/plugin-sdk 2.6.2 → 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/README.md +1090 -623
- 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/{useExpressPayment.d.ts → useExpressPaymentMethods.d.ts} +13 -10
- package/dist/react/hooks/{useExpressPayment.js → useExpressPaymentMethods.js} +17 -8
- 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 +9 -7
- package/dist/react/index.js +8 -5
- package/dist/react/providers/TagadaProvider.js +5 -5
- 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 +23 -4
- 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/dist/v2/react/providers/TagadaProvider.js +5 -5
- package/package.json +90 -90
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { CheckoutData } from '../hooks/useCheckout';
|
|
3
|
+
interface GooglePayButtonProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
onSuccess?: (payment: any) => void;
|
|
7
|
+
onError?: (error: string) => void;
|
|
8
|
+
onCancel?: () => void;
|
|
9
|
+
checkout: CheckoutData | null;
|
|
10
|
+
}
|
|
11
|
+
export declare const GooglePayButton: React.FC<GooglePayButtonProps>;
|
|
12
|
+
export default GooglePayButton;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import GooglePayButtonReact from '@google-pay/button-react';
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useExpressPaymentMethods } from '../hooks/useExpressPaymentMethods';
|
|
5
|
+
import { useOrderSummary } from '../hooks/useOrderSummary';
|
|
6
|
+
import { usePayment } from '../hooks/usePayment';
|
|
7
|
+
import { useShippingRates } from '../hooks/useShippingRates';
|
|
8
|
+
import { Button } from './Button';
|
|
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, }) => {
|
|
12
|
+
const { googlePayPaymentMethod, reComputeOrderSummary, handleAddExpressId, updateCheckoutSessionValues, updateCustomerEmail, shippingMethods, setError: setContextError, } = useExpressPaymentMethods();
|
|
13
|
+
const [processingPayment, setProcessingPayment] = useState(false);
|
|
14
|
+
const [pendingPaymentData, setPendingPaymentData] = useState(null);
|
|
15
|
+
const [googlePayError, setGooglePayError] = useState(null);
|
|
16
|
+
const checkoutSessionId = checkout?.checkoutSession?.id;
|
|
17
|
+
const { processApplePayPayment: processExpressPayment } = usePayment(); // Will create processGooglePayPayment method
|
|
18
|
+
const { orderSummary } = useOrderSummary({ sessionId: checkoutSessionId });
|
|
19
|
+
const { selectRate } = useShippingRates({ checkout: checkout || undefined });
|
|
20
|
+
// Don't render if no Google Pay payment method is enabled
|
|
21
|
+
if (!googlePayPaymentMethod) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
// Register express payment method
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
handleAddExpressId('google_pay');
|
|
27
|
+
}, [handleAddExpressId]);
|
|
28
|
+
// Convert Google Pay address to internal Address format
|
|
29
|
+
const googlePayAddressToAddress = useCallback((googlePayAddress) => {
|
|
30
|
+
return {
|
|
31
|
+
address1: googlePayAddress.address1 || '',
|
|
32
|
+
address2: googlePayAddress.address2 || '',
|
|
33
|
+
lastName: googlePayAddress.name?.split(' ').slice(-1)[0] || '',
|
|
34
|
+
firstName: googlePayAddress.name?.split(' ').slice(0, -1).join(' ') || '',
|
|
35
|
+
city: googlePayAddress.locality || '',
|
|
36
|
+
state: googlePayAddress.administrativeArea || '',
|
|
37
|
+
country: googlePayAddress.countryCode || '',
|
|
38
|
+
postal: googlePayAddress.postalCode || '',
|
|
39
|
+
phone: googlePayAddress.phoneNumber || '',
|
|
40
|
+
email: '',
|
|
41
|
+
};
|
|
42
|
+
}, []);
|
|
43
|
+
// Tokenize Google Pay data using Basis Theory
|
|
44
|
+
const tokenizeGooglePayTokenWithBasisTheory = useCallback(async (paymentData) => {
|
|
45
|
+
try {
|
|
46
|
+
const googlePayTokenString = paymentData.paymentMethodData.tokenizationData.token;
|
|
47
|
+
const googlePayToken = JSON.parse(googlePayTokenString);
|
|
48
|
+
const response = await fetch('https://api.basistheory.com/google-pay', {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
'BT-API-KEY': basistheoryPublicKey,
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
google_payment_data: googlePayToken,
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorData = await response.json().catch(() => ({}));
|
|
60
|
+
console.error('Basis Theory API error:', errorData);
|
|
61
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
62
|
+
}
|
|
63
|
+
const jsonResponse = await response.json();
|
|
64
|
+
return jsonResponse?.token_intent || jsonResponse?.google_pay;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error('Error tokenizing Google Pay data:', error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
// Charge payment in backend
|
|
72
|
+
const chargePayment = useCallback(async (token) => {
|
|
73
|
+
if (!checkoutSessionId) {
|
|
74
|
+
throw new Error('Checkout session ID is not available');
|
|
75
|
+
}
|
|
76
|
+
setProcessingPayment(true);
|
|
77
|
+
try {
|
|
78
|
+
// Use the payment hook to process the Google Pay payment
|
|
79
|
+
// Note: You'll need to implement processGooglePayPayment similar to processApplePayPayment
|
|
80
|
+
await processExpressPayment(checkoutSessionId, token, {
|
|
81
|
+
onSuccess: (payment) => {
|
|
82
|
+
setProcessingPayment(false);
|
|
83
|
+
if (onSuccess) {
|
|
84
|
+
onSuccess(payment);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
onFailure: (error) => {
|
|
88
|
+
setProcessingPayment(false);
|
|
89
|
+
const errorMessage = error || 'Payment Failed';
|
|
90
|
+
setGooglePayError(errorMessage);
|
|
91
|
+
setContextError(errorMessage);
|
|
92
|
+
if (onError) {
|
|
93
|
+
onError(errorMessage);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Error charging payment:', error);
|
|
100
|
+
const errorMessage = error instanceof Error ? error.message : 'Payment Failed';
|
|
101
|
+
setProcessingPayment(false);
|
|
102
|
+
setGooglePayError(errorMessage);
|
|
103
|
+
setContextError(errorMessage);
|
|
104
|
+
if (onError) {
|
|
105
|
+
onError(errorMessage);
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}, [checkoutSessionId, processExpressPayment, onSuccess, onError, setContextError]);
|
|
110
|
+
// Process payment data
|
|
111
|
+
const onGooglePaymentData = useCallback(async (paymentData) => {
|
|
112
|
+
setProcessingPayment(true);
|
|
113
|
+
try {
|
|
114
|
+
// Extract billing address if available
|
|
115
|
+
let billingAddress;
|
|
116
|
+
if (paymentData.paymentMethodData.info?.billingAddress) {
|
|
117
|
+
billingAddress = googlePayAddressToAddress(paymentData.paymentMethodData.info.billingAddress);
|
|
118
|
+
}
|
|
119
|
+
// Extract shipping address if available
|
|
120
|
+
let shippingAddress;
|
|
121
|
+
if (paymentData.shippingAddress) {
|
|
122
|
+
shippingAddress = googlePayAddressToAddress(paymentData.shippingAddress);
|
|
123
|
+
}
|
|
124
|
+
// Update checkout session with addresses before processing payment
|
|
125
|
+
if (shippingAddress) {
|
|
126
|
+
await updateCheckoutSessionValues({
|
|
127
|
+
data: {
|
|
128
|
+
shippingAddress,
|
|
129
|
+
billingAddress: billingAddress ?? null,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// Update customer email if provided
|
|
134
|
+
if (paymentData.email) {
|
|
135
|
+
await updateCustomerEmail({
|
|
136
|
+
data: {
|
|
137
|
+
email: paymentData.email,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const payToken = await tokenizeGooglePayTokenWithBasisTheory(paymentData);
|
|
142
|
+
await chargePayment(payToken);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error('Error processing Google Pay payment:', error);
|
|
146
|
+
const errorMessage = error instanceof Error ? error.message : 'Payment Failed';
|
|
147
|
+
setProcessingPayment(false);
|
|
148
|
+
setGooglePayError(errorMessage);
|
|
149
|
+
setContextError(errorMessage);
|
|
150
|
+
if (onError) {
|
|
151
|
+
onError(errorMessage);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}, [
|
|
155
|
+
googlePayAddressToAddress,
|
|
156
|
+
updateCheckoutSessionValues,
|
|
157
|
+
updateCustomerEmail,
|
|
158
|
+
tokenizeGooglePayTokenWithBasisTheory,
|
|
159
|
+
chargePayment,
|
|
160
|
+
onError,
|
|
161
|
+
setContextError,
|
|
162
|
+
]);
|
|
163
|
+
// Effect to process payment data in background
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (pendingPaymentData && !processingPayment) {
|
|
166
|
+
const processPaymentInBackground = async () => {
|
|
167
|
+
try {
|
|
168
|
+
await onGooglePaymentData(pendingPaymentData);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error('Background payment processing failed:', error);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
setPendingPaymentData(null);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
void processPaymentInBackground();
|
|
178
|
+
}
|
|
179
|
+
}, [pendingPaymentData, processingPayment, onGooglePaymentData]);
|
|
180
|
+
// Handle payment data changes during the flow
|
|
181
|
+
const handleGooglePayDataChanged = useCallback((intermediatePaymentData) => {
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
const processCallback = async () => {
|
|
184
|
+
try {
|
|
185
|
+
const paymentDataRequestUpdate = {};
|
|
186
|
+
if (intermediatePaymentData.callbackTrigger === 'SHIPPING_ADDRESS') {
|
|
187
|
+
const address = intermediatePaymentData.shippingAddress;
|
|
188
|
+
const shippingAddress = {
|
|
189
|
+
address1: address?.addressLines?.[0] || '',
|
|
190
|
+
address2: address?.addressLines?.[1] || '',
|
|
191
|
+
lastName: address?.name?.split(' ').slice(-1)[0] || '',
|
|
192
|
+
firstName: address?.name?.split(' ').slice(0, -1).join(' ') || '',
|
|
193
|
+
city: address?.locality || '',
|
|
194
|
+
state: address?.administrativeArea || '',
|
|
195
|
+
country: address?.countryCode || '',
|
|
196
|
+
postal: address?.postalCode || '',
|
|
197
|
+
phone: address?.phoneNumber || '',
|
|
198
|
+
email: '',
|
|
199
|
+
};
|
|
200
|
+
await updateCheckoutSessionValues({
|
|
201
|
+
data: { shippingAddress },
|
|
202
|
+
});
|
|
203
|
+
const newOrderSummary = await reComputeOrderSummary();
|
|
204
|
+
if (newOrderSummary) {
|
|
205
|
+
paymentDataRequestUpdate.newShippingOptionParameters = {
|
|
206
|
+
defaultSelectedOptionId: newOrderSummary.shippingMethods[0]?.identifier || '',
|
|
207
|
+
shippingOptions: newOrderSummary.shippingMethods.map((method) => ({
|
|
208
|
+
id: method.identifier,
|
|
209
|
+
label: method.label,
|
|
210
|
+
description: method.amount + ': ' + method.detail || '',
|
|
211
|
+
})),
|
|
212
|
+
};
|
|
213
|
+
paymentDataRequestUpdate.newTransactionInfo = {
|
|
214
|
+
totalPriceStatus: 'FINAL',
|
|
215
|
+
totalPrice: newOrderSummary.total.amount,
|
|
216
|
+
currencyCode: orderSummary?.currency || 'USD',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if (intermediatePaymentData.callbackTrigger === 'SHIPPING_OPTION') {
|
|
221
|
+
// Update shipping rate
|
|
222
|
+
if (selectRate) {
|
|
223
|
+
await selectRate(intermediatePaymentData.shippingOptionData.id);
|
|
224
|
+
}
|
|
225
|
+
const newOrderSummary = await reComputeOrderSummary();
|
|
226
|
+
if (newOrderSummary) {
|
|
227
|
+
paymentDataRequestUpdate.newTransactionInfo = {
|
|
228
|
+
totalPriceStatus: 'FINAL',
|
|
229
|
+
totalPrice: newOrderSummary.total.amount,
|
|
230
|
+
currencyCode: orderSummary?.currency || 'USD',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (intermediatePaymentData.callbackTrigger === 'OFFER') {
|
|
235
|
+
console.log('OFFER callback triggered, no action needed');
|
|
236
|
+
}
|
|
237
|
+
else if (intermediatePaymentData.callbackTrigger === 'INITIALIZE') {
|
|
238
|
+
console.log('INITIALIZE callback triggered, no action needed');
|
|
239
|
+
}
|
|
240
|
+
resolve(paymentDataRequestUpdate);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
console.error('Error in onPaymentDataChanged:', error);
|
|
244
|
+
resolve({
|
|
245
|
+
error: {
|
|
246
|
+
reason: 'SHIPPING_ADDRESS_UNSERVICEABLE',
|
|
247
|
+
message: 'Unable to calculate shipping for this address',
|
|
248
|
+
intent: intermediatePaymentData.callbackTrigger,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
void processCallback();
|
|
254
|
+
});
|
|
255
|
+
}, [updateCheckoutSessionValues, reComputeOrderSummary, selectRate, orderSummary]);
|
|
256
|
+
// Handle payment authorization
|
|
257
|
+
const handleGooglePayAuthorized = useCallback((paymentData) => {
|
|
258
|
+
setPendingPaymentData(paymentData);
|
|
259
|
+
return Promise.resolve({ transactionState: 'SUCCESS' });
|
|
260
|
+
}, []);
|
|
261
|
+
// Don't render if no order summary
|
|
262
|
+
if (!orderSummary) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const minorUnitsToCurrencyString = (amountMinor, currency) => {
|
|
266
|
+
return (amountMinor / 100).toFixed(2);
|
|
267
|
+
};
|
|
268
|
+
const allowedCardNetworks = ['AMEX', 'DISCOVER', 'INTERAC', 'JCB', 'MASTERCARD', 'VISA'];
|
|
269
|
+
const allowedCardAuthMethods = ['PAN_ONLY', 'CRYPTOGRAM_3DS'];
|
|
270
|
+
const baseCardPaymentMethod = {
|
|
271
|
+
type: 'CARD',
|
|
272
|
+
parameters: {
|
|
273
|
+
allowedAuthMethods: allowedCardAuthMethods,
|
|
274
|
+
allowedCardNetworks: allowedCardNetworks,
|
|
275
|
+
billingAddressRequired: true,
|
|
276
|
+
billingAddressParameters: {
|
|
277
|
+
format: 'FULL',
|
|
278
|
+
phoneNumberRequired: true,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
const tokenizationSpecification = {
|
|
283
|
+
type: 'PAYMENT_GATEWAY',
|
|
284
|
+
parameters: {
|
|
285
|
+
gateway: 'basistheory',
|
|
286
|
+
gatewayMerchantId: basistheoryTenantId,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
const paymentRequest = {
|
|
290
|
+
apiVersion: 2,
|
|
291
|
+
apiVersionMinor: 0,
|
|
292
|
+
allowedPaymentMethods: [
|
|
293
|
+
{
|
|
294
|
+
...baseCardPaymentMethod,
|
|
295
|
+
tokenizationSpecification,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
transactionInfo: {
|
|
299
|
+
totalPriceStatus: 'FINAL',
|
|
300
|
+
totalPrice: minorUnitsToCurrencyString(orderSummary.totalAdjustedAmount, orderSummary.currency),
|
|
301
|
+
currencyCode: orderSummary.currency,
|
|
302
|
+
},
|
|
303
|
+
merchantInfo: {
|
|
304
|
+
merchantName: googlePayPaymentMethod?.metadata?.merchantName ||
|
|
305
|
+
checkout?.checkoutSession?.store?.name ||
|
|
306
|
+
'Store',
|
|
307
|
+
merchantId: googlePayPaymentMethod?.metadata?.sandboxed
|
|
308
|
+
? '12345678901234567890'
|
|
309
|
+
: googlePayPaymentMethod?.metadata?.merchantId || '12345678901234567890',
|
|
310
|
+
},
|
|
311
|
+
shippingAddressRequired: true,
|
|
312
|
+
shippingOptionRequired: true,
|
|
313
|
+
emailRequired: true,
|
|
314
|
+
callbackIntents: ['SHIPPING_OPTION', 'SHIPPING_ADDRESS', 'PAYMENT_AUTHORIZATION'],
|
|
315
|
+
shippingOptionParameters: {
|
|
316
|
+
defaultSelectedOptionId: shippingMethods.length > 0 ? shippingMethods[0].identifier : '',
|
|
317
|
+
shippingOptions: shippingMethods.map((method) => ({
|
|
318
|
+
id: method.identifier,
|
|
319
|
+
label: method.label,
|
|
320
|
+
description: method.amount + ': ' + method.detail || '',
|
|
321
|
+
})),
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
const environment = googlePayPaymentMethod?.metadata?.sandboxed ? 'TEST' : 'PRODUCTION';
|
|
325
|
+
return (_jsxs("div", { className: "w-full", children: [!processingPayment ? (_jsx(GooglePayButtonReact, { environment: environment, paymentRequest: paymentRequest, onPaymentAuthorized: handleGooglePayAuthorized, onPaymentDataChanged: handleGooglePayDataChanged, onError: (error) => {
|
|
326
|
+
console.error('Google Pay error:', error);
|
|
327
|
+
const errorMessage = 'Google Pay error: ' + error.statusMessage;
|
|
328
|
+
setGooglePayError(errorMessage);
|
|
329
|
+
setContextError(errorMessage);
|
|
330
|
+
if (onError) {
|
|
331
|
+
onError(errorMessage);
|
|
332
|
+
}
|
|
333
|
+
}, onCancel: () => {
|
|
334
|
+
console.log('Google Pay payment cancelled');
|
|
335
|
+
if (onCancel) {
|
|
336
|
+
onCancel();
|
|
337
|
+
}
|
|
338
|
+
}, existingPaymentMethodRequired: false, buttonColor: "black", buttonType: "plain", buttonSizeMode: "fill", buttonLocale: "en", className: `m-0 h-10 w-full rounded-sm p-0 text-base text-white ${className}` })) : (_jsx(Button, { type: "button", variant: "outline", size: "lg", className: "h-10 w-full bg-black text-base text-white shadow-sm hover:bg-black/80", disabled: true, 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" }), _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 }))] }));
|
|
339
|
+
};
|
|
340
|
+
export default GooglePayButton;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { default as DebugDrawer } from './DebugDrawer';
|
|
2
|
-
export { default as Button } from './Button';
|
|
3
1
|
export { default as ApplePayButton } from './ApplePayButton';
|
|
2
|
+
export { default as Button } from './Button';
|
|
3
|
+
export { default as DebugDrawer } from './DebugDrawer';
|
|
4
|
+
export { default as GooglePayButton } from './GooglePayButton';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { default as DebugDrawer } from './DebugDrawer';
|
|
2
|
-
export { default as Button } from './Button';
|
|
3
1
|
export { default as ApplePayButton } from './ApplePayButton';
|
|
2
|
+
export { default as Button } from './Button';
|
|
3
|
+
export { default as DebugDrawer } from './DebugDrawer';
|
|
4
|
+
export { default as GooglePayButton } from './GooglePayButton';
|
|
@@ -282,7 +282,9 @@ export function useApplePay(options = {}) {
|
|
|
282
282
|
// Tokenize the Apple Pay payment
|
|
283
283
|
const applePayToken = await tokenizeApplePay(event);
|
|
284
284
|
// Complete the Apple Pay session
|
|
285
|
-
session.completePayment(
|
|
285
|
+
session.completePayment({
|
|
286
|
+
status: window.ApplePaySession.STATUS_SUCCESS,
|
|
287
|
+
});
|
|
286
288
|
// Process the payment using the SDK's payment methods
|
|
287
289
|
await processApplePayPayment(checkoutSessionId, applePayToken, {
|
|
288
290
|
onSuccess: (payment) => {
|
|
@@ -298,7 +300,9 @@ export function useApplePay(options = {}) {
|
|
|
298
300
|
}
|
|
299
301
|
catch (error) {
|
|
300
302
|
console.error('Payment processing failed:', error);
|
|
301
|
-
session.completePayment(
|
|
303
|
+
session.completePayment({
|
|
304
|
+
status: window.ApplePaySession.STATUS_FAILURE,
|
|
305
|
+
});
|
|
302
306
|
setProcessingPayment(false);
|
|
303
307
|
const errorMsg = error instanceof Error ? error.message : 'Payment processing failed';
|
|
304
308
|
setError(errorMsg);
|
|
@@ -317,7 +321,18 @@ export function useApplePay(options = {}) {
|
|
|
317
321
|
return;
|
|
318
322
|
}
|
|
319
323
|
const { lineItems: newLineItems, total: newTotal } = newOrderSummary;
|
|
320
|
-
session.completeShippingMethodSelection(
|
|
324
|
+
session.completeShippingMethodSelection({
|
|
325
|
+
newTotal: {
|
|
326
|
+
label: newTotal.label,
|
|
327
|
+
amount: newTotal.amount,
|
|
328
|
+
type: 'final',
|
|
329
|
+
},
|
|
330
|
+
newLineItems: newLineItems.map(item => ({
|
|
331
|
+
label: item.label,
|
|
332
|
+
amount: item.amount,
|
|
333
|
+
type: 'final',
|
|
334
|
+
})),
|
|
335
|
+
});
|
|
321
336
|
}
|
|
322
337
|
catch (error) {
|
|
323
338
|
console.error('Shipping method selection failed:', error);
|
|
@@ -343,7 +358,24 @@ export function useApplePay(options = {}) {
|
|
|
343
358
|
console.log('======= APPLE PAY ON SHIPPING CONTACT SELECTED ======');
|
|
344
359
|
console.log('newOrderSummary', newOrderSummary);
|
|
345
360
|
console.log(newLineItems, newTotal, newShippingMethods);
|
|
346
|
-
session.completeShippingContactSelection(
|
|
361
|
+
session.completeShippingContactSelection({
|
|
362
|
+
newShippingMethods: newShippingMethods.map(method => ({
|
|
363
|
+
label: method.label,
|
|
364
|
+
amount: method.amount,
|
|
365
|
+
detail: method.detail,
|
|
366
|
+
identifier: method.identifier,
|
|
367
|
+
})),
|
|
368
|
+
newTotal: {
|
|
369
|
+
label: newTotal.label,
|
|
370
|
+
amount: newTotal.amount,
|
|
371
|
+
type: 'final',
|
|
372
|
+
},
|
|
373
|
+
newLineItems: newLineItems.map(item => ({
|
|
374
|
+
label: item.label,
|
|
375
|
+
amount: item.amount,
|
|
376
|
+
type: 'final',
|
|
377
|
+
})),
|
|
378
|
+
});
|
|
347
379
|
}
|
|
348
380
|
catch (error) {
|
|
349
381
|
console.error('Shipping contact selection failed:', error);
|
|
@@ -351,12 +383,8 @@ export function useApplePay(options = {}) {
|
|
|
351
383
|
}
|
|
352
384
|
})();
|
|
353
385
|
};
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const errorMsg = 'Apple Pay session error';
|
|
357
|
-
setError(errorMsg);
|
|
358
|
-
options.onError?.(errorMsg);
|
|
359
|
-
};
|
|
386
|
+
// Note: ApplePaySession doesn't have an onerror handler
|
|
387
|
+
// Error handling is done in the specific event handlers above
|
|
360
388
|
session.oncancel = () => {
|
|
361
389
|
console.log('Payment cancelled by user');
|
|
362
390
|
setProcessingPayment(false);
|
|
@@ -30,9 +30,17 @@ type ExpressShippingMethod = {
|
|
|
30
30
|
identifier: string;
|
|
31
31
|
detail: string;
|
|
32
32
|
};
|
|
33
|
-
export interface
|
|
33
|
+
export interface ExpressPaymentMethodsContextType {
|
|
34
|
+
paymentMethods: PaymentMethod[] | undefined;
|
|
34
35
|
applePayPaymentMethod?: PaymentMethod;
|
|
35
36
|
googlePayPaymentMethod?: PaymentMethod;
|
|
37
|
+
paypalPaymentMethod?: PaymentMethod;
|
|
38
|
+
klarnaPaymentMethod?: PaymentMethod;
|
|
39
|
+
availableExpressPaymentMethodIds: string[];
|
|
40
|
+
setAvailableExpressPaymentMethodIds: (value: string[]) => void;
|
|
41
|
+
handleAddExpressId: (id: string) => void;
|
|
42
|
+
shippingMethods: ExpressShippingMethod[];
|
|
43
|
+
lineItems: ExpressOrderLineItem[];
|
|
36
44
|
reComputeOrderSummary: () => Promise<{
|
|
37
45
|
lineItems: ExpressOrderLineItem[];
|
|
38
46
|
total: {
|
|
@@ -41,12 +49,6 @@ export interface ExpressPaymentContextType {
|
|
|
41
49
|
};
|
|
42
50
|
shippingMethods: ExpressShippingMethod[];
|
|
43
51
|
} | undefined>;
|
|
44
|
-
loading?: boolean;
|
|
45
|
-
availableExpressPaymentMethodIds: string[];
|
|
46
|
-
setAvailableExpressPaymentMethodIds: (value: string[]) => void;
|
|
47
|
-
shippingMethods: ExpressShippingMethod[];
|
|
48
|
-
lineItems: ExpressOrderLineItem[];
|
|
49
|
-
handleAddExpressId: (id: string) => void;
|
|
50
52
|
updateCheckoutSessionValues: (input: {
|
|
51
53
|
data: {
|
|
52
54
|
shippingAddress: Address;
|
|
@@ -58,14 +60,15 @@ export interface ExpressPaymentContextType {
|
|
|
58
60
|
email: string;
|
|
59
61
|
};
|
|
60
62
|
}) => Promise<void>;
|
|
63
|
+
loading?: boolean;
|
|
61
64
|
error: string | null;
|
|
62
65
|
setError: (error: string | null) => void;
|
|
63
66
|
}
|
|
64
|
-
interface
|
|
67
|
+
interface ExpressPaymentMethodsProviderProps {
|
|
65
68
|
children: ReactNode;
|
|
66
69
|
customerId?: string;
|
|
67
70
|
checkout?: CheckoutData;
|
|
68
71
|
}
|
|
69
|
-
export declare const
|
|
70
|
-
export declare const
|
|
72
|
+
export declare const ExpressPaymentMethodsProvider: React.FC<ExpressPaymentMethodsProviderProps>;
|
|
73
|
+
export declare const useExpressPaymentMethods: () => ExpressPaymentMethodsContextType;
|
|
71
74
|
export {};
|
|
@@ -3,8 +3,8 @@ import React, { createContext, useCallback, useContext, useMemo, useState } from
|
|
|
3
3
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
4
4
|
import { useOrderSummary } from './useOrderSummary';
|
|
5
5
|
import { useShippingRates } from './useShippingRates';
|
|
6
|
-
const
|
|
7
|
-
export const
|
|
6
|
+
const ExpressPaymentMethodsContext = createContext(undefined);
|
|
7
|
+
export const ExpressPaymentMethodsProvider = ({ children, customerId, checkout, }) => {
|
|
8
8
|
const { apiService } = useTagadaContext();
|
|
9
9
|
const [availableExpressPaymentMethodIds, setAvailableExpressPaymentMethodIds] = useState([]);
|
|
10
10
|
const [error, setError] = useState(null);
|
|
@@ -37,7 +37,7 @@ export const ExpressPaymentProvider = ({ children, customerId, checkout, }) => {
|
|
|
37
37
|
return () => {
|
|
38
38
|
mounted = false;
|
|
39
39
|
};
|
|
40
|
-
}, [apiService, checkoutSessionId]);
|
|
40
|
+
}, [apiService, checkoutSessionId, checkout]);
|
|
41
41
|
const handleAddExpressId = (id) => {
|
|
42
42
|
setAvailableExpressPaymentMethodIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
|
|
43
43
|
};
|
|
@@ -128,14 +128,20 @@ export const ExpressPaymentProvider = ({ children, customerId, checkout, }) => {
|
|
|
128
128
|
body: input,
|
|
129
129
|
});
|
|
130
130
|
}, [apiService, customerId]);
|
|
131
|
+
// Identify specific payment method types
|
|
131
132
|
const enabledApplePayPaymentMethod = useMemo(() => paymentMethods?.find((p) => p.type === 'apple_pay'), [paymentMethods]);
|
|
132
133
|
const enabledGooglePayPaymentMethod = useMemo(() => paymentMethods?.find((p) => p.type === 'google_pay'), [paymentMethods]);
|
|
134
|
+
const enabledPaypalPaymentMethod = useMemo(() => paymentMethods?.find((p) => p.type === 'paypal'), [paymentMethods]);
|
|
135
|
+
const enabledKlarnaPaymentMethod = useMemo(() => paymentMethods?.find((p) => p.type === 'klarna'), [paymentMethods]);
|
|
133
136
|
const loading = !paymentMethods || isLoadingPaymentMethods || isLoadingOrderSummary;
|
|
134
137
|
const contextValue = {
|
|
138
|
+
paymentMethods: paymentMethods || undefined,
|
|
135
139
|
availableExpressPaymentMethodIds,
|
|
136
140
|
setAvailableExpressPaymentMethodIds,
|
|
137
141
|
applePayPaymentMethod: enabledApplePayPaymentMethod,
|
|
138
142
|
googlePayPaymentMethod: enabledGooglePayPaymentMethod,
|
|
143
|
+
paypalPaymentMethod: enabledPaypalPaymentMethod,
|
|
144
|
+
klarnaPaymentMethod: enabledKlarnaPaymentMethod,
|
|
139
145
|
shippingMethods,
|
|
140
146
|
lineItems,
|
|
141
147
|
reComputeOrderSummary,
|
|
@@ -146,13 +152,16 @@ export const ExpressPaymentProvider = ({ children, customerId, checkout, }) => {
|
|
|
146
152
|
error,
|
|
147
153
|
setError,
|
|
148
154
|
};
|
|
149
|
-
const hasAnyEnabled = Boolean(enabledApplePayPaymentMethod ||
|
|
150
|
-
|
|
155
|
+
const hasAnyEnabled = Boolean(enabledApplePayPaymentMethod ||
|
|
156
|
+
enabledGooglePayPaymentMethod ||
|
|
157
|
+
enabledPaypalPaymentMethod ||
|
|
158
|
+
enabledKlarnaPaymentMethod);
|
|
159
|
+
return (_jsx(ExpressPaymentMethodsContext.Provider, { value: contextValue, children: hasAnyEnabled ? _jsx(_Fragment, { children: children }) : _jsx(_Fragment, {}) }));
|
|
151
160
|
};
|
|
152
|
-
export const
|
|
153
|
-
const context = useContext(
|
|
161
|
+
export const useExpressPaymentMethods = () => {
|
|
162
|
+
const context = useContext(ExpressPaymentMethodsContext);
|
|
154
163
|
if (context === undefined) {
|
|
155
|
-
throw new Error('
|
|
164
|
+
throw new Error('useExpressPaymentMethods must be used within an ExpressPaymentMethodsProvider');
|
|
156
165
|
}
|
|
157
166
|
return context;
|
|
158
167
|
};
|
|
@@ -38,6 +38,8 @@ export interface ExtractedAddress {
|
|
|
38
38
|
locality: string;
|
|
39
39
|
administrativeAreaLevel1: string;
|
|
40
40
|
administrativeAreaLevel1Long: string;
|
|
41
|
+
administrativeAreaLevel2: string;
|
|
42
|
+
administrativeAreaLevel2Long: string;
|
|
41
43
|
country: string;
|
|
42
44
|
postalCode: string;
|
|
43
45
|
}
|
|
@@ -155,6 +155,8 @@ export function useGoogleAutocomplete(options) {
|
|
|
155
155
|
locality: '',
|
|
156
156
|
administrativeAreaLevel1: '',
|
|
157
157
|
administrativeAreaLevel1Long: '',
|
|
158
|
+
administrativeAreaLevel2: '',
|
|
159
|
+
administrativeAreaLevel2Long: '',
|
|
158
160
|
country: '',
|
|
159
161
|
postalCode: '',
|
|
160
162
|
};
|
|
@@ -170,8 +172,13 @@ export function useGoogleAutocomplete(options) {
|
|
|
170
172
|
if (types.includes('locality')) {
|
|
171
173
|
extracted.locality = component.long_name;
|
|
172
174
|
}
|
|
173
|
-
if (types.includes('administrative_area_level_2')
|
|
174
|
-
extracted.
|
|
175
|
+
if (types.includes('administrative_area_level_2')) {
|
|
176
|
+
extracted.administrativeAreaLevel2 = component.short_name;
|
|
177
|
+
extracted.administrativeAreaLevel2Long = component.long_name;
|
|
178
|
+
// Use level_2 as fallback for locality if locality is not set
|
|
179
|
+
if (!extracted.locality) {
|
|
180
|
+
extracted.locality = component.long_name;
|
|
181
|
+
}
|
|
175
182
|
}
|
|
176
183
|
if (types.includes('administrative_area_level_1')) {
|
|
177
184
|
extracted.administrativeAreaLevel1 = component.short_name;
|
|
@@ -184,6 +191,15 @@ export function useGoogleAutocomplete(options) {
|
|
|
184
191
|
extracted.postalCode = component.long_name;
|
|
185
192
|
}
|
|
186
193
|
});
|
|
194
|
+
// For countries like France where administrative_area_level_1 (région) may be missing,
|
|
195
|
+
// use administrative_area_level_2 (département) as the primary state/province value
|
|
196
|
+
// We prefer the long_name (e.g., "Bouches-du-Rhône") over short_name (e.g., "13")
|
|
197
|
+
// because it's more likely to match our state database entries
|
|
198
|
+
if (!extracted.administrativeAreaLevel1 && extracted.administrativeAreaLevel2) {
|
|
199
|
+
// Use long name as the primary value (e.g., "Bouches-du-Rhône" instead of "13")
|
|
200
|
+
extracted.administrativeAreaLevel1 = extracted.administrativeAreaLevel2Long || extracted.administrativeAreaLevel2;
|
|
201
|
+
extracted.administrativeAreaLevel1Long = extracted.administrativeAreaLevel2Long;
|
|
202
|
+
}
|
|
187
203
|
console.log('🏗️ Extracted address components:', extracted);
|
|
188
204
|
return extracted;
|
|
189
205
|
}, []);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface UseGooglePayOptions {
|
|
2
|
+
onSuccess?: (payment: any) => void;
|
|
3
|
+
onError?: (error: string) => void;
|
|
4
|
+
onCancel?: () => void;
|
|
5
|
+
checkoutSessionId?: string;
|
|
6
|
+
customerId?: string;
|
|
7
|
+
storeName?: string;
|
|
8
|
+
currencyCode?: string;
|
|
9
|
+
merchantId?: string;
|
|
10
|
+
merchantName?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const useGooglePay: ({ onSuccess, onError, onCancel, checkoutSessionId, customerId, storeName, currencyCode, merchantId, merchantName, }: UseGooglePayOptions) => {
|
|
13
|
+
handleGooglePayAuthorized: (paymentData: any) => Promise<any>;
|
|
14
|
+
handleGooglePayDataChanged: (intermediatePaymentData: any) => Promise<any>;
|
|
15
|
+
processingPayment: boolean;
|
|
16
|
+
setProcessingPayment: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
17
|
+
googlePayError: string | null;
|
|
18
|
+
setGooglePayError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
19
|
+
isGooglePayAvailable: boolean;
|
|
20
|
+
paymentRequest: null;
|
|
21
|
+
};
|
|
22
|
+
export {};
|