@tagadapay/plugin-sdk 1.0.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/README.md +475 -0
- package/dist/data/currencies.json +2410 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +37 -0
- package/dist/react/components/DebugDrawer.d.ts +7 -0
- package/dist/react/components/DebugDrawer.js +368 -0
- package/dist/react/components/OffersDemo.d.ts +1 -0
- package/dist/react/components/OffersDemo.js +50 -0
- package/dist/react/components/index.d.ts +1 -0
- package/dist/react/components/index.js +1 -0
- package/dist/react/config/environment.d.ts +22 -0
- package/dist/react/config/environment.js +132 -0
- package/dist/react/config/payment.d.ts +23 -0
- package/dist/react/config/payment.js +52 -0
- package/dist/react/hooks/useAuth.d.ts +4 -0
- package/dist/react/hooks/useAuth.js +12 -0
- package/dist/react/hooks/useCheckout.d.ts +262 -0
- package/dist/react/hooks/useCheckout.js +325 -0
- package/dist/react/hooks/useCurrency.d.ts +4 -0
- package/dist/react/hooks/useCurrency.js +640 -0
- package/dist/react/hooks/useCustomer.d.ts +7 -0
- package/dist/react/hooks/useCustomer.js +14 -0
- package/dist/react/hooks/useEnvironment.d.ts +7 -0
- package/dist/react/hooks/useEnvironment.js +18 -0
- package/dist/react/hooks/useLocale.d.ts +2 -0
- package/dist/react/hooks/useLocale.js +43 -0
- package/dist/react/hooks/useOffers.d.ts +99 -0
- package/dist/react/hooks/useOffers.js +115 -0
- package/dist/react/hooks/useOrder.d.ts +44 -0
- package/dist/react/hooks/useOrder.js +77 -0
- package/dist/react/hooks/usePayment.d.ts +60 -0
- package/dist/react/hooks/usePayment.js +343 -0
- package/dist/react/hooks/usePaymentPolling.d.ts +45 -0
- package/dist/react/hooks/usePaymentPolling.js +146 -0
- package/dist/react/hooks/useProducts.d.ts +95 -0
- package/dist/react/hooks/useProducts.js +120 -0
- package/dist/react/hooks/useSession.d.ts +10 -0
- package/dist/react/hooks/useSession.js +17 -0
- package/dist/react/hooks/useThreeds.d.ts +38 -0
- package/dist/react/hooks/useThreeds.js +162 -0
- package/dist/react/hooks/useThreedsModal.d.ts +16 -0
- package/dist/react/hooks/useThreedsModal.js +328 -0
- package/dist/react/index.d.ts +26 -0
- package/dist/react/index.js +27 -0
- package/dist/react/providers/TagadaProvider.d.ts +55 -0
- package/dist/react/providers/TagadaProvider.js +471 -0
- package/dist/react/services/apiService.d.ts +149 -0
- package/dist/react/services/apiService.js +168 -0
- package/dist/react/types.d.ts +151 -0
- package/dist/react/types.js +4 -0
- package/dist/react/utils/__tests__/urlUtils.test.d.ts +1 -0
- package/dist/react/utils/__tests__/urlUtils.test.js +189 -0
- package/dist/react/utils/deviceInfo.d.ts +39 -0
- package/dist/react/utils/deviceInfo.js +163 -0
- package/dist/react/utils/jwtDecoder.d.ts +14 -0
- package/dist/react/utils/jwtDecoder.js +86 -0
- package/dist/react/utils/money.d.ts +2273 -0
- package/dist/react/utils/money.js +104 -0
- package/dist/react/utils/tokenStorage.d.ts +16 -0
- package/dist/react/utils/tokenStorage.js +52 -0
- package/dist/react/utils/urlUtils.d.ts +239 -0
- package/dist/react/utils/urlUtils.js +449 -0
- package/package.json +64 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
export class ApiService {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.config = options.environmentConfig;
|
|
6
|
+
this.token = options.token || null;
|
|
7
|
+
this.onTokenUpdate = options.onTokenUpdate;
|
|
8
|
+
this.onTokenClear = options.onTokenClear;
|
|
9
|
+
}
|
|
10
|
+
updateToken(token) {
|
|
11
|
+
this.token = token;
|
|
12
|
+
}
|
|
13
|
+
updateConfig(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Store the store ID in localStorage
|
|
18
|
+
*/
|
|
19
|
+
storeStoreId(storeId) {
|
|
20
|
+
try {
|
|
21
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
22
|
+
window.localStorage.setItem('tagada_store_id', storeId);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.warn('[SDK] Failed to store store ID in localStorage:', error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the store ID from localStorage
|
|
31
|
+
*/
|
|
32
|
+
getStoredStoreId() {
|
|
33
|
+
try {
|
|
34
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
35
|
+
return window.localStorage.getItem('tagada_store_id');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn('[SDK] Failed to get store ID from localStorage:', error);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Make an authenticated request to the API
|
|
45
|
+
*/
|
|
46
|
+
async fetch(endpoint, options = {}) {
|
|
47
|
+
const { method = 'GET', body, headers = {}, params, skipAuth = false, captureErrors = true } = options;
|
|
48
|
+
// Build the full URL
|
|
49
|
+
const url = `${this.config.apiConfig.baseUrl}${endpoint}`;
|
|
50
|
+
// If auth is required and token is not available, throw an error
|
|
51
|
+
if (!skipAuth && !this.token) {
|
|
52
|
+
throw new Error('No authentication token available');
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
// Prepare headers
|
|
56
|
+
const requestHeaders = {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
...headers,
|
|
59
|
+
};
|
|
60
|
+
// Add CMS token to headers if available and not skipped
|
|
61
|
+
if (!skipAuth && this.token) {
|
|
62
|
+
requestHeaders['x-cms-token'] = this.token;
|
|
63
|
+
}
|
|
64
|
+
// Create axios config
|
|
65
|
+
const config = {
|
|
66
|
+
method,
|
|
67
|
+
url,
|
|
68
|
+
headers: requestHeaders,
|
|
69
|
+
params,
|
|
70
|
+
data: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined,
|
|
71
|
+
};
|
|
72
|
+
console.log(`[SDK] Making ${method} request to: ${url}`);
|
|
73
|
+
if (body) {
|
|
74
|
+
console.log(`[SDK] Request body:`, body);
|
|
75
|
+
}
|
|
76
|
+
// Make the request
|
|
77
|
+
const response = await axios(config);
|
|
78
|
+
// Check for X-TGD-STOREID header in response and store it
|
|
79
|
+
const storeIdHeader = response.headers['x-tgd-storeid'] || response.headers['X-TGD-STOREID'];
|
|
80
|
+
if (storeIdHeader && typeof storeIdHeader === 'string') {
|
|
81
|
+
console.log(`[SDK] Store ID received from header: ${storeIdHeader}`);
|
|
82
|
+
this.storeStoreId(storeIdHeader);
|
|
83
|
+
}
|
|
84
|
+
console.log(`[SDK] Response status: ${response.status}`);
|
|
85
|
+
console.log(`[SDK] Response data:`, response.data);
|
|
86
|
+
return response.data;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (captureErrors) {
|
|
90
|
+
console.error(`[SDK] Request failed: ${method} ${url}`, error);
|
|
91
|
+
}
|
|
92
|
+
if (axios.isAxiosError(error)) {
|
|
93
|
+
const axiosError = error;
|
|
94
|
+
if (axiosError.response?.status === 401) {
|
|
95
|
+
// Token might be expired, clear it
|
|
96
|
+
if (this.onTokenClear) {
|
|
97
|
+
this.onTokenClear();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
throw new Error(`API request failed: ${axiosError.response?.status} ${axiosError.response?.statusText}`);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Create an anonymous token for the given store
|
|
107
|
+
*/
|
|
108
|
+
async createAnonymousToken(storeId) {
|
|
109
|
+
const response = await this.fetch('/api/v1/cms/session/anonymous', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
body: {
|
|
112
|
+
storeId,
|
|
113
|
+
role: 'anonymous',
|
|
114
|
+
},
|
|
115
|
+
skipAuth: true, // Skip auth for anonymous token creation
|
|
116
|
+
});
|
|
117
|
+
return response;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Initialize a CMS session with device info and user data
|
|
121
|
+
*/
|
|
122
|
+
async initializeSession(sessionData) {
|
|
123
|
+
console.debug('[API] Initializing session');
|
|
124
|
+
return this.fetch('/api/v1/cms/session/init', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
body: sessionData,
|
|
127
|
+
skipAuth: false, // Use the token for session initialization
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get customer profile
|
|
132
|
+
*/
|
|
133
|
+
async getCustomerProfile(customerId) {
|
|
134
|
+
return this.fetch(`/api/v1/customer/profile/${customerId}`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get session status
|
|
138
|
+
*/
|
|
139
|
+
async getSessionStatus(sessionId) {
|
|
140
|
+
return this.fetch(`/api/v1/customer/session/${sessionId}`, {
|
|
141
|
+
method: 'GET',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Update customer information (email, marketing preferences)
|
|
146
|
+
*/
|
|
147
|
+
async updateCustomer(customerId, data) {
|
|
148
|
+
return this.fetch(`/api/v1/customers/${customerId}`, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
body: { data },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Update customer and checkout session information in one request
|
|
155
|
+
*/
|
|
156
|
+
async updateCustomerAndSessionInfo(checkoutSessionId, data) {
|
|
157
|
+
return this.fetch(`/api/v1/checkout-sessions/${checkoutSessionId}/customer-and-session-info`, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
body: data,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Clear the authentication token
|
|
164
|
+
*/
|
|
165
|
+
clearToken() {
|
|
166
|
+
this.updateToken(null);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the Tagada Pay React SDK
|
|
3
|
+
*/
|
|
4
|
+
export type Environment = 'production' | 'development' | 'local';
|
|
5
|
+
export interface ApiConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
endpoints: {
|
|
8
|
+
checkout: {
|
|
9
|
+
sessionInit: string;
|
|
10
|
+
sessionStatus: string;
|
|
11
|
+
};
|
|
12
|
+
customer: {
|
|
13
|
+
profile: string;
|
|
14
|
+
session: string;
|
|
15
|
+
};
|
|
16
|
+
store: {
|
|
17
|
+
config: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface EnvironmentConfig {
|
|
22
|
+
environment: Environment;
|
|
23
|
+
apiConfig: ApiConfig;
|
|
24
|
+
}
|
|
25
|
+
export interface Customer {
|
|
26
|
+
id: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
firstName?: string;
|
|
29
|
+
lastName?: string;
|
|
30
|
+
phone?: string;
|
|
31
|
+
isAuthenticated: boolean;
|
|
32
|
+
role: 'authenticated' | 'anonymous';
|
|
33
|
+
}
|
|
34
|
+
export interface Session {
|
|
35
|
+
sessionId: string;
|
|
36
|
+
storeId: string;
|
|
37
|
+
accountId: string;
|
|
38
|
+
customerId: string;
|
|
39
|
+
role: 'authenticated' | 'anonymous';
|
|
40
|
+
isValid: boolean;
|
|
41
|
+
isLoading: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface AuthState {
|
|
44
|
+
isAuthenticated: boolean;
|
|
45
|
+
isLoading: boolean;
|
|
46
|
+
customer: Customer | null;
|
|
47
|
+
session: Session | null;
|
|
48
|
+
}
|
|
49
|
+
export interface Locale {
|
|
50
|
+
locale: string;
|
|
51
|
+
language: string;
|
|
52
|
+
region: string;
|
|
53
|
+
messages?: Record<string, string>;
|
|
54
|
+
}
|
|
55
|
+
export interface Currency {
|
|
56
|
+
code: string;
|
|
57
|
+
symbol: string;
|
|
58
|
+
name: string;
|
|
59
|
+
}
|
|
60
|
+
export interface Store {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
domain: string;
|
|
64
|
+
currency: string;
|
|
65
|
+
locale: string;
|
|
66
|
+
presentmentCurrencies: string[];
|
|
67
|
+
chargeCurrencies: string[];
|
|
68
|
+
}
|
|
69
|
+
export interface PickupPoint {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
country: string;
|
|
73
|
+
postal_code: string;
|
|
74
|
+
city: string;
|
|
75
|
+
address: string;
|
|
76
|
+
address2?: string;
|
|
77
|
+
house_number?: string;
|
|
78
|
+
phone?: string;
|
|
79
|
+
email?: string;
|
|
80
|
+
latitude?: number;
|
|
81
|
+
longitude?: number;
|
|
82
|
+
opening_hours?: string;
|
|
83
|
+
extra_info?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface OrderItem {
|
|
86
|
+
id: string;
|
|
87
|
+
productId: string;
|
|
88
|
+
variantId: string;
|
|
89
|
+
quantity: number;
|
|
90
|
+
unitAmount: number;
|
|
91
|
+
amount: number;
|
|
92
|
+
adjustedAmount: number;
|
|
93
|
+
currency: string;
|
|
94
|
+
recurring?: boolean;
|
|
95
|
+
interval?: 'day' | 'week' | 'month' | 'year' | null;
|
|
96
|
+
intervalCount?: number | null;
|
|
97
|
+
orderLineItemProduct?: {
|
|
98
|
+
name: string;
|
|
99
|
+
};
|
|
100
|
+
orderLineItemVariant?: {
|
|
101
|
+
name: string;
|
|
102
|
+
imageUrl: string | null;
|
|
103
|
+
};
|
|
104
|
+
subscriptionSettings?: {
|
|
105
|
+
trial?: boolean;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export interface OrderSummary {
|
|
109
|
+
currency: string;
|
|
110
|
+
totalPromotionAmount: number;
|
|
111
|
+
totalTaxAmount: number;
|
|
112
|
+
shippingCost: number;
|
|
113
|
+
shippingCostIsFree: boolean;
|
|
114
|
+
subtotalAmount: number;
|
|
115
|
+
subtotalAdjustedAmount: number;
|
|
116
|
+
totalAdjustedAmount: number;
|
|
117
|
+
adjustments?: {
|
|
118
|
+
type: string;
|
|
119
|
+
amount: number;
|
|
120
|
+
description: string;
|
|
121
|
+
}[];
|
|
122
|
+
}
|
|
123
|
+
export interface OrderAddress {
|
|
124
|
+
firstName: string;
|
|
125
|
+
lastName: string;
|
|
126
|
+
address1: string;
|
|
127
|
+
address2?: string;
|
|
128
|
+
city: string;
|
|
129
|
+
state: string;
|
|
130
|
+
postal: string;
|
|
131
|
+
country: string;
|
|
132
|
+
phone?: string;
|
|
133
|
+
}
|
|
134
|
+
export interface Order {
|
|
135
|
+
id: string;
|
|
136
|
+
currency: string;
|
|
137
|
+
paidAmount: number;
|
|
138
|
+
status: string;
|
|
139
|
+
createdAt: string;
|
|
140
|
+
metadata?: Record<string, any>;
|
|
141
|
+
items: OrderItem[];
|
|
142
|
+
summaries?: OrderSummary[];
|
|
143
|
+
shippingAddress?: OrderAddress;
|
|
144
|
+
billingAddress?: OrderAddress;
|
|
145
|
+
pickupAddress?: PickupPoint;
|
|
146
|
+
checkoutSession?: {
|
|
147
|
+
returnUrl?: string;
|
|
148
|
+
[key: string]: any;
|
|
149
|
+
};
|
|
150
|
+
relatedOrders?: Order[];
|
|
151
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { extractCheckoutToken, extractCheckoutTokenFromCurrentUrl, extractCheckoutTokenFromPath, isValidCheckoutToken, extractAndValidateCheckoutToken, getCheckoutToken, buildCheckoutUrl, hasCheckoutToken, } from '../urlUtils';
|
|
2
|
+
// Mock window.location for testing
|
|
3
|
+
const mockLocation = (href) => {
|
|
4
|
+
Object.defineProperty(window, 'location', {
|
|
5
|
+
value: { href },
|
|
6
|
+
writable: true,
|
|
7
|
+
});
|
|
8
|
+
};
|
|
9
|
+
describe('URL Utilities', () => {
|
|
10
|
+
const validToken = '0cb592d75aae75e337b7b784f6624dbf';
|
|
11
|
+
const anotherValidToken = '1234567890abcdef1234567890abcdef';
|
|
12
|
+
describe('extractCheckoutToken', () => {
|
|
13
|
+
it('should extract token from full URL', () => {
|
|
14
|
+
const url = `https://example.com/checkout/${validToken}`;
|
|
15
|
+
expect(extractCheckoutToken(url)).toBe(validToken);
|
|
16
|
+
});
|
|
17
|
+
it('should extract token from path', () => {
|
|
18
|
+
const path = `/checkout/${validToken}`;
|
|
19
|
+
expect(extractCheckoutToken(path)).toBe(validToken);
|
|
20
|
+
});
|
|
21
|
+
it('should extract token from anywhere in path', () => {
|
|
22
|
+
const path = `/some/path/${validToken}/other/path`;
|
|
23
|
+
expect(extractCheckoutToken(path)).toBe(validToken);
|
|
24
|
+
});
|
|
25
|
+
it('should extract token from token only', () => {
|
|
26
|
+
expect(extractCheckoutToken(validToken)).toBe(validToken);
|
|
27
|
+
});
|
|
28
|
+
it('should return null for invalid URLs', () => {
|
|
29
|
+
expect(extractCheckoutToken('https://example.com/checkout')).toBeNull();
|
|
30
|
+
expect(extractCheckoutToken('invalid-token')).toBeNull();
|
|
31
|
+
expect(extractCheckoutToken('')).toBeNull();
|
|
32
|
+
expect(extractCheckoutToken(null)).toBeNull();
|
|
33
|
+
expect(extractCheckoutToken(undefined)).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
it('should normalize token to lowercase', () => {
|
|
36
|
+
const upperCaseToken = validToken.toUpperCase();
|
|
37
|
+
expect(extractCheckoutToken(upperCaseToken)).toBe(validToken);
|
|
38
|
+
});
|
|
39
|
+
it('should handle multiple tokens and return first match', () => {
|
|
40
|
+
const path = `/path/${validToken}/another/${anotherValidToken}`;
|
|
41
|
+
expect(extractCheckoutToken(path)).toBe(validToken);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('extractCheckoutTokenFromCurrentUrl', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
// Reset window.location mock
|
|
47
|
+
delete window.location;
|
|
48
|
+
});
|
|
49
|
+
it('should extract token from current URL', () => {
|
|
50
|
+
mockLocation(`https://example.com/checkout/${validToken}`);
|
|
51
|
+
expect(extractCheckoutTokenFromCurrentUrl()).toBe(validToken);
|
|
52
|
+
});
|
|
53
|
+
it('should return null when no token in URL', () => {
|
|
54
|
+
mockLocation('https://example.com/checkout');
|
|
55
|
+
expect(extractCheckoutTokenFromCurrentUrl()).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
it('should return null in server environment', () => {
|
|
58
|
+
const originalWindow = global.window;
|
|
59
|
+
delete global.window;
|
|
60
|
+
expect(extractCheckoutTokenFromCurrentUrl()).toBeNull();
|
|
61
|
+
global.window = originalWindow;
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('extractCheckoutTokenFromPath', () => {
|
|
65
|
+
it('should extract token from pathname', () => {
|
|
66
|
+
const pathname = `/checkout/${validToken}`;
|
|
67
|
+
expect(extractCheckoutTokenFromPath(pathname)).toBe(validToken);
|
|
68
|
+
});
|
|
69
|
+
it('should return null for invalid pathnames', () => {
|
|
70
|
+
expect(extractCheckoutTokenFromPath('')).toBeNull();
|
|
71
|
+
expect(extractCheckoutTokenFromPath(null)).toBeNull();
|
|
72
|
+
expect(extractCheckoutTokenFromPath(undefined)).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('isValidCheckoutToken', () => {
|
|
76
|
+
it('should validate correct token format', () => {
|
|
77
|
+
expect(isValidCheckoutToken(validToken)).toBe(true);
|
|
78
|
+
expect(isValidCheckoutToken(anotherValidToken)).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
it('should reject invalid token formats', () => {
|
|
81
|
+
expect(isValidCheckoutToken('invalid-token')).toBe(false);
|
|
82
|
+
expect(isValidCheckoutToken('1234567890abcdef')).toBe(false); // Too short
|
|
83
|
+
expect(isValidCheckoutToken('1234567890abcdef1234567890abcdef1234567890abcdef')).toBe(false); // Too long
|
|
84
|
+
expect(isValidCheckoutToken('')).toBe(false);
|
|
85
|
+
expect(isValidCheckoutToken(null)).toBe(false);
|
|
86
|
+
expect(isValidCheckoutToken(undefined)).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
it('should accept uppercase tokens', () => {
|
|
89
|
+
expect(isValidCheckoutToken(validToken.toUpperCase())).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('extractAndValidateCheckoutToken', () => {
|
|
93
|
+
it('should extract and validate valid token', () => {
|
|
94
|
+
const url = `https://example.com/checkout/${validToken}`;
|
|
95
|
+
expect(extractAndValidateCheckoutToken(url)).toBe(validToken);
|
|
96
|
+
});
|
|
97
|
+
it('should return null for invalid token', () => {
|
|
98
|
+
const url = 'https://example.com/checkout/invalid-token';
|
|
99
|
+
expect(extractAndValidateCheckoutToken(url)).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
it('should return null when no token found', () => {
|
|
102
|
+
const url = 'https://example.com/checkout';
|
|
103
|
+
expect(extractAndValidateCheckoutToken(url)).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('getCheckoutToken', () => {
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
delete window.location;
|
|
109
|
+
});
|
|
110
|
+
it('should get token from current URL first', () => {
|
|
111
|
+
mockLocation(`https://example.com/checkout/${validToken}`);
|
|
112
|
+
const token = getCheckoutToken({
|
|
113
|
+
currentUrl: true,
|
|
114
|
+
specificUrl: `https://other.com/checkout/${anotherValidToken}`,
|
|
115
|
+
fallbackToken: 'fallback-token',
|
|
116
|
+
});
|
|
117
|
+
expect(token).toBe(validToken);
|
|
118
|
+
});
|
|
119
|
+
it('should fall back to specific URL when current URL has no token', () => {
|
|
120
|
+
mockLocation('https://example.com/checkout');
|
|
121
|
+
const token = getCheckoutToken({
|
|
122
|
+
currentUrl: true,
|
|
123
|
+
specificUrl: `https://other.com/checkout/${validToken}`,
|
|
124
|
+
fallbackToken: 'fallback-token',
|
|
125
|
+
});
|
|
126
|
+
expect(token).toBe(validToken);
|
|
127
|
+
});
|
|
128
|
+
it('should fall back to fallback token when others fail', () => {
|
|
129
|
+
mockLocation('https://example.com/checkout');
|
|
130
|
+
const token = getCheckoutToken({
|
|
131
|
+
currentUrl: true,
|
|
132
|
+
specificUrl: 'https://other.com/checkout',
|
|
133
|
+
fallbackToken: validToken,
|
|
134
|
+
});
|
|
135
|
+
expect(token).toBe(validToken);
|
|
136
|
+
});
|
|
137
|
+
it('should return null when all sources fail', () => {
|
|
138
|
+
mockLocation('https://example.com/checkout');
|
|
139
|
+
const token = getCheckoutToken({
|
|
140
|
+
currentUrl: true,
|
|
141
|
+
specificUrl: 'https://other.com/checkout',
|
|
142
|
+
fallbackToken: 'invalid-token',
|
|
143
|
+
});
|
|
144
|
+
expect(token).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
it('should skip current URL when disabled', () => {
|
|
147
|
+
mockLocation(`https://example.com/checkout/${validToken}`);
|
|
148
|
+
const token = getCheckoutToken({
|
|
149
|
+
currentUrl: false,
|
|
150
|
+
specificUrl: `https://other.com/checkout/${anotherValidToken}`,
|
|
151
|
+
});
|
|
152
|
+
expect(token).toBe(anotherValidToken);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('buildCheckoutUrl', () => {
|
|
156
|
+
it('should build correct checkout URL', () => {
|
|
157
|
+
const baseUrl = 'https://example.com/checkout';
|
|
158
|
+
const url = buildCheckoutUrl(baseUrl, validToken);
|
|
159
|
+
expect(url).toBe(`https://example.com/checkout/${validToken}`);
|
|
160
|
+
});
|
|
161
|
+
it('should handle base URL with trailing slash', () => {
|
|
162
|
+
const baseUrl = 'https://example.com/checkout/';
|
|
163
|
+
const url = buildCheckoutUrl(baseUrl, validToken);
|
|
164
|
+
expect(url).toBe(`https://example.com/checkout/${validToken}`);
|
|
165
|
+
});
|
|
166
|
+
it('should throw error for invalid base URL', () => {
|
|
167
|
+
expect(() => buildCheckoutUrl('', validToken)).toThrow('Invalid base URL or checkout token');
|
|
168
|
+
expect(() => buildCheckoutUrl(null, validToken)).toThrow('Invalid base URL or checkout token');
|
|
169
|
+
});
|
|
170
|
+
it('should throw error for invalid token', () => {
|
|
171
|
+
expect(() => buildCheckoutUrl('https://example.com/checkout', 'invalid-token')).toThrow('Invalid base URL or checkout token');
|
|
172
|
+
expect(() => buildCheckoutUrl('https://example.com/checkout', '')).toThrow('Invalid base URL or checkout token');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe('hasCheckoutToken', () => {
|
|
176
|
+
it('should return true when URL contains valid token', () => {
|
|
177
|
+
const url = `https://example.com/checkout/${validToken}`;
|
|
178
|
+
expect(hasCheckoutToken(url)).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
it('should return false when URL has no token', () => {
|
|
181
|
+
const url = 'https://example.com/checkout';
|
|
182
|
+
expect(hasCheckoutToken(url)).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
it('should return false when URL has invalid token', () => {
|
|
185
|
+
const url = 'https://example.com/checkout/invalid-token';
|
|
186
|
+
expect(hasCheckoutToken(url)).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface DeviceInfo {
|
|
2
|
+
userAgent: {
|
|
3
|
+
browser: {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
};
|
|
7
|
+
os: {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
};
|
|
11
|
+
device?: {
|
|
12
|
+
type: string;
|
|
13
|
+
model: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
screenResolution: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
timeZone: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get browser locale
|
|
24
|
+
*/
|
|
25
|
+
export declare function getBrowserLocale(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Collect all device information
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectDeviceInfo(): DeviceInfo;
|
|
30
|
+
/**
|
|
31
|
+
* Get URL parameters for session initialization
|
|
32
|
+
*/
|
|
33
|
+
export declare function getUrlParams(): {
|
|
34
|
+
locale?: string;
|
|
35
|
+
currency?: string;
|
|
36
|
+
utmSource?: string;
|
|
37
|
+
utmMedium?: string;
|
|
38
|
+
utmCampaign?: string;
|
|
39
|
+
};
|