@tagadapay/plugin-sdk 1.0.10 โ†’ 1.0.12

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.
@@ -273,6 +273,12 @@ export interface UseCheckoutResult {
273
273
  shippingCountryChanged?: boolean;
274
274
  billingCountryChanged?: boolean;
275
275
  }>;
276
+ previewOrderSummary: (orderBumpOfferIds: string[], orderBumpType?: 'primary' | 'secondary' | 'vip') => Promise<{
277
+ savings: number;
278
+ savingsPct: number;
279
+ currency: string;
280
+ error?: any;
281
+ }>;
276
282
  clear: () => void;
277
283
  }
278
284
  export declare function useCheckout(options?: UseCheckoutOptions): UseCheckoutResult;
@@ -3,7 +3,7 @@ import { useTagadaContext } from '../providers/TagadaProvider';
3
3
  import { getCheckoutToken } from '../utils/urlUtils';
4
4
  import { useCurrency } from '../hooks/useCurrency';
5
5
  export function useCheckout(options = {}) {
6
- const { apiService } = useTagadaContext();
6
+ const { apiService, updateCheckoutDebugData, refreshCoordinator } = useTagadaContext();
7
7
  const { code: currentCurrency } = useCurrency();
8
8
  const [checkout, setCheckout] = useState(null);
9
9
  const [isLoading, setIsLoading] = useState(false);
@@ -21,6 +21,38 @@ export function useCheckout(options = {}) {
21
21
  }
22
22
  };
23
23
  }, []);
