@tagadapay/plugin-sdk 2.8.8 → 2.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/react/config/environment.d.ts +1 -22
  2. package/dist/react/config/environment.js +1 -132
  3. package/dist/react/utils/deviceInfo.d.ts +1 -39
  4. package/dist/react/utils/deviceInfo.js +1 -163
  5. package/dist/react/utils/jwtDecoder.d.ts +1 -14
  6. package/dist/react/utils/jwtDecoder.js +1 -86
  7. package/dist/react/utils/tokenStorage.d.ts +1 -16
  8. package/dist/react/utils/tokenStorage.js +1 -53
  9. package/dist/v2/core/client.d.ts +92 -0
  10. package/dist/v2/core/client.js +386 -0
  11. package/dist/v2/core/config/environment.d.ts +22 -0
  12. package/dist/v2/core/config/environment.js +140 -0
  13. package/dist/v2/core/pathRemapping.js +61 -3
  14. package/dist/v2/core/resources/apiClient.d.ts +8 -0
  15. package/dist/v2/core/resources/apiClient.js +30 -9
  16. package/dist/v2/core/resources/funnel.d.ts +14 -0
  17. package/dist/v2/core/resources/payments.d.ts +23 -0
  18. package/dist/v2/core/types.d.ts +271 -0
  19. package/dist/v2/core/types.js +4 -0
  20. package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
  21. package/dist/v2/core/utils/deviceInfo.js +162 -0
  22. package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
  23. package/dist/v2/core/utils/eventDispatcher.js +24 -0
  24. package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
  25. package/dist/v2/core/utils/jwtDecoder.js +85 -0
  26. package/dist/v2/core/utils/pluginConfig.js +6 -0
  27. package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
  28. package/dist/v2/core/utils/tokenStorage.js +52 -0
  29. package/dist/v2/react/components/DebugDrawer.js +90 -1
  30. package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
  31. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
  32. package/dist/v2/react/hooks/useFunnel.d.ts +1 -1
  33. package/dist/v2/react/hooks/useFunnel.js +209 -32
  34. package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
  35. package/dist/v2/react/hooks/useISOData.js +4 -2
  36. package/dist/v2/react/hooks/useOffersQuery.d.ts +24 -29
  37. package/dist/v2/react/hooks/useOffersQuery.js +164 -204
  38. package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
  39. package/dist/v2/react/providers/TagadaProvider.d.ts +8 -21
  40. package/dist/v2/react/providers/TagadaProvider.js +79 -673
  41. package/package.json +1 -1
