@tagadapay/plugin-sdk 2.6.4 → 2.6.8
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 +39 -0
- 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 +8 -4
- package/dist/v2/core/resources/index.js +8 -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/storeConfig.d.ts +25 -0
- package/dist/v2/core/resources/storeConfig.js +16 -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 +13 -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 +5 -1
- package/dist/v2/react/hooks/useApiQuery.js +5 -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/useStoreConfigQuery.d.ts +17 -0
- package/dist/v2/react/hooks/useStoreConfigQuery.js +52 -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 +32 -17
- package/dist/v2/react/index.js +19 -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
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ A comprehensive React SDK for building plugins on the TagadaPay platform. Create
|
|
|
15
15
|
- **[useCheckout (V2)](#usecheckout)** - TanStack Query-based checkout management
|
|
16
16
|
- **[useOffers (V2)](#useoffers)** - Dynamic pricing with automatic caching
|
|
17
17
|
- **[useProducts (V2)](#useproducts)** - Product data management
|
|
18
|
+
- **[useStoreConfig (V2)](#usestoreconfig)** - Store configuration with automatic caching
|
|
18
19
|
- **[usePayment (V2)](#usepayment)** - Payment processing with 3DS support
|
|
19
20
|
- **[V2 Utility Functions](#v2-utility-functions--advanced-features)** - Enhanced utilities and TanStack Query integration
|
|
20
21
|
|
|
@@ -708,6 +709,44 @@ const {
|
|
|
708
709
|
} = useProducts({ storeId, enabled });
|
|
709
710
|
```
|
|
710
711
|
|
|
712
|
+
#### useStoreConfig()
|
|
713
|
+
|
|
714
|
+
Hook for fetching store configuration with automatic caching.
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
const {
|
|
718
|
+
// Query data
|
|
719
|
+
storeConfig, // StoreConfig | undefined
|
|
720
|
+
isLoading, // boolean
|
|
721
|
+
error, // Error | null
|
|
722
|
+
isSuccess, // boolean
|
|
723
|
+
|
|
724
|
+
// Actions
|
|
725
|
+
refetch, // () => Promise<void>
|
|
726
|
+
} = useStoreConfig({ storeId, enabled });
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
**Example:**
|
|
730
|
+
|
|
731
|
+
```tsx
|
|
732
|
+
import { useStoreConfig } from '@tagadapay/plugin-sdk/v2';
|
|
733
|
+
|
|
734
|
+
function StoreInfo() {
|
|
735
|
+
const { storeConfig, isLoading } = useStoreConfig();
|
|
736
|
+
|
|
737
|
+
if (isLoading) return <div>Loading...</div>;
|
|
738
|
+
|
|
739
|
+
return (
|
|
740
|
+
<div>
|
|
741
|
+
<h1>{storeConfig?.storeName}</h1>
|
|
742
|
+
<p>Currency: {storeConfig?.currency}</p>
|
|
743
|
+
</div>
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
See [useStoreConfig documentation](./docs/README-useStoreConfig.md) for more details.
|
|
749
|
+
|
|
711
750
|
#### useOrder()
|
|
712
751
|
|
|
713
752
|
Hook for order management and tracking.
|
|
@@ -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);
|
|
@@ -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 {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
export const useGooglePay = ({ onSuccess, onError, onCancel, checkoutSessionId, customerId, storeName = 'Store', currencyCode = 'USD', merchantId = '12345678901234567890', merchantName = 'Store', }) => {
|
|
3
|
+
const [processingPayment, setProcessingPayment] = useState(false);
|
|
4
|
+
const [googlePayError, setGooglePayError] = useState(null);
|
|
5
|
+
const [isGooglePayAvailable, setIsGooglePayAvailable] = useState(false);
|
|
6
|
+
// Check if Google Pay is available on this device
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (typeof window !== 'undefined' && window.google?.payments?.api) {
|
|
9
|
+
setIsGooglePayAvailable(true);
|
|
10
|
+
}
|
|
11
|
+
}, []);
|
|
12
|
+
// The actual implementation will be done by the consumer
|
|
13
|
+
// This hook just provides state management
|
|
14
|
+
const handleGooglePayAuthorized = useCallback((paymentData) => {
|
|
15
|
+
// This will be implemented by the consumer
|
|
16
|
+
return Promise.resolve({ transactionState: 'SUCCESS' });
|
|
17
|
+
}, []);
|
|
18
|
+
const handleGooglePayDataChanged = useCallback((intermediatePaymentData) => {
|
|
19
|
+
// This will be implemented by the consumer
|
|
20
|
+
return Promise.resolve({});
|
|
21
|
+
}, []);
|
|
22
|
+
return {
|
|
23
|
+
handleGooglePayAuthorized,
|
|
24
|
+
handleGooglePayDataChanged,
|
|
25
|
+
processingPayment,
|
|
26
|
+
setProcessingPayment,
|
|
27
|
+
googlePayError,
|
|
28
|
+
setGooglePayError,
|
|
29
|
+
isGooglePayAvailable,
|
|
30
|
+
paymentRequest: null, // Will be provided by consumer
|
|
31
|
+
};
|
|
32
|
+
};
|
package/dist/react/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export { usePaymentPolling } from './hooks/usePaymentPolling';
|
|
|
43
43
|
export { useThreeds } from './hooks/useThreeds';
|
|
44
44
|
export { useThreedsModal } from './hooks/useThreedsModal';
|
|
45
45
|
export { useApplePay } from './hooks/useApplePay';
|
|
46
|
+
export { useGooglePay } from './hooks/useGooglePay';
|
|
46
47
|
export { ExpressPaymentMethodsProvider, useExpressPaymentMethods } from './hooks/useExpressPaymentMethods';
|
|
47
48
|
export type { ExpressPaymentMethodsContextType } from './hooks/useExpressPaymentMethods';
|
|
48
49
|
export type { AuthState, Currency, Customer, CustomerInfos, Environment, EnvironmentConfig, Locale, Order, OrderAddress, OrderItem, OrderSummary, PickupPoint, Session, Store } from './types';
|
|
@@ -59,5 +60,5 @@ export type { Payment, PaymentPollingHook, PollingOptions } from './hooks/usePay
|
|
|
59
60
|
export type { PaymentInstrument, ThreedsChallenge, ThreedsHook, ThreedsOptions, ThreedsProvider, ThreedsSession } from './hooks/useThreeds';
|
|
60
61
|
export type { ApplePayToken, CardPaymentMethod, PaymentHook, PaymentInstrumentResponse, PaymentOptions, PaymentResponse } from './hooks/usePayment';
|
|
61
62
|
export type { ApplePayAddress, ApplePayConfig, ApplePayLineItem, ApplePayPaymentAuthorizedEvent, ApplePayPaymentRequest, ApplePayPaymentToken, ApplePayValidateMerchantEvent, BasisTheorySessionRequest, BasisTheoryTokenizeRequest, PayToken, UseApplePayOptions, UseApplePayResult } from './types/apple-pay';
|
|
62
|
-
export { ApplePayButton, Button } from './components';
|
|
63
|
+
export { ApplePayButton, Button, GooglePayButton } from './components';
|
|
63
64
|
export { convertCurrency, formatMoney, formatMoneyWithoutSymbol, formatSimpleMoney, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits } from './utils/money';
|
package/dist/react/index.js
CHANGED
|
@@ -45,10 +45,12 @@ export { useThreeds } from './hooks/useThreeds';
|
|
|
45
45
|
export { useThreedsModal } from './hooks/useThreedsModal';
|
|
46
46
|
// Apple Pay hooks exports
|
|
47
47
|
export { useApplePay } from './hooks/useApplePay';
|
|
48
|
+
// Google Pay hooks exports
|
|
49
|
+
export { useGooglePay } from './hooks/useGooglePay';
|
|
48
50
|
// Express Payment context exports
|
|
49
51
|
// Express Payment Methods (extended functionality)
|
|
50
52
|
export { ExpressPaymentMethodsProvider, useExpressPaymentMethods } from './hooks/useExpressPaymentMethods';
|
|
51
53
|
// Component exports
|
|
52
|
-
export { ApplePayButton, Button } from './components';
|
|
54
|
+
export { ApplePayButton, Button, GooglePayButton } from './components';
|
|
53
55
|
// Utility exports
|
|
54
56
|
export { convertCurrency, formatMoney, formatMoneyWithoutSymbol, formatSimpleMoney, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits } from './utils/money';
|