24
+ // Update debug data whenever checkout state changes with comprehensive information
25
+ useEffect(() => {
26
+ const debugData = checkout
27
+ ? {
28
+ checkout,
29
+ sessionId: checkout.checkoutSession?.id,
30
+ checkoutToken: currentCheckoutTokenRef.current,
31
+ currency: checkout.summary?.currency,
32
+ totalAmount: checkout.summary?.totalAmount,
33
+ totalAdjustedAmount: checkout.summary?.totalAdjustedAmount,
34
+ promotionAmount: checkout.summary?.totalPromotionAmount,
35
+ itemsCount: checkout.summary?.items?.length || 0,
36
+ orderBumps: checkout.checkoutSession?.sessionLineItems?.filter((item) => item.isOrderBump) || [],
37
+ adjustments: checkout.summary?.adjustments || [],
38
+ isInitialized,
39
+ lastUpdated: new Date().toISOString(),
40
+ }
41
+ : null;
42
+ updateCheckoutDebugData(debugData, error, isLoading);
43
+ if (debugData) {
44
+ console.log('๐Ÿ› [useCheckout] Debug data updated for debug drawer', {
45
+ sessionId: debugData.sessionId,
46
+ totalAmount: debugData.totalAmount,
47
+ totalAdjustedAmount: debugData.totalAdjustedAmount,
48
+ promotionAmount: debugData.promotionAmount,
49
+ itemsCount: debugData.itemsCount,
50
+ orderBumpsCount: debugData.orderBumps?.length || 0,
51
+ adjustmentsCount: debugData.adjustments?.length || 0,
52
+ lastUpdated: debugData.lastUpdated,
53
+ });
54
+ }
55
+ }, [checkout, error, isLoading, isInitialized]); // Removed updateCheckoutDebugData from deps to prevent infinite loop
24
56
  const init = useCallback(async (params) => {
25
57
  // Don't allow init if we already have a checkout token
26
58
  if (providedToken) {
@@ -85,6 +117,14 @@ export function useCheckout(options = {}) {
85
117
  setCheckout(response);
86
118
  currentCheckoutTokenRef.current = checkoutToken;
87
119
  setIsInitialized(true);
120
+ console.log('๐Ÿ“Š [useCheckout] Checkout data updated', {
121
+ sessionId: response.checkoutSession?.id,
122
+ totalAmount: response.summary?.totalAmount,
123
+ promotionAmount: response.summary?.totalPromotionAmount,
124
+ itemsCount: response.summary?.items?.length || 0,
125
+ orderBumpsCount: response.checkoutSession?.sessionLineItems?.filter((item) => item.isOrderBump)?.length || 0,
126
+ timestamp: new Date().toISOString(),
127
+ });
88
128
  return response;
89
129
  }
90
130
  catch (err) {
@@ -100,8 +140,20 @@ export function useCheckout(options = {}) {
100
140
  if (!currentCheckoutTokenRef.current) {
101
141
  throw new Error('No checkout session to refresh');
102
142
  }
143
+ console.log('๐Ÿ”„ [useCheckout] Refreshing checkout data...', {
144
+ checkoutToken: currentCheckoutTokenRef.current.substring(0, 8) + '...',
145
+ timestamp: new Date().toISOString(),
146
+ });
103
147
  await getCheckout(currentCheckoutTokenRef.current);
148
+ console.log('โœ… [useCheckout] Refresh completed, debug data will be updated automatically');
104
149
  }, [getCheckout]);
150
+ // Register refresh function with coordinator and cleanup on unmount
151
+ useEffect(() => {
152
+ refreshCoordinator.registerCheckoutRefresh(refresh);
153
+ return () => {
154
+ refreshCoordinator.unregisterCheckoutRefresh();
155
+ };
156
+ }, [refresh, refreshCoordinator]);
105
157
  const updateAddress = useCallback(async (data) => {
106
158
  if (!checkout?.checkoutSession.id) {
107
159
  throw new Error('No checkout session available');
@@ -205,6 +257,8 @@ export function useCheckout(options = {}) {
205
257
  });
206
258
  if (response.success) {
207
259
  await refresh();
260
+ // Notify other hooks that checkout data changed
261
+ await refreshCoordinator.notifyCheckoutChanged();
208
262
  }
209
263
  return response;
210
264
  }
@@ -212,7 +266,7 @@ export function useCheckout(options = {}) {
212
266
  const error = err instanceof Error ? err : new Error('Failed to update line items');
213
267
  throw error;
214
268
  }
215
- }, [apiService, checkout?.checkoutSession.id, refresh]);
269
+ }, [apiService, checkout?.checkoutSession.id, refresh, refreshCoordinator]);
216
270
  const addLineItems = useCallback(async (lineItems) => {
217
271
  if (!checkout?.checkoutSession.id) {
218
272
  throw new Error('No checkout session available');
@@ -280,7 +334,13 @@ export function useCheckout(options = {}) {
280
334
  body: { orderBumpOfferId, selected },
281
335
  });
282
336
  if (response.success) {
337
+ console.log('๐ŸŽฏ [useCheckout] Order bump toggled successfully, refreshing checkout data...', {
338
+ orderBumpOfferId,
339
+ selected,
340
+ timestamp: new Date().toISOString(),
341
+ });
283
342
  await refresh();
343
+ console.log('โœ… [useCheckout] Order bump refresh completed, debug drawer should now show updated data');
284
344
  }
285
345
  return response;
286
346
  }
@@ -325,6 +385,30 @@ export function useCheckout(options = {}) {
325
385
  throw error;
326
386
  }
327
387
  }, [apiService, checkout?.checkoutSession.id, refresh]);