@@ -0,0 +1,386 @@
1
+ import { ApiClient } from './resources/apiClient';
2
+ import { detectEnvironment, getEnvironmentConfig } from './config/environment';
3
+ import { loadPluginConfig } from './utils/pluginConfig';
4
+ import { collectDeviceInfo, getBrowserLocale, getUrlParams } from './utils/deviceInfo';
5
+ import { decodeJWTClient, isTokenExpired } from './utils/jwtDecoder';
6
+ import { getClientToken, setClientToken } from './utils/tokenStorage';
7
+ import { EventDispatcher } from './utils/eventDispatcher';
8
+ export class TagadaClient {
9
+ constructor(config = {}) {
10
+ this.eventDispatcher = new EventDispatcher();
11
+ this.tokenPromise = null;
12
+ this.tokenResolver = null;
13
+ this.config = config;
14
+ this.instanceId = Math.random().toString(36).substr(2, 9);
15
+ this.boundHandleStorageChange = this.handleStorageChange.bind(this);
16
+ if (this.config.debugMode) {
17
+ console.log(`[TagadaClient ${this.instanceId}] Initializing...`);
18
+ }
19
+ // Initialize default state
20
+ const env = this.resolveEnvironment();
21
+ const envConfig = getEnvironmentConfig(env);
22
+ this.state = {
23
+ auth: {
24
+ isAuthenticated: false,
25
+ isLoading: false,
26
+ customer: null,
27
+ session: null,
28
+ },
29
+ session: null,
30
+ customer: null,
31
+ locale: {
32
+ locale: 'en-US',
33
+ language: 'en',
34
+ region: 'US',
35
+ messages: {},
36
+ },
37
+ currency: {
38
+ code: 'USD',
39
+ symbol: '$',
40
+ name: 'US Dollar',
41
+ },
42
+ store: null,
43
+ environment: envConfig,
44
+ isLoading: true,
45
+ isInitialized: false,
46
+ isSessionInitialized: false,
47
+ pluginConfig: { basePath: '/', config: {} },
48
+ pluginConfigLoading: !config.rawPluginConfig,
49
+ debugMode: config.debugMode ?? env !== 'production',
50
+ token: null,
51
+ };
52
+ // Initialize API Client
53
+ this.apiClient = new ApiClient({
54
+ baseURL: envConfig.apiConfig.baseUrl,
55
+ });
56
+ // Setup token waiting mechanism
57
+ this.apiClient.setTokenProvider(this.waitForToken.bind(this));
58
+ // Listen for storage changes (cross-tab sync)
59
+ if (typeof window !== 'undefined') {
60
+ window.addEventListener('storage', this.boundHandleStorageChange);
61
+ }
62
+ // Start initialization
63
+ this.initialize();
64
+ }
65
+ /**
66
+ * Cleanup client resources
67
+ */
68
+ destroy() {
69
+ if (typeof window !== 'undefined') {
70
+ window.removeEventListener('storage', this.boundHandleStorageChange);
71
+ }
72
+ if (this.state.debugMode) {
73
+ console.log(`[TagadaClient ${this.instanceId}] Destroyed`);
74
+ }
75
+ this.eventDispatcher.clear();
76
+ }
77
+ /**
78
+ * Handle storage changes (e.g. token update in another tab)
79
+ */
80
+ handleStorageChange() {
81
+ const storedToken = getClientToken();
82
+ // Avoid unnecessary re-initialization if token hasn't changed
83
+ if (storedToken === this.state.token) {
84
+ if (this.state.debugMode) {
85
+ console.log(`[TagadaClient ${this.instanceId}] Token unchanged (ignoring event)`);
86
+ }
87
+ return;
88
+ }
89
+ // Re-run initialization when token may have changed
90
+ if (this.state.debugMode) {
91
+ console.log(`[TagadaClient ${this.instanceId}] Storage changed, re-initializing token...`);
92
+ }
93
+ this.initializeToken();
94
+ }
95
+ /**
96
+ * Subscribe to state changes
97
+ */
98
+ subscribe(listener) {
99
+ return this.eventDispatcher.subscribe(listener);
100
+ }
101
+ /**
102
+ * Get current state
103
+ */
104
+ getState() {
105
+ return this.state;
106
+ }
107
+ /**
108
+ * Update state and notify listeners
109
+ */
110
+ updateState(updates) {
111
+ this.state = { ...this.state, ...updates };
112
+ this.eventDispatcher.notify(this.state);
113
+ }
114
+ /**
115
+ * Resolve environment
116
+ */
117
+ resolveEnvironment() {
118
+ if (this.config.environment)
119
+ return this.config.environment;
120
+ return detectEnvironment();
121
+ }
122
+ /**
123
+ * Main initialization flow
124
+ */
125
+ async initialize() {
126
+ try {
127
+ // 1. Load Plugin Config
128
+ await this.initializePluginConfig();
129
+ // 2. Initialize Token (Background or Blocking based on config)
130
+ if (this.state.pluginConfig.storeId) {
131
+ await this.initializeToken();
132
+ }
133
+ else {
134
+ console.warn('[TagadaClient] No store ID found in plugin config. Skipping token initialization.');
135
+ this.updateState({ isLoading: false, isInitialized: true });
136
+ }
137
+ }
138
+ catch (error) {
139
+ console.error('[TagadaClient] Initialization failed:', error);
140
+ this.updateState({ isLoading: false, isInitialized: true });
141
+ }
142
+ }
143
+ /**
144
+ * Load plugin configuration
145
+ */
146
+ async initializePluginConfig() {
147
+ if (!this.state.pluginConfigLoading)
148
+ return;
149
+ try {
150
+ const configVariant = this.config.localConfig || 'default';
151
+ const config = await loadPluginConfig(configVariant, this.config.rawPluginConfig);
152
+ this.updateState({
153
+ pluginConfig: config,
154
+ pluginConfigLoading: false,
155
+ });
156
+ if (this.state.debugMode) {
157
+ console.log('[TagadaClient] Plugin config loaded:', config);
158
+ }
159
+ }
160
+ catch (error) {
161
+ console.error('[TagadaClient] Failed to load plugin config:', error);
162
+ this.updateState({
163
+ pluginConfig: { basePath: '/', config: {} },
164
+ pluginConfigLoading: false,
165
+ });
166
+ }
167
+ }
168
+ /**
169
+ * Initialize token and session
170
+ */
171
+ async initializeToken() {
172
+ // Check for existing token in URL or storage
173
+ const existingToken = getClientToken();
174
+ const urlParams = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '');
175
+ const queryToken = urlParams.get('token');
176
+ if (this.state.debugMode) {
177
+ console.log(`[TagadaClient ${this.instanceId}] Initializing token...`, {
178
+ hasExistingToken: !!existingToken,
179
+ hasQueryToken: !!queryToken
180
+ });
181
+ }
182
+ let tokenToUse = null;
183
+ let shouldPersist = false;
184
+ if (queryToken) {
185
+ tokenToUse = queryToken;
186
+ shouldPersist = true;
187
+ }
188
+ else if (existingToken && !isTokenExpired(existingToken)) {
189
+ tokenToUse = existingToken;
190
+ }
191
+ if (tokenToUse) {
192
+ this.setToken(tokenToUse);
193
+ // Persist token if it came from query (updates localStorage and fires event)
194
+ // We do this AFTER setToken so state is updated and handleStorageChange sees match
195
+ if (shouldPersist) {
196
+ if (this.state.debugMode) {
197
+ console.log(`[TagadaClient ${this.instanceId}] Persisting query token to storage...`);
198
+ }
199
+ setClientToken(tokenToUse);
200
+ }
201
+ const decodedSession = decodeJWTClient(tokenToUse);
202
+ if (decodedSession) {
203
+ this.updateState({ session: decodedSession });
204
+ await this.initializeSession(decodedSession);
205
+ }
206
+ else {
207
+ console.error('[TagadaClient] Failed to decode token');
208
+ this.updateState({ isInitialized: true, isLoading: false });
209
+ }
210
+ }
211
+ else {
212
+ // Create anonymous token
213
+ const storeId = this.state.pluginConfig.storeId;
214
+ if (storeId) {
215
+ if (this.state.debugMode) {
216
+ console.log(`[TagadaClient ${this.instanceId}] Creating anonymous token for store:`, storeId);
217
+ }
218
+ await this.createAnonymousToken(storeId);
219
+ }
220
+ else {
221
+ this.updateState({ isInitialized: true, isLoading: false });
222
+ }
223
+ }
224
+ }
225
+ /**
226
+ * Set token and resolve waiting requests
227
+ */
228
+ setToken(token) {
229
+ this.apiClient.updateToken(token);
230
+ this.updateState({ token });
231
+ // Notify waiting requests
232
+ if (this.tokenResolver) {
233
+ this.tokenResolver(token);
234
+ this.tokenPromise = null;
235
+ this.tokenResolver = null;
236
+ }
237
+ else {
238
+ // If we set a token but no one was waiting, ensure future requests don't wait unnecessarily
239
+ // Actually, if we have a token, the provider won't be called by ApiClient logic if we handle it right.
240
+ // But to be safe, if we set a token, we can pre-resolve the promise if it exists.
241
+ // Wait, if tokenPromise exists, tokenResolver must exist.
242
+ }
243
+ }
244
+ /**
245
+ * Wait for token to be available
246
+ */
247
+ waitForToken() {
248
+ if (this.apiClient.getCurrentToken()) {
249
+ return Promise.resolve(this.apiClient.getCurrentToken());
250
+ }
251
+ if (!this.tokenPromise) {
252
+ this.tokenPromise = new Promise((resolve) => {
253
+ this.tokenResolver = resolve;
254
+ });
255
+ }
256
+ return this.tokenPromise;
257
+ }
258
+ /**
259
+ * Create anonymous token
260
+ */
261
+ async createAnonymousToken(storeId) {
262
+ try {
263
+ if (this.state.debugMode)
264
+ console.log('[TagadaClient] Creating anonymous token for store:', storeId);
265
+ // We use fetch directly or ApiClient with skipAuth to avoid waiting for itself
266
+ const response = await this.apiClient.post('/api/v1/cms/session/anonymous', { storeId, role: 'anonymous' }, { skipAuth: true });
267
+ this.setToken(response.token);
268
+ setClientToken(response.token);
269
+ const decodedSession = decodeJWTClient(response.token);
270
+ if (decodedSession) {
271
+ this.updateState({ session: decodedSession });
272
+ await this.initializeSession(decodedSession);
273
+ }
274
+ this.updateState({ isSessionInitialized: true });
275
+ }
276
+ catch (error) {
277
+ console.error('[TagadaClient] Failed to create anonymous token:', error);
278
+ this.updateState({ isInitialized: true, isLoading: false });
279
+ }
280
+ }
281
+ /**
282
+ * Initialize session
283
+ */
284
+ async initializeSession(sessionData) {
285
+ try {
286
+ if (this.state.debugMode) {
287
+ console.log(`[TagadaClient ${this.instanceId}] Initializing session...`, { sessionId: sessionData.sessionId });
288
+ }
289
+ const deviceInfo = collectDeviceInfo();
290
+ const urlParams = getUrlParams();
291
+ const browserLocale = getBrowserLocale();
292
+ const sessionInitData = {
293
+ storeId: sessionData.storeId,
294
+ accountId: sessionData.accountId,
295
+ customerId: sessionData.customerId,
296
+ role: sessionData.role,
297
+ browserLocale,
298
+ queryLocale: urlParams.locale,
299
+ queryCurrency: urlParams.currency,
300
+ utmSource: urlParams.utmSource,
301
+ utmMedium: urlParams.utmMedium,
302
+ utmCampaign: urlParams.utmCampaign,
303
+ browser: deviceInfo.userAgent.browser.name,
304
+ browserVersion: deviceInfo.userAgent.browser.version,
305
+ os: deviceInfo.userAgent.os.name,
306
+ osVersion: deviceInfo.userAgent.os.version,
307
+ deviceType: deviceInfo.userAgent.device?.type,
308
+ deviceModel: deviceInfo.userAgent.device?.model,
309
+ screenWidth: deviceInfo.screenResolution.width,
310
+ screenHeight: deviceInfo.screenResolution.height,
311
+ timeZone: deviceInfo.timeZone,
312
+ };
313
+ const response = await this.apiClient.post('/api/v1/cms/session/init', sessionInitData);
314
+ // Update state with session data
315
+ this.updateSessionState(response, sessionData);
316
+ this.updateState({
317
+ isInitialized: true,
318
+ isSessionInitialized: true,
319
+ isLoading: false,
320
+ });
321
+ if (this.state.debugMode)
322
+ console.log('[TagadaClient] Session initialized successfully');
323
+ }
324
+ catch (error) {
325
+ console.error('[TagadaClient] Error initializing session:', error);
326
+ this.updateState({
327
+ isInitialized: true,
328
+ isLoading: false,
329
+ });
330
+ }
331
+ }
332
+ updateSessionState(response, sessionData) {
333
+ // Update Store
334
+ if (response.store) {
335
+ const storeData = response.store;
336
+ const storeConfig = {
337
+ ...response.store,
338
+ presentmentCurrencies: storeData.presentmentCurrencies || [response.store.currency || 'USD'],
339
+ chargeCurrencies: storeData.chargeCurrencies || [response.store.currency || 'USD'],
340
+ };
341
+ this.updateState({ store: storeConfig });
342
+ }
343
+ // Update Locale
344
+ const localeConfig = {
345
+ locale: response.locale,
346
+ language: response.locale.split('-')[0],
347
+ region: response.locale.split('-')[1] ?? 'US',
348
+ messages: response.messages ?? {},
349
+ };
350
+ this.updateState({ locale: localeConfig });
351
+ // Update Currency
352
+ if (response.store) {
353
+ const currencyConfig = {
354
+ code: response.store.currency,
355
+ symbol: this.getCurrencySymbol(response.store.currency),
356
+ name: this.getCurrencyName(response.store.currency),
357
+ };
358
+ this.updateState({ currency: currencyConfig });
359
+ }
360
+ // Update Customer & Auth
361
+ const authState = {
362
+ isAuthenticated: response.customer?.isAuthenticated ?? false,
363
+ isLoading: false,
364
+ customer: response.customer ?? null,
365
+ session: sessionData,
366
+ };
367
+ this.updateState({
368
+ customer: response.customer ?? null,
369
+ auth: authState
370
+ });
371
+ }
372
+ // Helper methods
373
+ getCurrencySymbol(code) {
374
+ const symbols = {
375
+ USD: '$', EUR: '€', GBP: '£', JPY: '¥', CAD: 'C$', AUD: 'A$',
376
+ };
377
+ return symbols[code] || code;
378
+ }
379
+ getCurrencyName(code) {
380
+ const names = {
381
+ USD: 'US Dollar', EUR: 'Euro', GBP: 'British Pound',
382
+ JPY: 'Japanese Yen', CAD: 'Canadian Dollar', AUD: 'Australian Dollar',
383
+ };
384
+ return names[code] || code;
385
+ }
386
+ }
@@ -0,0 +1,22 @@
1
+ import { ApiConfig, Environment, EnvironmentConfig } from '../types';
2
+ /**
3
+ * Environment configurations for different deployment environments
4
+ */
5
+ export declare const ENVIRONMENT_CONFIGS: Record<Environment, ApiConfig>;
6
+ /**
7
+ * Get the environment configuration based on the current environment
8
+ */
9
+ export declare function getEnvironmentConfig(environment?: Environment): EnvironmentConfig;
10
+ /**
11
+ * Build a complete API URL from environment config and endpoint path
12
+ */
13
+ export declare function buildApiUrl(config: EnvironmentConfig, endpointPath: string): string;
14
+ /**
15
+ * Get a specific endpoint URL
16
+ */
17
+ export declare function getEndpointUrl(config: EnvironmentConfig, category: keyof ApiConfig['endpoints'], endpoint: string): string;
18
+ /**
19
+ * Auto-detect environment based on hostname, URL patterns, and deployment context
20
+ * Works with any build tool or deployment system
21
+ */
22
+ export declare function detectEnvironment(): Environment;
@@ -0,0 +1,140 @@
1
+ import { resolveEnvValue } from '../utils/env';
2
+ /**
3
+ * Environment configurations for different deployment environments
4
+ */
5
+ export const ENVIRONMENT_CONFIGS = {
6
+ production: {
7
+ baseUrl: 'https://app.tagadapay.com',
8
+ endpoints: {
9
+ checkout: {
10
+ sessionInit: '/api/v1/checkout/session/init',
11
+ sessionStatus: '/api/v1/checkout/session/status',
12
+ },
13
+ customer: {
14
+ profile: '/api/v1/customer/profile',
15
+ session: '/api/v1/customer/session',
16
+ },
17
+ store: {
18
+ config: '/api/v1/store/config',
19
+ },
20
+ },
21
+ },
22
+ development: {
23
+ baseUrl: 'https://app.tagadapay.dev',
24
+ endpoints: {
25
+ checkout: {
26
+ sessionInit: '/api/v1/checkout/session/init',
27
+ sessionStatus: '/api/v1/checkout/session/status',
28
+ },
29
+ customer: {
30
+ profile: '/api/v1/customer/profile',
31
+ session: '/api/v1/customer/session',
32
+ },
33
+ store: {
34
+ config: '/api/v1/store/config',
35
+ },
36
+ },
37
+ },
38
+ local: {
39
+ baseUrl: 'http://app.localhost:3000',
40
+ endpoints: {
41
+ checkout: {
42
+ sessionInit: '/api/v1/checkout/session/init',
43
+ sessionStatus: '/api/v1/checkout/session/status',
44
+ },
45
+ customer: {
46
+ profile: '/api/v1/customer/profile',
47
+ session: '/api/v1/customer/session',
48
+ },
49
+ store: {
50
+ config: '/api/v1/store/config',
51
+ },
52
+ },
53
+ },
54
+ };
55
+ /**
56
+ * Get the environment configuration based on the current environment
57
+ */
58
+ export function getEnvironmentConfig(environment = 'local') {
59
+ const apiConfig = ENVIRONMENT_CONFIGS[environment];
60
+ if (!apiConfig) {
61
+ console.warn(`Unknown environment: ${environment}. Falling back to local.`);
62
+ return {
63
+ environment: 'local',
64
+ apiConfig: ENVIRONMENT_CONFIGS.local,
65
+ };
66
+ }
67
+ return {
68
+ environment,
69
+ apiConfig,
70
+ };
71
+ }
72
+ /**
73
+ * Build a complete API URL from environment config and endpoint path
74
+ */
75
+ export function buildApiUrl(config, endpointPath) {
76
+ return `${config.apiConfig.baseUrl}${endpointPath}`;
77
+ }
78
+ /**
79
+ * Get a specific endpoint URL
80
+ */
81
+ export function getEndpointUrl(config, category, endpoint) {
82
+ const categoryEndpoints = config.apiConfig.endpoints[category];
83
+ const endpointPath = categoryEndpoints[endpoint];
84
+ if (!endpointPath) {
85
+ throw new Error(`Endpoint not found: ${category}.${endpoint}`);
86
+ }
87
+ return buildApiUrl(config, endpointPath);
88
+ }
89
+ /**
90
+ * Auto-detect environment based on hostname, URL patterns, and deployment context
91
+ * Works with any build tool or deployment system
92
+ */
93
+ export function detectEnvironment() {
94
+ // 1. Check environment variables first
95
+ const envVar = resolveEnvValue('TAGADA_ENVIRONMENT') || resolveEnvValue('TAGADA_ENV');
96
+ if (envVar) {
97
+ const normalized = envVar.toLowerCase();
98
+ if (normalized === 'production' || normalized === 'development' || normalized === 'local') {
99
+ return normalized;
100
+ }
101
+ }
102
+ // Check if we're in browser
103
+ if (typeof window === 'undefined') {
104
+ return 'local'; // SSR fallback
105
+ }
106
+ const hostname = window.location.hostname;
107
+ const href = window.location.href;
108
+ // Production: deployed to production domains
109
+ if (hostname === 'app.tagadapay.com' ||
110
+ hostname.includes('tagadapay.com') ||
111
+ hostname.includes('yourproductiondomain.com')) {
112
+ return 'production';
113
+ }
114
+ // Development: deployed to staging/dev domains or has dev indicators
115
+ if (hostname === 'app.tagadapay.dev' ||
116
+ hostname.includes('tagadapay.dev') || // ✅ app.tagadapay.dev and subdomains
117
+ hostname.includes('vercel.app') ||
118
+ hostname.includes('netlify.app') ||
119
+ hostname.includes('surge.sh') ||
120
+ hostname.includes('github.io') ||
121
+ hostname.includes('herokuapp.com') ||
122
+ hostname.includes('railway.app') ||
123
+ href.includes('?env=dev') ||
124
+ href.includes('?dev=true') ||
125
+ href.includes('#dev')) {
126
+ return 'development';
127
+ }
128
+ // Local: localhost, local IPs, or local domains
129
+ if (hostname === 'localhost' ||
130
+ hostname.startsWith('127.') ||
131
+ hostname.startsWith('192.168.') ||
132
+ hostname.startsWith('10.') ||
133
+ hostname.includes('.local') ||
134
+ hostname === '' ||
135
+ hostname === '0.0.0.0') {
136
+ return 'local';
137
+ }
138
+ // Default fallback for unknown domains (safer to use development)
139
+ return 'production';
140
+ }
@@ -395,6 +395,57 @@ function matchesPathPattern(pathname, pattern) {
395
395
  return { matched: pathname === pattern, params: {} };
396
396
  }
