@tagadapay/plugin-sdk 2.4.4 → 2.4.7

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.
@@ -1,4 +1,5 @@
1
1
  import React, { ReactNode } from 'react';
2
+ import { CheckoutData } from './useCheckout';
2
3
  export interface Address {
3
4
  address1: string;
4
5
  address2?: string;
@@ -62,8 +63,8 @@ export interface ExpressPaymentContextType {
62
63
  }
63
64
  interface ExpressPaymentProviderProps {
64
65
  children: ReactNode;
65
- checkoutSessionId: string;
66
66
  customerId?: string;
67
+ checkout?: CheckoutData;
67
68
  }
68
69
  export declare const ExpressPaymentProvider: React.FC<ExpressPaymentProviderProps>;
69
70
  export declare const useExpressPayment: () => ExpressPaymentContextType;
@@ -4,17 +4,20 @@ import { useTagadaContext } from '../providers/TagadaProvider';
4
4
  import { useOrderSummary } from './useOrderSummary';
5
5
  import { useShippingRates } from './useShippingRates';
6
6
  const ExpressPaymentContext = createContext(undefined);
7
- export const ExpressPaymentProvider = ({ children, checkoutSessionId, customerId, }) => {
7
+ export const ExpressPaymentProvider = ({ children, customerId, checkout, }) => {
8
8
  const { apiService } = useTagadaContext();
9
9
  const [availableExpressPaymentMethodIds, setAvailableExpressPaymentMethodIds] = useState([]);
10
10
  const [error, setError] = useState(null);
11
11
  const [paymentMethods, setPaymentMethods] = useState(null);
12
12
  const [isLoadingPaymentMethods, setIsLoadingPaymentMethods] = useState(false);
13
+ const checkoutSessionId = checkout?.checkoutSession.id;
13
14
  // Fetch enabled payment methods for this checkout session
14
15
  React.useEffect(() => {
15
16
  let mounted = true;
16
17
  const fetchPaymentMethods = async () => {
17
18
  try {
19
+ if (!checkoutSessionId)
20
+ return;
18
21
  setIsLoadingPaymentMethods(true);
19
22
  const response = await apiService.fetch(`/api/v1/payment-methods?checkoutSessionId=${encodeURIComponent(checkoutSessionId)}`);
20
23
  if (mounted)
@@ -29,7 +32,7 @@ export const ExpressPaymentProvider = ({ children, checkoutSessionId, customerId
29
32
  setIsLoadingPaymentMethods(false);
30
33
  }
31
34
  };
32
- if (checkoutSessionId)
35
+ if (checkout)
33
36
  void fetchPaymentMethods();
34
37
  return () => {
35
38
  mounted = false;
@@ -40,7 +43,7 @@ export const ExpressPaymentProvider = ({ children, checkoutSessionId, customerId
40
43
  };
41
44
  // Base data hooks
42
45
  const { orderSummary, isLoading: isLoadingOrderSummary, isRefetching: isRefetchingOrderSummary, refetch: refetchOrderSummary, } = useOrderSummary({ sessionId: checkoutSessionId });
43
- const { shippingRates, isLoading: isLoadingShippingRates, isFetching: isFetchingShippingRates, refetch: refetchRates, } = useShippingRates({ checkoutSessionId });
46
+ const { shippingRates, refetch: refetchRates } = useShippingRates({ checkout });
44
47
  const minorUnitsToCurrencyString = (amountMinor, currency) => {
45
48
  if (!amountMinor || !currency)
46
49
  return '0.00';
@@ -18,6 +18,7 @@ export function usePostPurchases(options) {
18
18
  try {
19
19
  const response = await apiService.fetch(`/api/v1/post-purchase/${orderId}/offers`, {
20
20
  method: 'GET',
21
+ skipAuth: true,
21
22
  });
22
23
  const fetchedOffers = response || [];
23
24
  setOffers(fetchedOffers);
@@ -69,6 +70,7 @@ export function usePostPurchases(options) {
69
70
  method: 'POST',
70
71
  body: JSON.stringify({
71
72
  checkoutSessionId,
73
+ skipAuth: true,
72
74
  metadata: {
73
75
  comingFromPostPurchase: true,
74
76
  postOrder: orderId,
@@ -237,6 +239,7 @@ export function usePostPurchases(options) {
237
239
  }
238
240
  const response = await apiService.fetch(`/api/v1/checkout-sessions/${sessionState.checkoutSessionId}/pay`, {
239
241
  method: 'POST',
242
+ skipAuth: true,
240
243
  body: JSON.stringify({
241
244
  checkoutSessionId: sessionState.checkoutSessionId,
242
245
  draft: options?.draft || false,
@@ -1,4 +1,4 @@
1
- import { PickupPoint } from '../types';
1
+ import { CheckoutData } from './useCheckout';
2
2
  export interface ShippingRate {
3
3
  id: string;
4
4
  shippingRateName: string;
@@ -13,36 +13,19 @@ export interface ShippingRate {
13
13
  }
14
14
  export interface ShippingRatesResponse {
15
15
  rates: ShippingRate[];
16
- pickupPoint?: PickupPoint;
17
16
  forceCheckoutSessionRefetch?: boolean;
18
17
  }
19
18
  export interface UseShippingRatesOptions {
20
19
  onSuccess?: () => void;
21
- disabled?: boolean;
22
- checkoutSessionId?: string;
20
+ checkout?: CheckoutData;
23
21
  }
24
22
  export interface UseShippingRatesResult {
25
23
  shippingRates: ShippingRate[] | undefined;
26
- forceCheckoutSessionRefetch: boolean | undefined;
27
- handleSelectRate: (rateId: string) => void;
28
- isPickupPointSelected: boolean;
29
- selectedRateId: string | null;
30
- setSelectedRateId: (rateId: string | null) => void;
31
- setShippingRate: (data: {
32
- shippingRateId: string;
33
- }) => Promise<void>;
34
- setShippingRateAsync: (data: {
35
- shippingRateId: string;
36
- }) => Promise<void>;
37
- isPending: boolean;
38
- isFetching: boolean;
24
+ selectedRate: ShippingRate | undefined;
25
+ selectRate: (rateId: string) => Promise<void> | undefined;
39
26
  isLoading: boolean;
40
27
  error: Error | null;
28
+ clearError: () => void;
41
29
  refetch: () => Promise<void>;
42
- setPickupPoint: (data: {
43
- checkoutSessionId: string;
44
- data: PickupPoint;
45
- }) => Promise<void>;
46
- selectedRate: ShippingRate | undefined;
47
30
  }
48
- export declare const useShippingRates: ({ onSuccess, disabled, checkoutSessionId }?: UseShippingRatesOptions) => UseShippingRatesResult;
31
+ export declare const useShippingRates: ({ onSuccess, checkout }?: UseShippingRatesOptions) => UseShippingRatesResult;
@@ -26,31 +26,34 @@ function useDebounce(callback, delay) {
26
26
  }, delay);
27
27
  }), [callback, delay]);
28
28
  }
29
- export const useShippingRates = ({ onSuccess, disabled, checkoutSessionId } = {}) => {
29
+ export const useShippingRates = ({ onSuccess, checkout } = {}) => {
30
30
  const { apiService } = useTagadaContext();
31
31
  const [selectedRateId, setSelectedRateId] = useState(null);
32
+ // At very first init, set the shipping rate from the checkout if available
32
33
  const [shippingRates, setShippingRates] = useState();
33
- const [forceCheckoutSessionRefetch, setForceCheckoutSessionRefetch] = useState();
34
- const [isLoading, setIsLoading] = useState(false);
35
34
  const [isFetching, setIsFetching] = useState(false);
36
35
  const [isPending, setIsPending] = useState(false);
37
36
  const [error, setError] = useState(null);
38
37
  const isMountedRef = useRef(true);
39
38
  const isUpdatingRef = useRef(false);
40
- // Use checkoutSessionId from props or fall back to session
41
- const sessionId = checkoutSessionId;
39
+ const previousSessionIdRef = useRef(undefined);
40
+ const isFetchingRef = useRef(false);
41
+ const hasSyncedSelectionFromCheckoutRef = useRef(false);
42
+ const sessionId = checkout?.checkoutSession.id;
42
43
  // Track mounted state
43
44
  useEffect(() => {
44
45
  isMountedRef.current = true;
45
46
  return () => {
46
47
  isMountedRef.current = false;
48
+ isFetchingRef.current = false;
47
49
  };
48
50
  }, []);
49
51
  // Fetch shipping rates
50
52
  const fetchShippingRates = useCallback(async () => {
51
- if (!sessionId || !apiService)
53
+ if (!sessionId || !apiService || isFetchingRef.current)
52
54
  return;
53
55
  try {
56
+ isFetchingRef.current = true;
54
57
  setIsFetching(true);
55
58
  setError(null);
56
59
  const response = await apiService.fetch(`/api/v1/checkout-sessions/${sessionId}/shipping-rates`, {
@@ -61,7 +64,6 @@ export const useShippingRates = ({ onSuccess, disabled, checkoutSessionId } = {}
61
64
  });
62
65
  if (isMountedRef.current) {
63
66
  setShippingRates(response.rates);
64
- setForceCheckoutSessionRefetch(response.forceCheckoutSessionRefetch);
65
67
  }
66
68
  }
67
69
  catch (err) {
@@ -71,17 +73,17 @@ export const useShippingRates = ({ onSuccess, disabled, checkoutSessionId } = {}
71
73
  }
72
74
  finally {
73
75
  if (isMountedRef.current) {
76
+ isFetchingRef.current = false;
74
77
  setIsFetching(false);
75
78
  }
76
79
  }
77
80
  }, [sessionId, apiService]);
78
- // Initial fetch of shipping rates when sessionId is available
79
81
  useEffect(() => {
80
- if (sessionId && !disabled && isMountedRef.current) {
82
+ if (sessionId && isMountedRef.current && sessionId !== previousSessionIdRef.current) {
83
+ previousSessionIdRef.current = sessionId;
81
84
  fetchShippingRates();
82
85
  }
83
- }, [sessionId, disabled, fetchShippingRates]);
84
- // Debounced success handler to prevent query invalidation storms
86
+ }, [sessionId]);
85
87
  const debouncedOnSuccess = useDebounce(useCallback(async () => {
86
88
  if (!isMountedRef.current || isUpdatingRef.current)
87
89
  return;
@@ -101,14 +103,15 @@ export const useShippingRates = ({ onSuccess, disabled, checkoutSessionId } = {}
101
103
  isUpdatingRef.current = false;
102
104
  }
103
105
  }
104
- }, [fetchShippingRates, onSuccess]), 300);
106
+ }, [onSuccess]), // Removed fetchShippingRates from dependencies
107
+ 300);
105
108
  // Set shipping rate mutation
106
109
  const setShippingRate = useCallback(async (data) => {
107
110
  if (!sessionId || !apiService)
108
111
  return;
109
112
  try {
110
113
  setIsPending(true);
111
- setError(null);
114
+ clearError();
112
115
  await apiService.fetch(`/api/v1/checkout-sessions/${sessionId}/shipping-rate`, {
113
116
  method: 'POST',
114
117
  body: data,
@@ -129,76 +132,54 @@ export const useShippingRates = ({ onSuccess, disabled, checkoutSessionId } = {}
129
132
  }
130
133
  }
131
134
  }, [sessionId, apiService, debouncedOnSuccess, selectedRateId]);
132
- const setShippingRateAsync = setShippingRate; // Alias for consistency
133
- // Debounced refetch for country/postal changes
134
- const debouncedRefetchRates = useDebounce(fetchShippingRates, 500);
135
- // Set pickup point mutation
136
- const setPickupPoint = useCallback(async (data) => {
137
- if (!apiService)
138
- return;
139
- try {
140
- setIsPending(true);
141
- setError(null);
142
- await apiService.fetch(`/api/v1/checkout-sessions/${data.checkoutSessionId}/pickup-point`, {
143
- method: 'POST',
144
- body: { data: data.data },
145
- });
146
- if (isMountedRef.current) {
147
- await debouncedOnSuccess();
148
- }
149
- }
150
- catch (err) {
151
- if (isMountedRef.current) {
152
- setSelectedRateId(selectedRateId); // Revert to previous selection
153
- setError(err instanceof Error ? err : new Error('Failed to update pickup point'));
154
- }
155
- }
156
- finally {
157
- if (isMountedRef.current) {
158
- setIsPending(false);
159
- }
160
- }
161
- }, [apiService, debouncedOnSuccess, selectedRateId]);
135
+ // Stable refetch function that can be called externally
136
+ const refetch = useCallback(() => {
137
+ return fetchShippingRates();
138
+ }, [fetchShippingRates]);
162
139
  const handleSelectRate = useCallback((rateId) => {
163
- if (isPending || disabled || !isMountedRef.current)
140
+ if (isPending || !isMountedRef.current)
164
141
  return;
165
- const selectedRate = shippingRates?.find((rate) => rate.id === rateId);
166
142
  if (isMountedRef.current) {
167
143
  setSelectedRateId(rateId);
168
- setShippingRate({ shippingRateId: rateId });
144
+ return setShippingRate({ shippingRateId: rateId });
145
+ }
146
+ }, [setShippingRate, isPending]);
147
+ const findCheapestShippingRate = useCallback(() => {
148
+ if (!shippingRates || shippingRates.length === 0)
149
+ return;
150
+ if (selectedRateId) {
151
+ const exists = shippingRates.some((r) => r.id === selectedRateId);
152
+ if (!exists) {
153
+ // Find the cheapest shipping rate and select it
154
+ const cheapest = shippingRates.reduce((min, rate) => {
155
+ return rate.amount < min.amount ? rate : min;
156
+ }, shippingRates[0]);
157
+ handleSelectRate(cheapest.id);
158
+ }
169
159
  }
170
- }, [setShippingRate, isPending, disabled, shippingRates]);
171
- // Auto-select shipping rate when only one is available
160
+ }, [shippingRates, selectedRateId, setShippingRate]);
161
+ // Sync selectedRateId from checkout only once when checkout data becomes available
172
162
  useEffect(() => {
173
- if (!shippingRates?.length || selectedRateId || !isMountedRef.current)
163
+ if (!hasSyncedSelectionFromCheckoutRef.current && checkout?.checkoutSession.shippingRate?.id) {
164
+ setSelectedRateId(checkout.checkoutSession.shippingRate.id);
165
+ hasSyncedSelectionFromCheckoutRef.current = true;
174
166
  return;
175
- const nonPickupRates = shippingRates.filter((rate) => !rate.isPickupPoint);
176
- if (nonPickupRates.length === 1) {
177
- handleSelectRate(nonPickupRates[0].id);
178
167
  }
179
- else if (nonPickupRates.length > 1) {
180
- const cheapestRate = nonPickupRates.reduce((prev, current) => current.amount < prev.amount ? current : prev);
181
- handleSelectRate(cheapestRate.id);
168
+ else if (shippingRates && shippingRates.length > 0 && !shippingRates.find((rate) => rate.id === selectedRateId)) {
169
+ findCheapestShippingRate();
182
170
  }
183
- }, [shippingRates, selectedRateId, handleSelectRate]);
184
- // Get the currently selected shipping rate
171
+ }, [checkout?.checkoutSession.shippingRate?.id, shippingRates]);
185
172
  const selectedRate = shippingRates?.find((rate) => rate.id === selectedRateId);
186
- const isPickupPointSelected = Boolean(selectedRate?.isPickupPoint);
173
+ const clearError = useCallback(() => {
174
+ setError(null);
175
+ }, []);
187
176
  return {
188
177
  shippingRates,
189
- forceCheckoutSessionRefetch,
190
- handleSelectRate,
191
- isPickupPointSelected,
192
- selectedRateId,
193
- setSelectedRateId,
194
- setShippingRate,
195
- setShippingRateAsync,
196
- isPending,
197
- isFetching,
198
- isLoading,
199
- error,
200
- refetch: fetchShippingRates,
201
- setPickupPoint,
202
178
  selectedRate,
179
+ selectRate: handleSelectRate,
180
+ isLoading: isFetching || isPending,
181
+ error,
182
+ clearError,
183
+ refetch,
203
184
  };
204
185
  };
@@ -16,6 +16,8 @@ export { useOrderBump } from './hooks/useOrderBump';
16
16
  export { usePostPurchases } from './hooks/usePostPurchases';
17
17
  export { useProducts } from './hooks/useProducts';
18
18
  export { useSession } from './hooks/useSession';
19
+ export { useShippingRates } from './hooks/useShippingRates';
20
+ export type { UseShippingRatesOptions, UseShippingRatesResult } from './hooks/useShippingRates';
19
21
  export { useTranslations } from './hooks/useTranslations';
20
22
  export { useVipOffers } from './hooks/useVipOffers';
21
23
  export { useTagadaContext } from './providers/TagadaProvider';
@@ -19,6 +19,7 @@ export { useOrderBump } from './hooks/useOrderBump';
19
19
  export { usePostPurchases } from './hooks/usePostPurchases';
20
20
  export { useProducts } from './hooks/useProducts';
21
21
  export { useSession } from './hooks/useSession';
22
+ export { useShippingRates } from './hooks/useShippingRates';
22
23
  export { useTranslations } from './hooks/useTranslations';
23
24
  export { useVipOffers } from './hooks/useVipOffers';
24
25
  export { useTagadaContext } from './providers/TagadaProvider';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "2.4.4",
3
+ "version": "2.4.7",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",