@tagadapay/plugin-sdk 2.5.2 โ 2.6.2
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/dist/react/hooks/useCustomerInfos.d.ts +15 -0
- package/dist/react/hooks/useCustomerInfos.js +54 -0
- package/dist/react/hooks/useCustomerOrders.d.ts +14 -0
- package/dist/react/hooks/useCustomerOrders.js +51 -0
- package/dist/react/hooks/useCustomerSubscriptions.d.ts +56 -0
- package/dist/react/hooks/useCustomerSubscriptions.js +77 -0
- package/dist/react/hooks/useLogin.js +1 -1
- package/dist/react/index.d.ts +4 -1
- package/dist/react/index.js +3 -0
- package/dist/react/providers/TagadaProvider.js +64 -52
- package/dist/react/types.d.ts +109 -0
- package/dist/react/utils/tokenStorage.js +1 -0
- package/dist/v2/core/resources/checkout.d.ts +1 -0
- package/dist/v2/core/resources/checkout.js +1 -0
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +1 -0
- package/dist/v2/react/hooks/useCheckoutQuery.js +79 -4
- package/dist/v2/react/hooks/useOrderBumpQuery.d.ts +0 -1
- package/dist/v2/react/hooks/useOrderBumpQuery.js +6 -5
- package/dist/v2/react/hooks/usePluginConfig.d.ts +2 -0
- package/dist/v2/react/hooks/usePluginConfig.js +3 -1
- package/dist/v2/react/providers/TagadaProvider.js +69 -8
- package/package.json +2 -2
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CustomerInfos } from '../types';
|
|
2
|
+
export interface UseCustomerInfosOptions {
|
|
3
|
+
customerId?: string | null;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface UseCustomerInfosResult {
|
|
7
|
+
data: CustomerInfos | null;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
error: Error | null;
|
|
10
|
+
refetch: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* useCustomerInfos - Fetches customer infos from `/api/v1/customers/{customerId}` with `storeId` param
|
|
14
|
+
*/
|
|
15
|
+
export declare function useCustomerInfos(options: UseCustomerInfosOptions): UseCustomerInfosResult;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
4
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
5
|
+
/**
|
|
6
|
+
* useCustomerInfos - Fetches customer infos from `/api/v1/customers/{customerId}` with `storeId` param
|
|
7
|
+
*/
|
|
8
|
+
export function useCustomerInfos(options) {
|
|
9
|
+
const { apiService } = useTagadaContext();
|
|
10
|
+
const { storeId } = usePluginConfig();
|
|
11
|
+
const stableOptions = useMemo(() => {
|
|
12
|
+
return {
|
|
13
|
+
customerId: options.customerId ?? null,
|
|
14
|
+
enabled: options.enabled ?? true,
|
|
15
|
+
};
|
|
16
|
+
}, [options.customerId, options.enabled]);
|
|
17
|
+
const isEnabled = useMemo(() => {
|
|
18
|
+
return Boolean(stableOptions.enabled && stableOptions.customerId && storeId);
|
|
19
|
+
}, [stableOptions.enabled, stableOptions.customerId, storeId]);
|
|
20
|
+
const [data, setData] = useState(null);
|
|
21
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
22
|
+
const [error, setError] = useState(null);
|
|
23
|
+
const fetchCustomerInfos = useCallback(async () => {
|
|
24
|
+
if (!isEnabled)
|
|
25
|
+
return;
|
|
26
|
+
if (!stableOptions.customerId || !storeId)
|
|
27
|
+
return;
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
setError(null);
|
|
30
|
+
try {
|
|
31
|
+
const response = await apiService.fetch(`/api/v1/customers/${stableOptions.customerId}`, {
|
|
32
|
+
method: 'GET',
|
|
33
|
+
params: { storeId },
|
|
34
|
+
});
|
|
35
|
+
setData(response ?? null);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const safeError = err instanceof Error ? err : new Error('Failed to fetch customer infos');
|
|
39
|
+
setError(safeError);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
setIsLoading(false);
|
|
43
|
+
}
|
|
44
|
+
}, [apiService, isEnabled, stableOptions.customerId, storeId]);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
void fetchCustomerInfos();
|
|
47
|
+
}, [fetchCustomerInfos]);
|
|
48
|
+
return {
|
|
49
|
+
data,
|
|
50
|
+
isLoading,
|
|
51
|
+
error,
|
|
52
|
+
refetch: fetchCustomerInfos,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OrderWithRelations } from '../types';
|
|
2
|
+
export interface UseCustomerOrdersOptions {
|
|
3
|
+
customerId?: string | null;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface UseCustomerOrdersResult {
|
|
7
|
+
data: {
|
|
8
|
+
orders: OrderWithRelations[];
|
|
9
|
+
} | null;
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
error: Error | null;
|
|
12
|
+
refetch: () => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare function useCustomerOrders(options: UseCustomerOrdersOptions): UseCustomerOrdersResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
4
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
5
|
+
export function useCustomerOrders(options) {
|
|
6
|
+
const { apiService } = useTagadaContext();
|
|
7
|
+
const { storeId } = usePluginConfig();
|
|
8
|
+
const stableOptions = useMemo(() => {
|
|
9
|
+
return {
|
|
10
|
+
customerId: options.customerId ?? null,
|
|
11
|
+
enabled: options.enabled ?? true,
|
|
12
|
+
};
|
|
13
|
+
}, [options.customerId, options.enabled]);
|
|
14
|
+
const isEnabled = useMemo(() => {
|
|
15
|
+
return Boolean(stableOptions.enabled && stableOptions.customerId && storeId);
|
|
16
|
+
}, [stableOptions.enabled, stableOptions.customerId, storeId]);
|
|
17
|
+
const [data, setData] = useState(null);
|
|
18
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const fetchOrders = useCallback(async () => {
|
|
21
|
+
if (!isEnabled)
|
|
22
|
+
return;
|
|
23
|
+
if (!stableOptions.customerId || !storeId)
|
|
24
|
+
return;
|
|
25
|
+
setIsLoading(true);
|
|
26
|
+
setError(null);
|
|
27
|
+
try {
|
|
28
|
+
const response = await apiService.fetch(`/api/v1/orders/customer/${stableOptions.customerId}`, {
|
|
29
|
+
method: 'GET',
|
|
30
|
+
params: { storeId },
|
|
31
|
+
});
|
|
32
|
+
setData(response ?? null);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const safeError = err instanceof Error ? err : new Error('Failed to fetch customer orders');
|
|
36
|
+
setError(safeError);
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
setIsLoading(false);
|
|
40
|
+
}
|
|
41
|
+
}, [apiService, isEnabled, stableOptions.customerId, storeId]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
void fetchOrders();
|
|
44
|
+
}, [fetchOrders]);
|
|
45
|
+
return {
|
|
46
|
+
data,
|
|
47
|
+
isLoading,
|
|
48
|
+
error,
|
|
49
|
+
refetch: fetchOrders,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface Subscription {
|
|
2
|
+
id: string;
|
|
3
|
+
status: string;
|
|
4
|
+
createdAt: string;
|
|
5
|
+
currency: string;
|
|
6
|
+
cancelAtPeriodEnd: boolean;
|
|
7
|
+
currentPeriodEnd: string | null;
|
|
8
|
+
currentPeriodStart: string | null;
|
|
9
|
+
quantity: number;
|
|
10
|
+
trialEnd: string | null;
|
|
11
|
+
customerId: string;
|
|
12
|
+
customerEmail: string;
|
|
13
|
+
customerName: string;
|
|
14
|
+
priceCurrencyOptions: Record<string, {
|
|
15
|
+
rate: number;
|
|
16
|
+
amount: number;
|
|
17
|
+
lock: boolean;
|
|
18
|
+
date: string;
|
|
19
|
+
}>;
|
|
20
|
+
priceInterval: string;
|
|
21
|
+
priceIntervalCount: number;
|
|
22
|
+
priceRecurring: boolean;
|
|
23
|
+
productId: string;
|
|
24
|
+
priceId: string;
|
|
25
|
+
productTitle: string;
|
|
26
|
+
}
|
|
27
|
+
export interface SubscriptionsResponse {
|
|
28
|
+
items: Subscription[];
|
|
29
|
+
pagination: {
|
|
30
|
+
page: number;
|
|
31
|
+
pageSize: number;
|
|
32
|
+
hasNext: boolean;
|
|
33
|
+
nextPage: number | null;
|
|
34
|
+
previousPage: number | null;
|
|
35
|
+
totalItems: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface UseCustomerSubscriptionsOptions {
|
|
39
|
+
customerId?: string | null;
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface UseCustomerSubscriptionsResult {
|
|
43
|
+
data: SubscriptionsResponse | null;
|
|
44
|
+
isLoading: boolean;
|
|
45
|
+
error: Error | null;
|
|
46
|
+
refetch: () => Promise<void>;
|
|
47
|
+
resumeSubscription: (subscriptionId: string) => Promise<{
|
|
48
|
+
success: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
}>;
|
|
51
|
+
cancelSubscription: (subscriptionId: string) => Promise<{
|
|
52
|
+
success: boolean;
|
|
53
|
+
error?: string;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
export declare function useCustomerSubscriptions(options: UseCustomerSubscriptionsOptions): UseCustomerSubscriptionsResult;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
4
|
+
export function useCustomerSubscriptions(options) {
|
|
5
|
+
const { apiService } = useTagadaContext();
|
|
6
|
+
const stableOptions = useMemo(() => {
|
|
7
|
+
return {
|
|
8
|
+
customerId: options.customerId ?? null,
|
|
9
|
+
enabled: options.enabled ?? true,
|
|
10
|
+
};
|
|
11
|
+
}, [options.customerId, options.enabled]);
|
|
12
|
+
const isEnabled = useMemo(() => {
|
|
13
|
+
return Boolean(stableOptions.enabled && stableOptions.customerId);
|
|
14
|
+
}, [stableOptions.enabled, stableOptions.customerId]);
|
|
15
|
+
const [data, setData] = useState(null);
|
|
16
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const fetchSubscriptions = useCallback(async () => {
|
|
19
|
+
if (!isEnabled)
|
|
20
|
+
return;
|
|
21
|
+
setIsLoading(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
// Token-authenticated request; backend infers customer from token
|
|
25
|
+
const response = await apiService.fetch(`/api/v1/subscriptions`, {
|
|
26
|
+
method: 'GET',
|
|
27
|
+
});
|
|
28
|
+
setData(response ?? null);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const safeError = err instanceof Error ? err : new Error('Failed to fetch subscriptions');
|
|
32
|
+
setError(safeError);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
setIsLoading(false);
|
|
36
|
+
}
|
|
37
|
+
}, [apiService, isEnabled]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
void fetchSubscriptions();
|
|
40
|
+
}, [fetchSubscriptions]);
|
|
41
|
+
const resumeSubscription = useCallback(async (subscriptionId) => {
|
|
42
|
+
try {
|
|
43
|
+
await apiService.fetch(`/api/v1/subscriptions/resume`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body: { subscriptionId },
|
|
46
|
+
});
|
|
47
|
+
await fetchSubscriptions();
|
|
48
|
+
return { success: true };
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to resume subscription';
|
|
52
|
+
return { success: false, error: errorMessage };
|
|
53
|
+
}
|
|
54
|
+
}, [apiService, fetchSubscriptions]);
|
|
55
|
+
const cancelSubscription = useCallback(async (subscriptionId) => {
|
|
56
|
+
try {
|
|
57
|
+
await apiService.fetch(`/api/v1/subscriptions/cancel`, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
body: { subscriptionId },
|
|
60
|
+
});
|
|
61
|
+
await fetchSubscriptions();
|
|
62
|
+
return { success: true };
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to cancel subscription';
|
|
66
|
+
return { success: false, error: errorMessage };
|
|
67
|
+
}
|
|
68
|
+
}, [apiService, fetchSubscriptions]);
|
|
69
|
+
return {
|
|
70
|
+
data,
|
|
71
|
+
isLoading,
|
|
72
|
+
error,
|
|
73
|
+
refetch: fetchSubscriptions,
|
|
74
|
+
resumeSubscription,
|
|
75
|
+
cancelSubscription,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
2
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
3
|
-
import { usePluginConfig } from './usePluginConfig';
|
|
4
3
|
import { setClientToken } from '../utils/tokenStorage';
|
|
4
|
+
import { usePluginConfig } from './usePluginConfig';
|
|
5
5
|
export function useLogin() {
|
|
6
6
|
const [isLoading, setIsLoading] = useState(false);
|
|
7
7
|
const [error, setError] = useState(null);
|
package/dist/react/index.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export { useCheckout } from './hooks/useCheckout';
|
|
|
7
7
|
export { useClubOffers } from './hooks/useClubOffers';
|
|
8
8
|
export { useCurrency } from './hooks/useCurrency';
|
|
9
9
|
export { useCustomer } from './hooks/useCustomer';
|
|
10
|
+
export { useCustomerInfos } from './hooks/useCustomerInfos';
|
|
11
|
+
export { useCustomerOrders } from './hooks/useCustomerOrders';
|
|
12
|
+
export { useCustomerSubscriptions } from './hooks/useCustomerSubscriptions';
|
|
10
13
|
export { useDiscounts } from './hooks/useDiscounts';
|
|
11
14
|
export { useEnvironment } from './hooks/useEnvironment';
|
|
12
15
|
export { useGeoLocation } from './hooks/useGeoLocation';
|
|
@@ -41,7 +44,7 @@ export { useThreeds } from './hooks/useThreeds';
|
|
|
41
44
|
export { useThreedsModal } from './hooks/useThreedsModal';
|
|
42
45
|
export { useApplePay } from './hooks/useApplePay';
|
|
43
46
|
export { ExpressPaymentProvider, useExpressPayment } from './hooks/useExpressPayment';
|
|
44
|
-
export type { AuthState, Currency, Customer, Environment, EnvironmentConfig, Locale, Order, OrderAddress, OrderItem, OrderSummary, PickupPoint, Session, Store } from './types';
|
|
47
|
+
export type { AuthState, Currency, Customer, CustomerInfos, Environment, EnvironmentConfig, Locale, Order, OrderAddress, OrderItem, OrderSummary, PickupPoint, Session, Store } from './types';
|
|
45
48
|
export type { CheckoutData, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutSessionPreview, Promotion, UseCheckoutOptions, UseCheckoutResult } from './hooks/useCheckout';
|
|
46
49
|
export type { Discount, DiscountCodeValidation, UseDiscountsOptions, UseDiscountsResult } from './hooks/useDiscounts';
|
|
47
50
|
export type { OrderBumpPreview, UseOrderBumpOptions, UseOrderBumpResult } from './hooks/useOrderBump';
|
package/dist/react/index.js
CHANGED
|
@@ -10,6 +10,9 @@ export { useCheckout } from './hooks/useCheckout';
|
|
|
10
10
|
export { useClubOffers } from './hooks/useClubOffers';
|
|
11
11
|
export { useCurrency } from './hooks/useCurrency';
|
|
12
12
|
export { useCustomer } from './hooks/useCustomer';
|
|
13
|
+
export { useCustomerInfos } from './hooks/useCustomerInfos';
|
|
14
|
+
export { useCustomerOrders } from './hooks/useCustomerOrders';
|
|
15
|
+
export { useCustomerSubscriptions } from './hooks/useCustomerSubscriptions';
|
|
13
16
|
export { useDiscounts } from './hooks/useDiscounts';
|
|
14
17
|
export { useEnvironment } from './hooks/useEnvironment';
|
|
15
18
|
export { useGeoLocation } from './hooks/useGeoLocation';
|
|
@@ -362,6 +362,58 @@ rawPluginConfig, }) {
|
|
|
362
362
|
setIsLoading(false);
|
|
363
363
|
}
|
|
364
364
|
}, [apiService, hasAttemptedAnonymousToken, initializeSession, finalDebugMode]);
|
|
365
|
+
// Initialize token from storage or create anonymous token (extracted to stable callback)
|
|
366
|
+
const initializeToken = useCallback(async () => {
|
|
367
|
+
try {
|
|
368
|
+
console.debug('[SDK] Initializing token...');
|
|
369
|
+
setIsLoading(true);
|
|
370
|
+
// Check for existing token
|
|
371
|
+
const existingToken = getClientToken();
|
|
372
|
+
let tokenToUse = null;
|
|
373
|
+
// Check URL params for token
|
|
374
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
375
|
+
const queryToken = urlParams.get('token');
|
|
376
|
+
if (queryToken) {
|
|
377
|
+
console.debug('[SDK] Found token in URL params');
|
|
378
|
+
tokenToUse = queryToken;
|
|
379
|
+
setClientToken(queryToken);
|
|
380
|
+
}
|
|
381
|
+
else if (existingToken && !isTokenExpired(existingToken)) {
|
|
382
|
+
console.debug('[SDK] Using existing token from storage');
|
|
383
|
+
tokenToUse = existingToken;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
console.debug('[SDK] No valid token found');
|
|
387
|
+
// Determine storeId for anonymous token
|
|
388
|
+
const targetStoreId = storeId || 'default-store';
|
|
389
|
+
await createAnonymousToken(targetStoreId);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (tokenToUse) {
|
|
393
|
+
setToken(tokenToUse);
|
|
394
|
+
// Update the API service with the token
|
|
395
|
+
apiService.updateToken(tokenToUse);
|
|
396
|
+
// Decode token to get session data
|
|
397
|
+
const decodedSession = decodeJWTClient(tokenToUse);
|
|
398
|
+
if (decodedSession) {
|
|
399
|
+
setSession(decodedSession);
|
|
400
|
+
// Initialize session with API call
|
|
401
|
+
await initializeSession(decodedSession);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
console.error('[SDK] Failed to decode token');
|
|
405
|
+
setIsInitialized(true);
|
|
406
|
+
setIsSessionInitialized(false); // Session failed to initialize
|
|
407
|
+
setIsLoading(false);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
console.error('[SDK] Error initializing token:', error);
|
|
413
|
+
setIsInitialized(true);
|
|
414
|
+
setIsLoading(false);
|
|
415
|
+
}
|
|
416
|
+
}, [apiService, storeId, createAnonymousToken, initializeSession]);
|
|
365
417
|
// Initialize token from storage or create anonymous token
|
|
366
418
|
// This runs in the background after phases 1 & 2 complete, but doesn't block rendering
|
|
367
419
|
useEffect(() => {
|
|
@@ -373,59 +425,19 @@ rawPluginConfig, }) {
|
|
|
373
425
|
return;
|
|
374
426
|
}
|
|
375
427
|
isInitializing.current = true;
|
|
376
|
-
const initializeToken = async () => {
|
|
377
|
-
try {
|
|
378
|
-
console.debug('[SDK] Initializing token...');
|
|
379
|
-
setIsLoading(true);
|
|
380
|
-
// Check for existing token
|
|
381
|
-
const existingToken = getClientToken();
|
|
382
|
-
let tokenToUse = null;
|
|
383
|
-
// Check URL params for token
|
|
384
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
385
|
-
const queryToken = urlParams.get('token');
|
|
386
|
-
if (queryToken) {
|
|
387
|
-
console.debug('[SDK] Found token in URL params');
|
|
388
|
-
tokenToUse = queryToken;
|
|
389
|
-
setClientToken(queryToken);
|
|
390
|
-
}
|
|
391
|
-
else if (existingToken && !isTokenExpired(existingToken)) {
|
|
392
|
-
console.debug('[SDK] Using existing token from storage');
|
|
393
|
-
tokenToUse = existingToken;
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
console.debug('[SDK] No valid token found');
|
|
397
|
-
// Determine storeId for anonymous token
|
|
398
|
-
const targetStoreId = storeId || 'default-store';
|
|
399
|
-
await createAnonymousToken(targetStoreId);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (tokenToUse) {
|
|
403
|
-
setToken(tokenToUse);
|
|
404
|
-
// Update the API service with the token
|
|
405
|
-
apiService.updateToken(tokenToUse);
|
|
406
|
-
// Decode token to get session data
|
|
407
|
-
const decodedSession = decodeJWTClient(tokenToUse);
|
|
408
|
-
if (decodedSession) {
|
|
409
|
-
setSession(decodedSession);
|
|
410
|
-
// Initialize session with API call
|
|
411
|
-
await initializeSession(decodedSession);
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
console.error('[SDK] Failed to decode token');
|
|
415
|
-
setIsInitialized(true);
|
|
416
|
-
setIsSessionInitialized(false); // Session failed to initialize
|
|
417
|
-
setIsLoading(false);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
catch (error) {
|
|
422
|
-
console.error('[SDK] Error initializing token:', error);
|
|
423
|
-
setIsInitialized(true);
|
|
424
|
-
setIsLoading(false);
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
428
|
void initializeToken();
|
|
428
|
-
}, [storeId,
|
|
429
|
+
}, [storeId, configLoading, initializeToken]);
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
function onStorage() {
|
|
432
|
+
// Re-run initialization when token may have changed in another tab
|
|
433
|
+
isInitializing.current = false;
|
|
434
|
+
void initializeToken();
|
|
435
|
+
}
|
|
436
|
+
window.addEventListener('storage', onStorage);
|
|
437
|
+
return () => {
|
|
438
|
+
window.removeEventListener('storage', onStorage);
|
|
439
|
+
};
|
|
440
|
+
}, [initializeToken]);
|
|
429
441
|
// Update auth state when customer/session changes
|
|
430
442
|
useEffect(() => {
|
|
431
443
|
setAuth({
|
package/dist/react/types.d.ts
CHANGED
|
@@ -150,3 +150,112 @@ export interface Order {
|
|
|
150
150
|
};
|
|
151
151
|
relatedOrders?: Order[];
|
|
152
152
|
}
|
|
153
|
+
export interface PaymentSummary {
|
|
154
|
+
id: string;
|
|
155
|
+
status: string;
|
|
156
|
+
amount: number;
|
|
157
|
+
currency: string;
|
|
158
|
+
createdAt: string;
|
|
159
|
+
updatedAt?: string;
|
|
160
|
+
provider?: string;
|
|
161
|
+
metadata?: Record<string, any>;
|
|
162
|
+
}
|
|
163
|
+
export interface PromotionSummary {
|
|
164
|
+
id: string;
|
|
165
|
+
code?: string | null;
|
|
166
|
+
type?: string;
|
|
167
|
+
amount?: number;
|
|
168
|
+
description?: string | null;
|
|
169
|
+
}
|
|
170
|
+
export interface OrderAdjustmentSummary {
|
|
171
|
+
type: string;
|
|
172
|
+
amount: number;
|
|
173
|
+
description: string;
|
|
174
|
+
}
|
|
175
|
+
export interface OrderWithRelations extends Order {
|
|
176
|
+
customer?: Customer;
|
|
177
|
+
store?: Store;
|
|
178
|
+
account?: {
|
|
179
|
+
id: string;
|
|
180
|
+
name?: string;
|
|
181
|
+
} | undefined;
|
|
182
|
+
items: OrderItem[];
|
|
183
|
+
payments?: PaymentSummary[];
|
|
184
|
+
summaries: OrderSummary[];
|
|
185
|
+
checkoutSession?: {
|
|
186
|
+
id?: string;
|
|
187
|
+
returnUrl?: string;
|
|
188
|
+
[key: string]: any;
|
|
189
|
+
};
|
|
190
|
+
promotions?: PromotionSummary[];
|
|
191
|
+
subscriptions?: any[];
|
|
192
|
+
adjustments: OrderAdjustmentSummary[];
|
|
193
|
+
}
|
|
194
|
+
export interface CustomerAddress {
|
|
195
|
+
company?: string;
|
|
196
|
+
firstName: string;
|
|
197
|
+
lastName: string;
|
|
198
|
+
address1: string;
|
|
199
|
+
city: string;
|
|
200
|
+
country: string;
|
|
201
|
+
state: string;
|
|
202
|
+
postal: string;
|
|
203
|
+
phone?: string;
|
|
204
|
+
email?: string;
|
|
205
|
+
}
|
|
206
|
+
export interface CustomerOrderSummary {
|
|
207
|
+
id: string;
|
|
208
|
+
storeId: string;
|
|
209
|
+
accountId: string;
|
|
210
|
+
createdAt: string;
|
|
211
|
+
updatedAt: string;
|
|
212
|
+
status: string;
|
|
213
|
+
cancelledAt: string | null;
|
|
214
|
+
cancelledReason: string | null;
|
|
215
|
+
paidAt: string | null;
|
|
216
|
+
paidAmount: number | null;
|
|
217
|
+
openAt: string | null;
|
|
218
|
+
abandonedAt: string | null;
|
|
219
|
+
currency: string;
|
|
220
|
+
externalCustomerType: string | null;
|
|
221
|
+
externalCustomerId: string | null;
|
|
222
|
+
externalOrderId: string | null;
|
|
223
|
+
billingAddress: CustomerAddress;
|
|
224
|
+
shippingAddress: Omit<CustomerAddress, 'email'>;
|
|
225
|
+
pickupAddress: any | null;
|
|
226
|
+
taxesIncluded: boolean;
|
|
227
|
+
draft: boolean;
|
|
228
|
+
checkoutSessionId: string | null;
|
|
229
|
+
sessionHash: string | null;
|
|
230
|
+
customerId: string;
|
|
231
|
+
createdFrom: string | null;
|
|
232
|
+
paymentInstrumentId: string | null;
|
|
233
|
+
refundedAt: string | null;
|
|
234
|
+
refundedAmount: number | null;
|
|
235
|
+
metadata?: Record<string, any>;
|
|
236
|
+
}
|
|
237
|
+
export interface CustomerInfos {
|
|
238
|
+
customer: {
|
|
239
|
+
id: string;
|
|
240
|
+
email: string | null;
|
|
241
|
+
firstName: string | null;
|
|
242
|
+
lastName: string | null;
|
|
243
|
+
externalCustomerId: string | null;
|
|
244
|
+
lastOrderId: string | null;
|
|
245
|
+
accountId: string;
|
|
246
|
+
storeId: string;
|
|
247
|
+
billingAddress: CustomerAddress | null;
|
|
248
|
+
shippingAddress: Omit<CustomerAddress, 'email'> | null;
|
|
249
|
+
currency: string | null;
|
|
250
|
+
locale: string | null;
|
|
251
|
+
draft: boolean;
|
|
252
|
+
acceptsMarketing: boolean;
|
|
253
|
+
createdAt: string;
|
|
254
|
+
updatedAt: string;
|
|
255
|
+
metadata: Record<string, any>;
|
|
256
|
+
device: any | null;
|
|
257
|
+
orders: CustomerOrderSummary[];
|
|
258
|
+
subscriptions: any[];
|
|
259
|
+
};
|
|
260
|
+
promotionCodes: any[];
|
|
261
|
+
}
|
|
@@ -10,6 +10,7 @@ export function setClientToken(token) {
|
|
|
10
10
|
if (typeof window !== 'undefined') {
|
|
11
11
|
try {
|
|
12
12
|
localStorage.setItem(TOKEN_KEY, token);
|
|
13
|
+
window.dispatchEvent(new Event('storage'));
|
|
13
14
|
}
|
|
14
15
|
catch (error) {
|
|
15
16
|
console.error('Failed to save token to localStorage:', error);
|
|
@@ -10,6 +10,7 @@ export class CheckoutResource {
|
|
|
10
10
|
* Initialize a new checkout session
|
|
11
11
|
*/
|
|
12
12
|
async initCheckout(params) {
|
|
13
|
+
// Pass all params including customerId to prevent duplicate customer creation
|
|
13
14
|
return this.apiClient.post('/api/v1/checkout/session/init', params);
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
@@ -19,6 +19,7 @@ export interface UseCheckoutQueryResult {
|
|
|
19
19
|
}>;
|
|
20
20
|
refresh: () => Promise<void>;
|
|
21
21
|
updateLineItems: (lineItems: CheckoutLineItem[]) => Promise<any>;
|
|
22
|
+
updateLineItemsOptimistic: (lineItems: CheckoutLineItem[]) => void;
|
|
22
23
|
setItemQuantity: (variantId: string, quantity: number, priceId?: string) => Promise<any>;
|
|
23
24
|
updateCustomer: (data: {
|
|
24
25
|
email: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Checkout Hook using TanStack Query
|
|
3
3
|
* Replaces the coordinator pattern with automatic cache invalidation
|
|
4
4
|
*/
|
|
5
|
-
import { useCallback, useState, useEffect, useMemo } from 'react';
|
|
5
|
+
import { useCallback, useState, useEffect, useMemo, useRef } from 'react';
|
|
6
6
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
7
7
|
import { CheckoutResource } from '../../core/resources/checkout';
|
|
8
8
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
@@ -14,7 +14,46 @@ export function useCheckoutQuery(options = {}) {
|
|
|
14
14
|
const { storeId } = usePluginConfig();
|
|
15
15
|
const currency = useCurrency();
|
|
16
16
|
const queryClient = useQueryClient();
|
|
17
|
-
const { isSessionInitialized } = useTagadaContext();
|
|
17
|
+
const { isSessionInitialized, session } = useTagadaContext();
|
|
18
|
+
// Track pending session promises to avoid creating multiple promises
|
|
19
|
+
const pendingSessionPromise = useRef(null);
|
|
20
|
+
const sessionResolvers = useRef(new Set());
|
|
21
|
+
// Resolve all pending promises when session becomes ready
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (isSessionInitialized && sessionResolvers.current.size > 0) {
|
|
24
|
+
console.log('โ
[useCheckout] CMS session ready, resolving pending promises');
|
|
25
|
+
sessionResolvers.current.forEach(resolve => resolve());
|
|
26
|
+
sessionResolvers.current.clear();
|
|
27
|
+
pendingSessionPromise.current = null;
|
|
28
|
+
}
|
|
29
|
+
}, [isSessionInitialized]);
|
|
30
|
+
// Clean, event-driven session waiting - no polling needed
|
|
31
|
+
const waitForSession = useCallback(async () => {
|
|
32
|
+
if (isSessionInitialized) {
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
// Reuse existing promise if one is pending
|
|
36
|
+
if (pendingSessionPromise.current) {
|
|
37
|
+
return pendingSessionPromise.current;
|
|
38
|
+
}
|
|
39
|
+
console.log('โณ [useCheckout] Waiting for CMS session to be initialized...');
|
|
40
|
+
// Create new promise that will be resolved by useEffect above
|
|
41
|
+
pendingSessionPromise.current = new Promise((resolve, reject) => {
|
|
42
|
+
// Add resolver to set for useEffect to call
|
|
43
|
+
sessionResolvers.current.add(resolve);
|
|
44
|
+
// Safety timeout
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
if (sessionResolvers.current.has(resolve)) {
|
|
47
|
+
sessionResolvers.current.delete(resolve);
|
|
48
|
+
if (sessionResolvers.current.size === 0) {
|
|
49
|
+
pendingSessionPromise.current = null;
|
|
50
|
+
}
|
|
51
|
+
reject(new Error('Session initialization timeout. Please refresh the page and try again.'));
|
|
52
|
+
}
|
|
53
|
+
}, 10000);
|
|
54
|
+
});
|
|
55
|
+
return pendingSessionPromise.current;
|
|
56
|
+
}, [isSessionInitialized]);
|
|
18
57
|
// Create checkout resource client
|
|
19
58
|
const checkoutResource = useMemo(() => {
|
|
20
59
|
try {
|
|
@@ -55,6 +94,8 @@ export function useCheckoutQuery(options = {}) {
|
|
|
55
94
|
...params,
|
|
56
95
|
storeId: params.storeId || storeId,
|
|
57
96
|
returnUrl: params.returnUrl || window.location.origin,
|
|
97
|
+
// Include customerId from session to prevent duplicate customer creation
|
|
98
|
+
customerId: params.customerId || session?.customerId,
|
|
58
99
|
customer: {
|
|
59
100
|
...params.customer,
|
|
60
101
|
currency: params.customer?.currency ?? currency.code,
|
|
@@ -75,7 +116,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
75
116
|
},
|
|
76
117
|
});
|
|
77
118
|
// Order bump functionality removed - use useOrderBumpQuery instead
|
|
78
|
-
// Line items mutation
|
|
119
|
+
// Line items mutation with optimistic updates
|
|
79
120
|
const lineItemsMutation = useMutation({
|
|
80
121
|
mutationFn: ({ lineItems }) => {
|
|
81
122
|
if (!checkout?.checkoutSession?.id) {
|
|
@@ -83,7 +124,38 @@ export function useCheckoutQuery(options = {}) {
|
|
|
83
124
|
}
|
|
84
125
|
return checkoutResource.updateLineItems(checkout.checkoutSession.id, lineItems);
|
|
85
126
|
},
|
|
86
|
-
|
|
127
|
+
onMutate: async ({ lineItems }) => {
|
|
128
|
+
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
|
|
129
|
+
await queryClient.cancelQueries({ queryKey: ['checkout', checkoutToken] });
|
|
130
|
+
// Snapshot the previous value
|
|
131
|
+
const previousCheckout = queryClient.getQueryData(['checkout', checkoutToken]);
|
|
132
|
+
// Optimistically update the checkout data
|
|
133
|
+
if (previousCheckout && checkout?.checkoutSession?.id) {
|
|
134
|
+
const optimisticCheckout = {
|
|
135
|
+
...previousCheckout,
|
|
136
|
+
checkoutSession: {
|
|
137
|
+
...previousCheckout.checkoutSession,
|
|
138
|
+
sessionLineItems: lineItems.map(item => ({
|
|
139
|
+
variantId: item.variantId,
|
|
140
|
+
quantity: item.quantity,
|
|
141
|
+
priceId: item.priceId,
|
|
142
|
+
isOrderBump: false, // Default for line items
|
|
143
|
+
})),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
queryClient.setQueryData(['checkout', checkoutToken], optimisticCheckout);
|
|
147
|
+
}
|
|
148
|
+
// Return a context object with the snapshotted value
|
|
149
|
+
return { previousCheckout };
|
|
150
|
+
},
|
|
151
|
+
onError: (err, _variables, context) => {
|
|
152
|
+
// If the mutation fails, use the context returned from onMutate to roll back
|
|
153
|
+
if (context && typeof context === 'object' && 'previousCheckout' in context) {
|
|
154
|
+
queryClient.setQueryData(['checkout', checkoutToken], context.previousCheckout);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
onSettled: () => {
|
|
158
|
+
// Always refetch after error or success to ensure we have the latest data
|
|
87
159
|
if (checkoutToken) {
|
|
88
160
|
void queryClient.invalidateQueries({ queryKey: ['checkout', checkoutToken] });
|
|
89
161
|
}
|
|
@@ -173,6 +245,8 @@ export function useCheckoutQuery(options = {}) {
|
|
|
173
245
|
isSuccess,
|
|
174
246
|
// Actions
|
|
175
247
|
init: async (params) => {
|
|
248
|
+
// Wait for session to be initialized to ensure we have customerId
|
|
249
|
+
await waitForSession();
|
|
176
250
|
const result = await initMutation.mutateAsync(params);
|
|
177
251
|
// Update internal token state so the query can fetch the checkout data
|
|
178
252
|
setInternalToken(result.checkoutToken);
|
|
@@ -185,6 +259,7 @@ export function useCheckoutQuery(options = {}) {
|
|
|
185
259
|
refresh,
|
|
186
260
|
// Checkout operations
|
|
187
261
|
updateLineItems: (lineItems) => lineItemsMutation.mutateAsync({ lineItems }),
|
|
262
|
+
updateLineItemsOptimistic: (lineItems) => lineItemsMutation.mutate({ lineItems }),
|
|
188
263
|
setItemQuantity: (variantId, quantity, priceId) => quantityMutation.mutateAsync({ variantId, quantity, priceId }),
|
|
189
264
|
updateCustomer: (data) => customerMutation.mutateAsync(data),
|
|
190
265
|
updateCustomerAndSessionInfo: (data) => customerAndSessionMutation.mutateAsync(data),
|
|
@@ -6,7 +6,7 @@ import { useState, useCallback, useEffect } from 'react';
|
|
|
6
6
|
import { useApiMutation, useInvalidateQuery, getGlobalApiClient } from './useApiQuery';
|
|
7
7
|
import { useCheckoutQuery } from './useCheckoutQuery';
|
|
8
8
|
export function useOrderBumpQuery(options) {
|
|
9
|
-
const { checkoutToken, offerId,
|
|
9
|
+
const { checkoutToken, offerId, checkout: providedCheckout } = options;
|
|
10
10
|
const { invalidateCheckout, invalidatePromotions } = useInvalidateQuery();
|
|
11
11
|
const client = getGlobalApiClient();
|
|
12
12
|
// Use checkout query only if no checkout is provided
|
|
@@ -23,11 +23,12 @@ export function useOrderBumpQuery(options) {
|
|
|
23
23
|
if (!checkout?.checkoutSession?.sessionLineItems) {
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
// Check if any order bump items exist in sessionLineItems
|
|
27
|
+
// The offerId is the upsell offer ID, but we check for isOrderBump === true
|
|
27
28
|
return checkout.checkoutSession.sessionLineItems.some((item) => {
|
|
28
|
-
return item.isOrderBump === true
|
|
29
|
+
return item.isOrderBump === true;
|
|
29
30
|
});
|
|
30
|
-
}, [checkout?.checkoutSession?.sessionLineItems
|
|
31
|
+
}, [checkout?.checkoutSession?.sessionLineItems]);
|
|
31
32
|
// State management
|
|
32
33
|
const [isSelected, setIsSelected] = useState(() => checkOrderBumpSelection());
|
|
33
34
|
const [error, setError] = useState(null);
|
|
@@ -82,7 +83,7 @@ export function useOrderBumpQuery(options) {
|
|
|
82
83
|
const error = err instanceof Error ? err : new Error('Failed to toggle order bump');
|
|
83
84
|
return { success: false, error: error.message };
|
|
84
85
|
}
|
|
85
|
-
}, [checkout?.checkoutSession?.id, isSelected, toggleMutation, offerId,
|
|
86
|
+
}, [checkout?.checkoutSession?.id, isSelected, toggleMutation, offerId, checkoutToken, actualCheckoutToken]);
|
|
86
87
|
return {
|
|
87
88
|
isSelected,
|
|
88
89
|
isToggling: toggleMutation.isPending,
|
|
@@ -11,6 +11,8 @@ export interface UsePluginConfigResult<TConfig = Record<string, any>> {
|
|
|
11
11
|
storeId?: string;
|
|
12
12
|
accountId?: string;
|
|
13
13
|
basePath?: string;
|
|
14
|
+
loading: boolean;
|
|
15
|
+
error?: Error;
|
|
14
16
|
isValid: boolean;
|
|
15
17
|
}
|
|
16
18
|
export declare function usePluginConfig<TConfig = Record<string, any>>(options?: UsePluginConfigOptions): UsePluginConfigResult<TConfig>;
|
|
@@ -6,7 +6,7 @@ import { useMemo } from 'react';
|
|
|
6
6
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
7
7
|
import { PluginConfigUtils } from '../../core/utils/pluginConfig';
|
|
8
8
|
export function usePluginConfig(options = {}) {
|
|
9
|
-
const { pluginConfig } = useTagadaContext();
|
|
9
|
+
const { pluginConfig, pluginConfigLoading } = useTagadaContext();
|
|
10
10
|
const config = useMemo(() => {
|
|
11
11
|
const baseConfig = PluginConfigUtils.getPluginConfig(options.rawConfig, {
|
|
12
12
|
storeId: pluginConfig.storeId,
|
|
@@ -30,6 +30,8 @@ export function usePluginConfig(options = {}) {
|
|
|
30
30
|
storeId: config.storeId,
|
|
31
31
|
accountId: config.accountId,
|
|
32
32
|
basePath: config.basePath,
|
|
33
|
+
loading: pluginConfigLoading,
|
|
34
|
+
error: undefined, // TODO: Add error handling if needed
|
|
33
35
|
isValid,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
@@ -48,9 +48,39 @@ const InitializationLoader = () => (_jsxs("div", { style: {
|
|
|
48
48
|
}
|
|
49
49
|
` })] }));
|
|
50
50
|
const TagadaContext = createContext(null);
|
|
51
|
+
// Global instance tracking for TagadaProvider
|
|
52
|
+
let globalTagadaInstance = null;
|
|
53
|
+
let globalTagadaInitialized = false;
|
|
51
54
|
export function TagadaProvider({ children, environment, customApiConfig, debugMode, // Remove default, will be set based on environment
|
|
52
55
|
localConfig, blockUntilSessionReady = false, // Default to new non-blocking behavior
|
|
53
56
|
rawPluginConfig, }) {
|
|
57
|
+
// Instance tracking
|
|
58
|
+
const [instanceId] = useState(() => {
|
|
59
|
+
if (!globalTagadaInstance) {
|
|
60
|
+
globalTagadaInstance = Math.random().toString(36).substr(2, 9);
|
|
61
|
+
}
|
|
62
|
+
return globalTagadaInstance;
|
|
63
|
+
});
|
|
64
|
+
const isActiveInstance = useMemo(() => {
|
|
65
|
+
if (!globalTagadaInitialized) {
|
|
66
|
+
globalTagadaInitialized = true;
|
|
67
|
+
console.log(`โ
[TagadaProvider] Instance ${instanceId} is now the active instance`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is duplicate - blocking execution`);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}, [instanceId]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
return () => {
|
|
77
|
+
if (globalTagadaInstance === instanceId) {
|
|
78
|
+
globalTagadaInitialized = false;
|
|
79
|
+
globalTagadaInstance = null;
|
|
80
|
+
console.log(`๐งน [TagadaProvider] Instance ${instanceId} cleanup - allowing new instances`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}, [instanceId]);
|
|
54
84
|
// LOCAL DEV ONLY: Use localConfig override if in local development, otherwise use default
|
|
55
85
|
const isLocalDev = typeof window !== 'undefined' &&
|
|
56
86
|
(window.location.hostname === 'localhost' ||
|
|
@@ -60,7 +90,7 @@ rawPluginConfig, }) {
|
|
|
60
90
|
// Debug logging (only log once during initial render)
|
|
61
91
|
const hasLoggedRef = useRef(false);
|
|
62
92
|
if (!hasLoggedRef.current) {
|
|
63
|
-
console.log(
|
|
93
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} Config Debug:`, {
|
|
64
94
|
hostname: typeof window !== 'undefined' ? window.location.hostname : 'SSR',
|
|
65
95
|
isLocalDev,
|
|
66
96
|
localConfig,
|
|
@@ -84,6 +114,11 @@ rawPluginConfig, }) {
|
|
|
84
114
|
const [configLoading, setConfigLoading] = useState(!rawPluginConfig);
|
|
85
115
|
// Load plugin config on mount with the specified variant
|
|
86
116
|
useEffect(() => {
|
|
117
|
+
// Prevent multiple config loads
|
|
118
|
+
if (configLoading === false && pluginConfig.storeId) {
|
|
119
|
+
console.log('๐ [TagadaProvider] Config already loaded, skipping reload');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
87
122
|
const loadConfig = async () => {
|
|
88
123
|
try {
|
|
89
124
|
// Use the v2 core loadPluginConfig function
|
|
@@ -239,12 +274,21 @@ rawPluginConfig, }) {
|
|
|
239
274
|
});
|
|
240
275
|
// Initialize session
|
|
241
276
|
const initializeSession = useCallback(async (sessionData) => {
|
|
277
|
+
if (!isActiveInstance) {
|
|
278
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is not active, skipping session initialization`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
242
281
|
if (!sessionData.storeId || !sessionData.accountId) {
|
|
243
|
-
console.error(
|
|
282
|
+
console.error(`[TagadaProvider] Instance ${instanceId} missing required session data`);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// Prevent multiple session initializations
|
|
286
|
+
if (isSessionInitialized) {
|
|
287
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} session already initialized, skipping`);
|
|
244
288
|
return;
|
|
245
289
|
}
|
|
246
290
|
if (finalDebugMode) {
|
|
247
|
-
console.debug(
|
|
291
|
+
console.debug(`[TagadaProvider] Instance ${instanceId} [DEBUG] Initializing session with store config...`, sessionData);
|
|
248
292
|
}
|
|
249
293
|
setIsLoading(true);
|
|
250
294
|
try {
|
|
@@ -352,15 +396,23 @@ rawPluginConfig, }) {
|
|
|
352
396
|
setIsInitialized(true);
|
|
353
397
|
setIsLoading(false);
|
|
354
398
|
}
|
|
355
|
-
}, [apiService, finalDebugMode]);
|
|
399
|
+
}, [apiService, finalDebugMode, isActiveInstance, instanceId]);
|
|
356
400
|
// Create anonymous token if needed
|
|
357
401
|
const createAnonymousToken = useCallback(async (targetStoreId) => {
|
|
402
|
+
if (!isActiveInstance) {
|
|
403
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is not active, skipping anonymous token creation`);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
358
406
|
if (hasAttemptedAnonymousToken || !targetStoreId) {
|
|
407
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} anonymous token already attempted or no storeId:`, {
|
|
408
|
+
hasAttemptedAnonymousToken,
|
|
409
|
+
targetStoreId,
|
|
410
|
+
});
|
|
359
411
|
return;
|
|
360
412
|
}
|
|
361
|
-
console.log(
|
|
413
|
+
console.log(`[TagadaProvider] Instance ${instanceId} ๐ Starting Phase 3 - Session initialization...`);
|
|
362
414
|
if (finalDebugMode) {
|
|
363
|
-
console.debug(
|
|
415
|
+
console.debug(`[TagadaProvider] Instance ${instanceId} [DEBUG] Creating anonymous token for store:`, targetStoreId);
|
|
364
416
|
}
|
|
365
417
|
setHasAttemptedAnonymousToken(true);
|
|
366
418
|
try {
|
|
@@ -405,15 +457,24 @@ rawPluginConfig, }) {
|
|
|
405
457
|
setIsInitialized(true);
|
|
406
458
|
setIsLoading(false);
|
|
407
459
|
}
|
|
408
|
-
}, [apiService, hasAttemptedAnonymousToken, initializeSession, finalDebugMode]);
|
|
460
|
+
}, [apiService, hasAttemptedAnonymousToken, initializeSession, finalDebugMode, isActiveInstance, instanceId]);
|
|
409
461
|
// Initialize token from storage or create anonymous token
|
|
410
462
|
// This runs in the background after phases 1 & 2 complete, but doesn't block rendering
|
|
411
463
|
useEffect(() => {
|
|
464
|
+
if (!isActiveInstance) {
|
|
465
|
+
console.log(`๐ซ [TagadaProvider] Instance ${instanceId} is not active, skipping token initialization`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
412
468
|
if (isInitializing.current) {
|
|
469
|
+
console.log(`๐ [TagadaProvider] Instance ${instanceId} already initializing, skipping`);
|
|
413
470
|
return;
|
|
414
471
|
}
|
|
415
472
|
// Wait for plugin config to load AND ensure we have a store ID before initializing
|
|
416
473
|
if (configLoading || !storeId) {
|
|
474
|
+
console.log(`โณ [TagadaProvider] Instance ${instanceId} waiting for config or storeId:`, {
|
|
475
|
+
configLoading,
|
|
476
|
+
storeId,
|
|
477
|
+
});
|
|
417
478
|
return;
|
|
418
479
|
}
|
|
419
480
|
isInitializing.current = true;
|
|
@@ -469,7 +530,7 @@ rawPluginConfig, }) {
|
|
|
469
530
|
}
|
|
470
531
|
};
|
|
471
532
|
void initializeToken();
|
|
472
|
-
}, [storeId, createAnonymousToken, initializeSession, configLoading]);
|
|
533
|
+
}, [storeId, createAnonymousToken, initializeSession, configLoading, isActiveInstance, instanceId]);
|
|
473
534
|
// Update auth state when customer/session changes
|
|
474
535
|
useEffect(() => {
|
|
475
536
|
setAuth({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tagadapay/plugin-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "Modern React SDK for building Tagada Pay plugins",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"version:sync": "node version-sync.js sync",
|
|
43
43
|
"version:list": "node version-sync.js list",
|
|
44
44
|
"version:next": "node version-sync.js next",
|
|
45
|
-
"postversion": "echo \"โ
Version updated to $(node -p 'require(\"./package.json\").version')\" && git push && git push --tags"
|
|
45
|
+
"postversion": "echo \"โ
Version updated to $(node -p 'require(\"./package.json\").version')\" && (git push && git push --tags || echo \"โ ๏ธ Git push failed - you may need to pull and push manually\")"
|
|
46
46
|
},
|
|
47
47
|
"keywords": [
|
|
48
48
|
"tagadapay",
|