@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.
Files changed (50) hide show
  1. package/dist/react/components/GooglePayButton.d.ts +12 -0
  2. package/dist/react/components/GooglePayButton.js +340 -0
  3. package/dist/react/components/index.d.ts +3 -2
  4. package/dist/react/components/index.js +3 -2
  5. package/dist/react/hooks/useApplePay.js +38 -10
  6. package/dist/react/hooks/useGoogleAutocomplete.d.ts +2 -0
  7. package/dist/react/hooks/useGoogleAutocomplete.js +18 -2
  8. package/dist/react/hooks/useGooglePay.d.ts +22 -0
  9. package/dist/react/hooks/useGooglePay.js +32 -0
  10. package/dist/react/index.d.ts +2 -1
  11. package/dist/react/index.js +3 -1
  12. package/dist/react/types/apple-pay.d.ts +0 -25
  13. package/dist/v2/core/googleAutocomplete.d.ts +2 -0
  14. package/dist/v2/core/googleAutocomplete.js +21 -8
  15. package/dist/v2/core/resources/checkout.d.ts +70 -2
  16. package/dist/v2/core/resources/discounts.d.ts +53 -0
  17. package/dist/v2/core/resources/discounts.js +29 -0
  18. package/dist/v2/core/resources/expressPaymentMethods.d.ts +56 -0
  19. package/dist/v2/core/resources/expressPaymentMethods.js +27 -0
  20. package/dist/v2/core/resources/index.d.ts +7 -4
  21. package/dist/v2/core/resources/index.js +7 -4
  22. package/dist/v2/core/resources/shippingRates.d.ts +36 -0
  23. package/dist/v2/core/resources/shippingRates.js +23 -0
  24. package/dist/v2/core/resources/vipOffers.d.ts +37 -0
  25. package/dist/v2/core/resources/vipOffers.js +27 -0
  26. package/dist/v2/core/utils/order.d.ts +1 -0
  27. package/dist/v2/core/utils/pluginConfig.d.ts +6 -6
  28. package/dist/v2/index.d.ts +12 -9
  29. package/dist/v2/index.js +3 -3
  30. package/dist/v2/react/components/ApplePayButton.d.ts +141 -0
  31. package/dist/v2/react/components/ApplePayButton.js +320 -0
  32. package/dist/v2/react/components/GooglePayButton.d.ts +19 -0
  33. package/dist/v2/react/components/GooglePayButton.js +355 -0
  34. package/dist/v2/react/hooks/useApiQuery.d.ts +4 -1
  35. package/dist/v2/react/hooks/useApiQuery.js +4 -1
  36. package/dist/v2/react/hooks/useDiscountsQuery.d.ts +30 -0
  37. package/dist/v2/react/hooks/useDiscountsQuery.js +175 -0
  38. package/dist/v2/react/hooks/useExpressPaymentMethods.d.ts +12 -0
  39. package/dist/v2/react/hooks/useExpressPaymentMethods.js +17 -0
  40. package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +2 -0
  41. package/dist/v2/react/hooks/useGoogleAutocomplete.js +18 -2
  42. package/dist/v2/react/hooks/useShippingRatesQuery.d.ts +22 -0
  43. package/dist/v2/react/hooks/useShippingRatesQuery.js +134 -0
  44. package/dist/v2/react/hooks/useVipOffersQuery.d.ts +72 -0
  45. package/dist/v2/react/hooks/useVipOffersQuery.js +140 -0
  46. package/dist/v2/react/index.d.ts +30 -17
  47. package/dist/v2/react/index.js +18 -10
  48. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +59 -0
  49. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +165 -0
  50. 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 { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
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 { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
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
  }