388
+ const previewOrderSummary = useCallback(async (orderBumpOfferIds, orderBumpType = 'vip') => {
389
+ if (!checkout?.checkoutSession.id) {
390
+ throw new Error('No checkout session available');
391
+ }
392
+ try {
393
+ const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkout.checkoutSession.id}/vip-preview`, {
394
+ method: 'POST',
395
+ body: {
396
+ orderBumpOfferIds,
397
+ orderBumpType,
398
+ },
399
+ });
400
+ return response;
401
+ }
402
+ catch (err) {
403
+ const error = err instanceof Error ? err : new Error('Failed to preview order summary');
404
+ return {
405
+ savings: 0,
406
+ savingsPct: 0,
407
+ currency: checkout?.summary?.currency ?? 'EUR',
408
+ error: error.message,
409
+ };
410
+ }
411
+ }, [apiService, checkout?.checkoutSession.id, checkout?.summary?.currency]);
328
412
  const clear = useCallback(() => {
329
413
  setCheckout(null);
330
414
  setError(null);
@@ -380,6 +464,7 @@ export function useCheckout(options = {}) {
380
464
  toggleOrderBump,
381
465
  updateCustomer,
382
466
  updateCustomerAndSessionInfo,
467
+ previewOrderSummary,
383
468
  clear,
384
469
  };
385
470
  }
@@ -0,0 +1,30 @@
1
+ export interface OrderBumpPreview {
2
+ savings: number;
3
+ savingsPct: number;
4
+ currency: string;
5
+ selectedOffers: {
6
+ productId: string | null;
7
+ variantId: string | null;
8
+ isSelected: boolean;
9
+ }[];
10
+ }
11
+ export interface UseOrderBumpOptions {
12
+ checkoutSessionId?: string;
13
+ offerId: string;
14
+ orderBumpType?: 'primary' | 'secondary' | 'vip';
15
+ autoPreview?: boolean;
16
+ }
17
+ export interface UseOrderBumpResult {
18
+ isSelected: boolean;
19
+ preview: OrderBumpPreview | null;
20
+ savings: number | null;
21
+ isLoading: boolean;
22
+ isToggling: boolean;
23
+ error: Error | null;
24
+ toggle: (selected?: boolean) => Promise<{
25
+ success: boolean;
26
+ error?: any;
27
+ }>;
28
+ refreshPreview: () => Promise<void>;
29
+ }
30
+ export declare function useOrderBump(options: UseOrderBumpOptions): UseOrderBumpResult;
@@ -0,0 +1,106 @@
1
+ import { useState, useCallback, useEffect } from 'react';
2
+ import { useTagadaContext } from '../providers/TagadaProvider';
3
+ export function useOrderBump(options) {
4
+ const { apiService, refreshCoordinator } = useTagadaContext();
5
+ const { checkoutSessionId, offerId, orderBumpType = 'vip', autoPreview = true } = options;
6
+ const [isSelected, setIsSelected] = useState(false);
7
+ const [preview, setPreview] = useState(null);
8
+ const [isLoading, setIsLoading] = useState(false);
9
+ const [isToggling, setIsToggling] = useState(false);
10
+ const [error, setError] = useState(null);
11
+ const refreshPreview = useCallback(async () => {
12
+ if (!checkoutSessionId)
13
+ return;
14
+ setIsLoading(true);
15
+ setError(null);
16
+ try {
17
+ const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/vip-preview`, {
18
+ method: 'POST',
19
+ body: {
20
+ orderBumpOfferIds: [offerId],
21
+ orderBumpType,
22
+ },
23
+ });
24
+ setPreview(response);
25
+ // Update isSelected based on preview data
26
+ const offerSelected = response.selectedOffers?.some((offer) => offer.isSelected);
27
+ setIsSelected(offerSelected ?? false);
28
+ }
29
+ catch (err) {
30
+ const error = err instanceof Error ? err : new Error('Failed to fetch preview');
31
+ setError(error);
32
+ console.error('Order bump preview failed:', error);
33
+ }
34
+ finally {
35
+ setIsLoading(false);
36
+ }
37
+ }, [checkoutSessionId, offerId, orderBumpType, apiService]);
38
+ const toggle = useCallback(async (selected) => {
39
+ if (!checkoutSessionId) {
40
+ throw new Error('No checkout session available');
41
+ }
42
+ const targetState = selected ?? !isSelected;
43
+ // Optimistic update
44
+ setIsSelected(targetState);
45
+ setIsToggling(true);
46
+ setError(null);
47
+ try {
48
+ const response = await apiService.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/toggle-order-bump`, {
49
+ method: 'POST',
50
+ body: {
51
+ orderBumpOfferId: offerId,
52
+ selected: targetState,
53
+ },
54
+ });
55
+ if (response.success) {
56
+ // Refresh preview to get updated savings
57
+ await refreshPreview();
58
+ // Notify checkout hook that order bump data changed
59
+ await refreshCoordinator.notifyOrderBumpChanged();
60
+ return { success: true };
61
+ }
62
+ else {
63
+ // Revert optimistic update
64
+ setIsSelected(!targetState);
65
+ return { success: false, error: response.error };
66
+ }
67
+ }
68
+ catch (err) {
69
+ // Revert optimistic update
70
+ setIsSelected(!targetState);
71
+ const error = err instanceof Error ? err : new Error('Failed to toggle order bump');
72
+ setError(error);
73
+ return { success: false, error: error.message };
74
+ }
75
+ finally {
76
+ setIsToggling(false);
77
+ }
78
+ }, [checkoutSessionId, offerId, isSelected, apiService, refreshPreview, refreshCoordinator]);
79
+ // Auto-fetch preview on mount and when dependencies change
80
+ useEffect(() => {
81
+ if (autoPreview && checkoutSessionId) {
82
+ refreshPreview().catch((error) => {
83
+ console.error('Auto-preview failed:', error);
84
+ });
85
+ }
86
+ }, [autoPreview, refreshPreview]);
87
+ // Register refresh function with coordinator and cleanup on unmount
88
+ useEffect(() => {
89
+ refreshCoordinator.registerOrderBumpRefresh(refreshPreview);
90
+ return () => {
91
+ refreshCoordinator.unregisterOrderBumpRefresh();
92
+ };
93
+ }, [refreshPreview, refreshCoordinator]);
94
+ // Calculate current savings
95
+ const savings = isSelected && preview?.savings ? preview.savings : preview?.savings || null;
96
+ return {
97
+ isSelected,
98
+ preview,
99
+ savings,
100
+ isLoading,
101
+ isToggling,
102
+ error,
103
+ toggle,
104
+ refreshPreview,
105
+ };
106
+ }
@@ -5,6 +5,7 @@ export { TagadaProvider } from './providers/TagadaProvider';
5
5
  export { useCheckout } from './hooks/useCheckout';
6
6
  export { useProducts } from './hooks/useProducts';
7
7
  export { useOffers } from './hooks/useOffers';
8
+ export { useOrderBump } from './hooks/useOrderBump';
8
9
  export { useSession } from './hooks/useSession';
9
10
  export { useCurrency } from './hooks/useCurrency';
10
11
  export { useCustomer } from './hooks/useCustomer';
@@ -19,6 +20,7 @@ export { useThreeds } from './hooks/useThreeds';
19
20
  export { useThreedsModal } from './hooks/useThreedsModal';
20
21
  export type { Customer, Session, AuthState, Locale, Currency, Store, Environment, EnvironmentConfig, Order, OrderItem, OrderSummary, OrderAddress, PickupPoint, } from './types';
21
22
  export type { UseCheckoutOptions, UseCheckoutResult, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutData, Promotion, } from './hooks/useCheckout';
23
+ export type { UseOrderBumpOptions, UseOrderBumpResult, OrderBumpPreview } from './hooks/useOrderBump';
22
24
  export type { Payment, PollingOptions, PaymentPollingHook } from './hooks/usePaymentPolling';
23
25
  export type { PaymentInstrument, ThreedsSession, ThreedsChallenge, ThreedsOptions, ThreedsHook, ThreedsProvider, } from './hooks/useThreeds';
24
26
  export type { ApplePayToken, CardPaymentMethod, PaymentResponse, PaymentOptions, PaymentInstrumentResponse, PaymentHook, } from './hooks/usePayment';
@@ -8,6 +8,7 @@ export { TagadaProvider } from './providers/TagadaProvider';
8
8
  export { useCheckout } from './hooks/useCheckout';
9
9
  export { useProducts } from './hooks/useProducts';
10
10
  export { useOffers } from './hooks/useOffers';
11
+ export { useOrderBump } from './hooks/useOrderBump';
11
12
  export { useSession } from './hooks/useSession';
12
13
  export { useCurrency } from './hooks/useCurrency';
13
14
  export { useCustomer } from './hooks/useCustomer';
@@ -25,6 +25,14 @@ interface TagadaContextValue {
25
25
  lastUpdated: Date | null;
26
26
  };
27
27
  updateCheckoutDebugData: (data: any, error?: Error | null, isLoading?: boolean) => void;
28
+ refreshCoordinator: {
29
+ registerCheckoutRefresh: (refreshFn: () => Promise<void>) => void;
30
+ registerOrderBumpRefresh: (refreshFn: () => Promise<void>) => void;
31
+ notifyCheckoutChanged: () => Promise<void>;
32
+ notifyOrderBumpChanged: () => Promise<void>;
33
+ unregisterCheckoutRefresh: () => void;
34
+ unregisterOrderBumpRefresh: () => void;
35
+ };
28
36
  money: {
29
37
  formatMoney: typeof formatMoney;
30
38
  getCurrencyInfo: typeof getCurrencyInfo;
@@ -39,17 +47,11 @@ interface TagadaProviderProps {
39
47
  children: ReactNode;
40
48
  environment?: Environment;
41
49
  customApiConfig?: Partial<EnvironmentConfig>;
42
- developmentMode?: boolean;
43
50
  debugMode?: boolean;
44
51
  storeId?: string;
45
52
  accountId?: string;
46
- mockData?: {
47
- customer?: Partial<Customer>;
48
- session?: Partial<Session>;
49
- store?: Partial<Store>;
50
- };
51
53
  }
52
- export declare function TagadaProvider({ children, environment, customApiConfig, developmentMode, debugMode, // Remove default, will be set based on environment
53
- storeId, accountId, mockData, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
54
+ export declare function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
55
+ storeId, accountId, }: TagadaProviderProps): import("react/jsx-runtime").JSX.Element;
54
56
  export declare function useTagadaContext(): TagadaContextValue;
55
57
  export {};
@@ -29,8 +29,8 @@ const InitializationLoader = () => (_jsx("div", { style: {
29
29
  }
30
30
  ` }) }));