397
397
  }
398
+ /**
399
+ * Map parameter names from external pattern to internal pattern
400
+ * Assumes parameters appear in the same order in both patterns
401
+ *
402
+ * @param externalParams - Parameters extracted from external URL
403
+ * @param externalPattern - External path pattern (e.g., /off1/:lolo)
404
+ * @param internalPattern - Internal path pattern (e.g., /post/:orderId)
405
+ * @returns Mapped parameters with internal param names
406
+ *
407
+ * @example
408
+ * ```typescript
409
+ * mapParamNames(
410
+ * {lolo: 'order_123'},
411
+ * '/off1/:lolo',
412
+ * '/post/:orderId'
413
+ * )
414
+ * // Returns: {orderId: 'order_123'}
415
+ * ```
416
+ */
417
+ function mapParamNames(externalParams, externalPattern, internalPattern) {
418
+ // Extract parameter names from patterns
419
+ const externalParamNames = extractParamNames(externalPattern);
420
+ const internalParamNames = extractParamNames(internalPattern);
421
+ // If no params or count mismatch, return original params
422
+ if (externalParamNames.length === 0 ||
423
+ externalParamNames.length !== internalParamNames.length) {
424
+ return externalParams;
425
+ }
426
+ // Map parameters by position
427
+ const mappedParams = {};
428
+ externalParamNames.forEach((externalName, index) => {
429
+ const internalName = internalParamNames[index];
430
+ const value = externalParams[externalName];
431
+ if (value !== undefined) {
432
+ mappedParams[internalName] = value;
433
+ }
434
+ });
435
+ return mappedParams;
436
+ }
437
+ /**
438
+ * Extract parameter names from a path pattern
439
+ *
440
+ * @param pattern - Path pattern (e.g., /post/:orderId/item/:itemId)
441
+ * @returns Array of parameter names (e.g., ['orderId', 'itemId'])
442
+ */
443
+ function extractParamNames(pattern) {
444
+ const matches = pattern.match(/:([a-zA-Z0-9_]+)/g);
445
+ if (!matches)
446
+ return [];
447
+ return matches.map(m => m.substring(1)); // Remove ':' prefix
448
+ }
398
449
  /**
399
450
  * Match a route and extract URL parameters
400
451
  *
@@ -496,9 +547,16 @@ export function matchRoute(internalPath) {
496
547
  if (externalPattern) {
497
548
  const externalMatch = matchesPathPattern(currentPath, externalPattern);
498
549
  if (externalMatch.matched) {
499
- // Since we enforce matching parameter names in CRM,
500
- // the params extracted from external pattern will have the correct names
501
- return { matched: true, params: externalMatch.params };
550
+ // Map parameter names from external pattern to internal pattern
551
+ // External: /off1/:lolo {lolo: 'value'}
552
+ // Internal: /post/:orderId → expects {orderId: 'value'}
553
+ console.log('[TagadaPay SDK] 🔄 Mapping params from external to internal pattern');
554
+ console.log('[TagadaPay SDK] External params:', externalMatch.params);
555
+ console.log('[TagadaPay SDK] External pattern:', externalPattern);
556
+ console.log('[TagadaPay SDK] Internal pattern:', internalPath);
557
+ const mappedParams = mapParamNames(externalMatch.params, externalPattern, internalPath);
558
+ console.log('[TagadaPay SDK] Mapped params:', mappedParams);
559
+ return { matched: true, params: mappedParams };
502
560
  }
503
561
  }
504
562
  // Fallback: try to extract from current URL using internal pattern
@@ -3,15 +3,23 @@
3
3
  * Shared between all resource clients
4
4
  */
5
5
  import { AxiosInstance, AxiosRequestConfig } from 'axios';
6
+ declare module 'axios' {
7
+ interface AxiosRequestConfig {
8
+ skipAuth?: boolean;
9
+ }
10
+ }
6
11
  export interface ApiClientConfig {
7
12
  baseURL: string;
8
13
  headers?: Record<string, string>;
9
14
  timeout?: number;
10
15
  }
16
+ export type TokenProvider = () => Promise<string | null>;
11
17
  export declare class ApiClient {
12
18
  axios: AxiosInstance;
13
19
  private currentToken;
20
+ private tokenProvider;
14
21
  constructor(config: ApiClientConfig);
22
+ setTokenProvider(provider: TokenProvider): void;
15
23
  get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
16
24
  post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;
17
25
  put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T>;