31
31
  const TagadaContext = createContext(null);
32
- export function TagadaProvider({ children, environment, customApiConfig, developmentMode = false, debugMode, // Remove default, will be set based on environment
33
- storeId, accountId, mockData, }) {
32
+ export function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
33
+ storeId, accountId, }) {
34
34
  const [isLoading, setIsLoading] = useState(true);
35
35
  const [isInitialized, setIsInitialized] = useState(false);
36
36
  const [token, setToken] = useState(null);
@@ -284,49 +284,6 @@ storeId, accountId, mockData, }) {
284
284
  try {
285
285
  console.debug('[SDK] Initializing token...');
286
286
  setIsLoading(true);
287
- if (developmentMode) {
288
- console.debug('[SDK] Development mode: Using mock data');
289
- // Use mock data in development mode
290
- if (mockData?.customer) {
291
- setCustomer({
292
- id: 'dev-customer-123',
293
- email: 'dev@example.com',
294
- firstName: 'John',
295
- lastName: 'Doe',
296
- phone: '+1234567890',
297
- isAuthenticated: true,
298
- role: 'authenticated',
299
- ...mockData.customer,
300
- });
301
- }
302
- if (mockData?.session) {
303
- setSession({
304
- sessionId: 'dev-session-123',
305
- storeId: 'dev-store-123',
306
- accountId: 'dev-account-123',
307
- customerId: 'dev-customer-123',
308
- role: 'authenticated',
309
- isValid: true,
310
- isLoading: false,
311
- ...mockData.session,
312
- });
313
- }
314
- if (mockData?.store) {
315
- setStore({
316
- id: 'dev-store-123',
317
- name: 'Development Store',
318
- domain: 'dev.localhost',
319
- currency: 'USD',
320
- locale: 'en-US',
321
- presentmentCurrencies: ['USD'],
322
- chargeCurrencies: ['USD'],
323
- ...mockData.store,
324
- });
325
- }
326
- setIsInitialized(true);
327
- setIsLoading(false);
328
- return;
329
- }
330
287
  // Check for existing token
331
288
  const existingToken = getClientToken();
332
289
  let tokenToUse = null;
@@ -374,7 +331,7 @@ storeId, accountId, mockData, }) {
374
331
  }
375
332
  };
376
333
  void initializeToken();
377
- }, [developmentMode, mockData, storeId, createAnonymousToken, initializeSession]);
334
+ }, [storeId, createAnonymousToken, initializeSession]);
378
335
  // Update auth state when customer/session changes
379
336
  useEffect(() => {
380
337
  setAuth({
@@ -384,6 +341,51 @@ storeId, accountId, mockData, }) {
384
341
  session,
385
342
  });
386
343
  }, [customer, session]);
344
+ // Refresh coordinator for bidirectional hook communication
345
+ const checkoutRefreshRef = useRef(null);
346
+ const orderBumpRefreshRef = useRef(null);
347
+ const refreshCoordinator = {
348
+ registerCheckoutRefresh: (refreshFn) => {
349
+ checkoutRefreshRef.current = refreshFn;
350
+ console.log('๐Ÿ”„ [RefreshCoordinator] Checkout refresh function registered');
351
+ },
352
+ registerOrderBumpRefresh: (refreshFn) => {
353
+ orderBumpRefreshRef.current = refreshFn;
354
+ console.log('๐Ÿ”„ [RefreshCoordinator] Order bump refresh function registered');
355
+ },
356
+ notifyCheckoutChanged: async () => {
357
+ if (orderBumpRefreshRef.current) {
358
+ console.log('๐Ÿ”„ [RefreshCoordinator] Checkout changed, refreshing order bump data...');
359
+ try {
360
+ await orderBumpRefreshRef.current();
361
+ console.log('โœ… [RefreshCoordinator] Order bump refresh completed');
362
+ }
363
+ catch (error) {
364
+ console.warn('โš ๏ธ [RefreshCoordinator] Order bump refresh failed:', error);
365
+ }
366
+ }
367
+ },
368
+ notifyOrderBumpChanged: async () => {
369
+ if (checkoutRefreshRef.current) {
370
+ console.log('๐Ÿ”„ [RefreshCoordinator] Order bump changed, refreshing checkout data...');
371
+ try {
372
+ await checkoutRefreshRef.current();
373
+ console.log('โœ… [RefreshCoordinator] Checkout refresh completed');
374
+ }
375
+ catch (error) {
376
+ console.warn('โš ๏ธ [RefreshCoordinator] Checkout refresh failed:', error);
377
+ }
378
+ }
379
+ },
380
+ unregisterCheckoutRefresh: () => {
381
+ checkoutRefreshRef.current = null;
382
+ console.log('๐Ÿงน [RefreshCoordinator] Checkout refresh function unregistered');
383
+ },
384
+ unregisterOrderBumpRefresh: () => {
385
+ orderBumpRefreshRef.current = null;
386
+ console.log('๐Ÿงน [RefreshCoordinator] Order bump refresh function unregistered');
387
+ },
388
+ };
387
389
  const contextValue = {
388
390
  auth,
389
391
  session,
@@ -406,6 +408,7 @@ storeId, accountId, mockData, }) {
406
408
  lastUpdated: new Date(),
407
409
  });
408
410
  },
411
+ refreshCoordinator,
409
412
  money: {
410
413
  formatMoney,
411
414
  getCurrencyInfo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagadapay/plugin-sdk",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Modern React SDK for building Tagada Pay plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",