@tagadapay/plugin-sdk 2.8.9 → 3.0.1

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 (54) hide show
  1. package/README.md +14 -14
  2. package/dist/index.js +1 -1
  3. package/dist/react/hooks/usePluginConfig.d.ts +1 -0
  4. package/dist/react/hooks/usePluginConfig.js +69 -18
  5. package/dist/react/providers/TagadaProvider.js +1 -4
  6. package/dist/v2/core/client.d.ts +22 -0
  7. package/dist/v2/core/client.js +90 -1
  8. package/dist/v2/core/config/environment.d.ts +24 -2
  9. package/dist/v2/core/config/environment.js +58 -25
  10. package/dist/v2/core/funnelClient.d.ts +84 -0
  11. package/dist/v2/core/funnelClient.js +252 -0
  12. package/dist/v2/core/index.d.ts +2 -0
  13. package/dist/v2/core/index.js +3 -0
  14. package/dist/v2/core/resources/apiClient.d.ts +5 -0
  15. package/dist/v2/core/resources/apiClient.js +47 -0
  16. package/dist/v2/core/resources/funnel.d.ts +8 -0
  17. package/dist/v2/core/resources/offers.d.ts +182 -8
  18. package/dist/v2/core/resources/offers.js +25 -0
  19. package/dist/v2/core/resources/products.d.ts +5 -0
  20. package/dist/v2/core/resources/products.js +15 -1
  21. package/dist/v2/core/types.d.ts +1 -0
  22. package/dist/v2/core/utils/funnelQueryKeys.d.ts +23 -0
  23. package/dist/v2/core/utils/funnelQueryKeys.js +23 -0
  24. package/dist/v2/core/utils/index.d.ts +2 -0
  25. package/dist/v2/core/utils/index.js +2 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
  27. package/dist/v2/core/utils/pluginConfig.js +84 -22
  28. package/dist/v2/core/utils/sessionStorage.d.ts +20 -0
  29. package/dist/v2/core/utils/sessionStorage.js +39 -0
  30. package/dist/v2/index.d.ts +3 -2
  31. package/dist/v2/index.js +1 -1
  32. package/dist/v2/react/components/ApplePayButton.js +1 -1
  33. package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +3 -0
  34. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +4 -3
  35. package/dist/v2/react/hooks/useClubOffers.d.ts +2 -2
  36. package/dist/v2/react/hooks/useFunnel.d.ts +27 -38
  37. package/dist/v2/react/hooks/useFunnel.js +22 -660
  38. package/dist/v2/react/hooks/useFunnelLegacy.d.ts +52 -0
  39. package/dist/v2/react/hooks/useFunnelLegacy.js +733 -0
  40. package/dist/v2/react/hooks/useOfferQuery.d.ts +109 -0
  41. package/dist/v2/react/hooks/useOfferQuery.js +483 -0
  42. package/dist/v2/react/hooks/useOffersQuery.d.ts +10 -58
  43. package/dist/v2/react/hooks/useOffersQuery.js +110 -8
  44. package/dist/v2/react/hooks/useProductsQuery.d.ts +1 -0
  45. package/dist/v2/react/hooks/useProductsQuery.js +10 -6
  46. package/dist/v2/react/index.d.ts +7 -4
  47. package/dist/v2/react/index.js +4 -2
  48. package/dist/v2/react/providers/TagadaProvider.d.ts +45 -2
  49. package/dist/v2/react/providers/TagadaProvider.js +116 -3
  50. package/dist/v2/standalone/index.d.ts +20 -0
  51. package/dist/v2/standalone/index.js +22 -0
  52. package/dist/v2/vue/index.d.ts +6 -0
  53. package/dist/v2/vue/index.js +10 -0
  54. package/package.json +6 -1
@@ -0,0 +1,84 @@
1
+ import { ApiClient } from './resources/apiClient';
2
+ import { SimpleFunnelContext, FunnelAction, FunnelNavigationResult } from './resources/funnel';
3
+ import { PluginConfig } from './utils/pluginConfig';
4
+ export interface FunnelClientConfig {
5
+ apiClient: ApiClient;
6
+ debugMode?: boolean;
7
+ pluginConfig?: PluginConfig;
8
+ environment?: {
9
+ environment: 'local' | 'development' | 'production' | 'staging';
10
+ };
11
+ /**
12
+ * Automatically redirect to result.url after navigation (default: true)
13
+ * Set to false if you want to handle navigation manually
14
+ */
15
+ autoRedirect?: boolean;
16
+ }
17
+ export interface FunnelState {
18
+ context: SimpleFunnelContext | null;
19
+ isLoading: boolean;
20
+ isInitialized: boolean;
21
+ isNavigating: boolean;
22
+ error: Error | null;
23
+ sessionError: Error | null;
24
+ }
25
+ export declare class FunnelClient {
26
+ state: FunnelState;
27
+ private resource;
28
+ private eventDispatcher;
29
+ private config;
30
+ private isInitializing;
31
+ private initializationAttempted;
32
+ constructor(config: FunnelClientConfig);
33
+ /**
34
+ * Update configuration (e.g. when plugin config loads)
35
+ */
36
+ setConfig(config: Partial<FunnelClientConfig>): void;
37
+ /**
38
+ * Subscribe to state changes
39
+ */
40
+ subscribe(listener: (state: FunnelState) => void): () => void;
41
+ /**
42
+ * Get current state
43
+ */
44
+ getState(): FunnelState;
45
+ /**
46
+ * Initialize session with automatic detection (cookies, URL, etc.)
47
+ */
48
+ autoInitialize(authSession: {
49
+ customerId: string;
50
+ sessionId: string;
51
+ }, store: {
52
+ id: string;
53
+ accountId: string;
54
+ }, funnelId?: string): Promise<SimpleFunnelContext | null>;
55
+ /**
56
+ * Manual initialization
57
+ */
58
+ initialize(authSession: {
59
+ customerId: string;
60
+ sessionId: string;
61
+ }, store: {
62
+ id: string;
63
+ accountId: string;
64
+ }, funnelId?: string, entryStepId?: string): Promise<SimpleFunnelContext<{}>>;
65
+ /**
66
+ * Navigate
67
+ */
68
+ navigate(event: FunnelAction): Promise<FunnelNavigationResult>;
69
+ /**
70
+ * Refresh session data
71
+ */
72
+ refreshSession(): Promise<SimpleFunnelContext<{}> | undefined>;
73
+ /**
74
+ * Update context data
75
+ */
76
+ updateContext(updates: Partial<SimpleFunnelContext>): Promise<void>;
77
+ /**
78
+ * End session
79
+ */
80
+ endSession(): Promise<void>;
81
+ private updateState;
82
+ private handleSessionSuccess;
83
+ private enrichContext;
84
+ }
@@ -0,0 +1,252 @@
1
+ import { FunnelResource } from './resources/funnel';
2
+ import { EventDispatcher } from './utils/eventDispatcher';
3
+ import { getFunnelSessionCookie, setFunnelSessionCookie } from './utils/sessionStorage';
4
+ import { detectEnvironment } from './config/environment';
5
+ export class FunnelClient {
6
+ constructor(config) {
7
+ this.eventDispatcher = new EventDispatcher();
8
+ // Guards
9
+ this.isInitializing = false;
10
+ this.initializationAttempted = false;
11
+ this.config = config;
12
+ this.resource = new FunnelResource(config.apiClient);
13
+ this.state = {
14
+ context: null,
15
+ isLoading: false,
16
+ isInitialized: false,
17
+ isNavigating: false,
18
+ error: null,
19
+ sessionError: null,
20
+ };
21
+ }
22
+ /**
23
+ * Update configuration (e.g. when plugin config loads)
24
+ */
25
+ setConfig(config) {
26
+ this.config = { ...this.config, ...config };
27
+ }
28
+ /**
29
+ * Subscribe to state changes
30
+ */
31
+ subscribe(listener) {
32
+ return this.eventDispatcher.subscribe(listener);
33
+ }
34
+ /**
35
+ * Get current state
36
+ */
37
+ getState() {
38
+ return this.state;
39
+ }
40
+ /**
41
+ * Initialize session with automatic detection (cookies, URL, etc.)
42
+ */
43
+ async autoInitialize(authSession, store, funnelId) {
44
+ if (this.state.context)
45
+ return this.state.context;
46
+ if (this.isInitializing)
47
+ return null;
48
+ if (this.initializationAttempted)
49
+ return null;
50
+ this.initializationAttempted = true;
51
+ this.isInitializing = true;
52
+ this.updateState({ isLoading: true, error: null });
53
+ try {
54
+ // URL params
55
+ const params = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '');
56
+ const urlFunnelId = params.get('funnelId');
57
+ const effectiveFunnelId = urlFunnelId || funnelId;
58
+ let existingSessionId = params.get('funnelSessionId');
59
+ // Cookie fallback
60
+ if (!existingSessionId) {
61
+ existingSessionId = getFunnelSessionCookie() || null;
62
+ }
63
+ if (this.config.debugMode) {
64
+ console.log('🚀 [FunnelClient] Auto-initializing...', { existingSessionId, effectiveFunnelId });
65
+ }
66
+ // Note: We proceed even without funnelId/sessionId - the backend will create a new anonymous session if needed
67
+ const response = await this.resource.initialize({
68
+ cmsSession: {
69
+ customerId: authSession.customerId,
70
+ sessionId: authSession.sessionId,
71
+ storeId: store.id,
72
+ accountId: store.accountId,
73
+ },
74
+ funnelId: effectiveFunnelId,
75
+ existingSessionId: existingSessionId || undefined,
76
+ currentUrl: typeof window !== 'undefined' ? window.location.href : undefined,
77
+ });
78
+ if (response.success && response.context) {
79
+ const enriched = this.enrichContext(response.context);
80
+ this.handleSessionSuccess(enriched);
81
+ return enriched;
82
+ }
83
+ else {
84
+ throw new Error(response.error || 'Failed to initialize funnel session');
85
+ }
86
+ }
87
+ catch (error) {
88
+ const err = error instanceof Error ? error : new Error(String(error));
89
+ this.updateState({ error: err, isLoading: false });
90
+ if (this.config.debugMode) {
91
+ console.error('❌ [FunnelClient] Init failed:', err);
92
+ }
93
+ throw err;
94
+ }
95
+ finally {
96
+ this.isInitializing = false;
97
+ }
98
+ }
99
+ /**
100
+ * Manual initialization
101
+ */
102
+ async initialize(authSession, store, funnelId, entryStepId) {
103
+ this.updateState({ isLoading: true, error: null });
104
+ try {
105
+ const response = await this.resource.initialize({
106
+ cmsSession: {
107
+ customerId: authSession.customerId,
108
+ sessionId: authSession.sessionId,
109
+ storeId: store.id,
110
+ accountId: store.accountId,
111
+ },
112
+ funnelId: funnelId,
113
+ entryStepId,
114
+ currentUrl: typeof window !== 'undefined' ? window.location.href : undefined,
115
+ });
116
+ if (response.success && response.context) {
117
+ const enriched = this.enrichContext(response.context);
118
+ this.handleSessionSuccess(enriched);
119
+ return enriched;
120
+ }
121
+ else {
122
+ throw new Error(response.error || 'Failed to initialize');
123
+ }
124
+ }
125
+ catch (error) {
126
+ const err = error instanceof Error ? error : new Error(String(error));
127
+ this.updateState({ error: err, isLoading: false });
128
+ throw err;
129
+ }
130
+ }
131
+ /**
132
+ * Navigate
133
+ */
134
+ async navigate(event) {
135
+ if (!this.state.context?.sessionId)
136
+ throw new Error('No active session');
137
+ this.updateState({ isNavigating: true, isLoading: true });
138
+ try {
139
+ const response = await this.resource.navigate({
140
+ sessionId: this.state.context.sessionId,
141
+ event
142
+ });
143
+ if (response.success && response.result) {
144
+ // Refresh session to get updated context
145
+ await this.refreshSession();
146
+ this.updateState({ isNavigating: false, isLoading: false });
147
+ const result = response.result;
148
+ // Auto-redirect if enabled (default: true) and result has a URL
149
+ const shouldAutoRedirect = this.config.autoRedirect !== false; // Default to true
150
+ if (shouldAutoRedirect && result?.url && typeof window !== 'undefined') {
151
+ if (this.config.debugMode) {
152
+ console.log('🚀 [FunnelClient] Auto-redirecting to:', result.url);
153
+ }
154
+ window.location.href = result.url;
155
+ }
156
+ return result;
157
+ }
158
+ throw new Error(response.error || 'Navigation failed');
159
+ }
160
+ catch (error) {
161
+ const err = error instanceof Error ? error : new Error(String(error));
162
+ this.updateState({ error: err, isNavigating: false, isLoading: false });
163
+ throw err;
164
+ }
165
+ }
166
+ /**
167
+ * Refresh session data
168
+ */
169
+ async refreshSession() {
170
+ if (!this.state.context?.sessionId)
171
+ return;
172
+ try {
173
+ const response = await this.resource.getSession(this.state.context.sessionId);
174
+ if (response.success && response.context) {
175
+ const enriched = this.enrichContext(response.context);
176
+ this.updateState({ context: enriched, sessionError: null });
177
+ return enriched;
178
+ }
179
+ }
180
+ catch (error) {
181
+ this.updateState({ sessionError: error instanceof Error ? error : new Error(String(error)) });
182
+ }
183
+ }
184
+ /**
185
+ * Update context data
186
+ */
187
+ async updateContext(updates) {
188
+ if (!this.state.context?.sessionId)
189
+ throw new Error('No active session');
190
+ this.updateState({ isLoading: true });
191
+ try {
192
+ const response = await this.resource.updateContext(this.state.context.sessionId, { contextUpdates: updates });
193
+ if (response.success) {
194
+ await this.refreshSession();
195
+ }
196
+ else {
197
+ throw new Error(response.error || 'Failed to update context');
198
+ }
199
+ }
200
+ finally {
201
+ this.updateState({ isLoading: false });
202
+ }
203
+ }
204
+ /**
205
+ * End session
206
+ */
207
+ async endSession() {
208
+ if (!this.state.context?.sessionId)
209
+ return;
210
+ try {
211
+ await this.resource.endSession(this.state.context.sessionId);
212
+ }
213
+ finally {
214
+ this.state.context = null;
215
+ this.updateState({ context: null, isInitialized: false });
216
+ if (typeof document !== 'undefined') {
217
+ // Clear cookie via import or manually if needed, but we have utility for that
218
+ // We should probably import clearFunnelSessionCookie
219
+ }
220
+ }
221
+ }
222
+ // Private helpers
223
+ updateState(updates) {
224
+ this.state = { ...this.state, ...updates };
225
+ this.eventDispatcher.notify(this.state);
226
+ }
227
+ handleSessionSuccess(context) {
228
+ setFunnelSessionCookie(context.sessionId);
229
+ this.updateState({
230
+ context,
231
+ isLoading: false,
232
+ isInitialized: true,
233
+ error: null,
234
+ sessionError: null
235
+ });
236
+ }
237
+ enrichContext(ctx) {
238
+ const env = this.config.environment?.environment || detectEnvironment();
239
+ if (env !== 'local')
240
+ return ctx;
241
+ const localResources = this.config.pluginConfig?.staticResources || {};
242
+ if (Object.keys(localResources).length === 0)
243
+ return ctx;
244
+ return {
245
+ ...ctx,
246
+ static: {
247
+ ...localResources,
248
+ ...(ctx.static || {})
249
+ }
250
+ };
251
+ }
252
+ }
@@ -4,6 +4,8 @@
4
4
  */
5
5
  export * from './utils';
6
6
  export * from './resources';
7
+ export * from './client';
8
+ export * from './funnelClient';
7
9
  export * from './pathRemapping';
8
10
  export * from './googleAutocomplete';
9
11
  export * from './isoData';
@@ -6,6 +6,9 @@
6
6
  export * from './utils';
7
7
  // Export resources (axios-based API clients)
8
8
  export * from './resources';
9
+ // Export clients (stateful logic)
10
+ export * from './client';
11
+ export * from './funnelClient';
9
12
  // Export path remapping helpers (framework-agnostic)
10
13
  export * from './pathRemapping';
11
14
  // Export legacy files that are still needed
@@ -18,6 +18,9 @@ export declare class ApiClient {
18
18
  axios: AxiosInstance;
19
19
  private currentToken;
20
20
  private tokenProvider;
21
+ private requestHistory;
22
+ private readonly WINDOW_MS;
23
+ private readonly MAX_REQUESTS;
21
24
  constructor(config: ApiClientConfig);
22
25
  setTokenProvider(provider: TokenProvider): void;
23
26
  get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T>;
@@ -30,4 +33,6 @@ export declare class ApiClient {
30
33
  updateToken(token: string | null): void;
31
34
  getCurrentToken(): string | null;
32
35
  updateConfig(config: Partial<ApiClientConfig>): void;
36
+ private checkRequestLimit;
37
+ private cleanupHistory;
33
38
  }
@@ -7,6 +7,10 @@ export class ApiClient {
7
7
  constructor(config) {
8
8
  this.currentToken = null;
9
9
  this.tokenProvider = null;
10
+ // Circuit breaker state
11
+ this.requestHistory = new Map();
12
+ this.WINDOW_MS = 5000; // 5 seconds window
13
+ this.MAX_REQUESTS = 30; // Max 30 requests per endpoint in window
10
14
  this.axios = axios.create({
11
15
  baseURL: config.baseURL,
12
16
  timeout: config.timeout || 30000,
@@ -15,8 +19,22 @@ export class ApiClient {
15
19
  ...config.headers,
16
20
  },
17
21
  });
22
+ // Cleanup interval for circuit breaker history
23
+ if (typeof setInterval !== 'undefined') {
24
+ setInterval(() => this.cleanupHistory(), 10000);
25
+ }
18
26
  // Request interceptor for logging and auth
19
27
  this.axios.interceptors.request.use(async (config) => {
28
+ // Circuit Breaker Check
29
+ if (config.url) {
30
+ try {
31
+ this.checkRequestLimit(`${config.method?.toUpperCase()}:${config.url}`);
32
+ }
33
+ catch (error) {
34
+ console.error('[SDK] 🛑 Request blocked by Circuit Breaker:', error);
35
+ return Promise.reject(error);
36
+ }
37
+ }
20
38
  // Check if we need to wait for token
21
39
  if (!config.skipAuth && !this.currentToken && this.tokenProvider) {
22
40
  try {
@@ -113,4 +131,33 @@ export class ApiClient {
113
131
  }
114
132
  console.log('[SDK] ApiClient configuration updated');
115
133
  }
134
+ // Circuit Breaker Implementation
135
+ checkRequestLimit(key) {
136
+ const now = Date.now();
137
+ const history = this.requestHistory.get(key);
138
+ if (!history) {
139
+ this.requestHistory.set(key, { count: 1, firstRequestTime: now });
140
+ return;
141
+ }
142
+ if (now - history.firstRequestTime > this.WINDOW_MS) {
143
+ // Window expired, reset
144
+ this.requestHistory.set(key, { count: 1, firstRequestTime: now });
145
+ return;
146
+ }
147
+ history.count++;
148
+ if (history.count > this.MAX_REQUESTS) {
149
+ const error = new Error(`Circuit Breaker: Too many requests to ${key} (${history.count} in ${this.WINDOW_MS}ms)`);
150
+ // Add a property to identify this as a circuit breaker error
151
+ error.isCircuitBreaker = true;
152
+ throw error;
153
+ }
154
+ }
155
+ cleanupHistory() {
156
+ const now = Date.now();
157
+ for (const [key, history] of this.requestHistory.entries()) {
158
+ if (now - history.firstRequestTime > this.WINDOW_MS) {
159
+ this.requestHistory.delete(key);
160
+ }
161
+ }
162
+ }
116
163
  }
@@ -344,6 +344,7 @@ export interface FunnelNavigationAction {
344
344
  }
345
345
  export interface FunnelNavigationResult {
346
346
  stepId: string;
347
+ url?: string;
347
348
  action: FunnelNavigationAction;
348
349
  context: SimpleFunnelContext;
349
350
  tracking?: {
@@ -408,6 +409,13 @@ export interface SimpleFunnelContext<TCustom = {}> {
408
409
  * - Standard keys provide IntelliSense, custom keys always allowed
409
410
  */
410
411
  resources?: FunnelResourceMap<TCustom>;
412
+ /**
413
+ * Static resources from plugin manifest (type: "static")
414
+ * - Configured in funnel editor's Static Resources tab
415
+ * - Available at runtime as context.static
416
+ * - Example: context.static.offer.id for statically configured offers
417
+ */
418
+ static?: Record<string, any>;
411
419
  /**
412
420
  * Legacy/Custom metadata
413
421
  * For backward compatibility and flexible unstructured data
@@ -3,44 +3,205 @@
3
3
  * Axios-based API client for offers endpoints
4
4
  */
5
5
  import { ApiClient } from './apiClient';
6
- export interface OfferItem {
6
+ import type { CheckoutSessionState, OrderSummary, OrderSummaryItem, VariantOption } from './postPurchases';
7
+ export type { CheckoutSessionState, OrderSummary, OrderSummaryItem, VariantOption };
8
+ /**
9
+ * Currency option with rate, amount, and lock info
10
+ */
11
+ export interface OfferCurrencyOption {
12
+ rate: number;
13
+ amount: number;
14
+ lock?: boolean;
15
+ date?: string;
16
+ }
17
+ /**
18
+ * Product info within an offer
19
+ */
20
+ export interface OfferProduct {
21
+ id: string;
22
+ name: string;
23
+ description: string | null;
24
+ storeId?: string;
25
+ externalProductId?: string | null;
26
+ externalCollectionIds?: string[] | null;
27
+ active?: boolean;
28
+ isShippable?: boolean;
29
+ isTaxable?: boolean;
30
+ taxCategory?: string;
31
+ unitLabel?: string;
32
+ accountingCode?: string;
33
+ accountId?: string;
34
+ autoSync?: boolean;
35
+ creditPrice?: number | null;
36
+ createdAt?: string;
37
+ updatedAt?: string;
38
+ storefrontRawData?: any;
39
+ }
40
+ /**
41
+ * Variant info within an offer
42
+ */
43
+ export interface OfferVariant {
44
+ id: string;
45
+ productId?: string;
46
+ default?: boolean;
47
+ name: string;
48
+ description?: string | null;
49
+ sku?: string;
50
+ externalVariantId?: string | null;
51
+ price?: number | null;
52
+ currency?: string | null;
53
+ compareAtPrice?: number | null;
54
+ grams?: number | null;
55
+ imageUrl: string | null;
56
+ active?: boolean;
57
+ accountId?: string | null;
58
+ creditPrice?: number | null;
59
+ createdAt?: string;
60
+ updatedAt?: string;
61
+ product?: OfferProduct | null;
62
+ }
63
+ /**
64
+ * Price info within an offer line item
65
+ */
66
+ export interface OfferLineItemPrice {
7
67
  id: string;
68
+ createdAt?: string;
69
+ updatedAt?: string;
70
+ accountId?: string;
71
+ productId?: string;
72
+ variantId: string;
73
+ default?: boolean;
74
+ currencyOptions: Record<string, OfferCurrencyOption>;
75
+ recurring?: boolean;
76
+ billingTiming?: string;
77
+ interval?: string;
78
+ intervalCount?: number;
79
+ rebillWithOrder?: boolean;
80
+ rebillMode?: string | null;
81
+ rebillStepIntervalMs?: number | null;
82
+ isExternalSellingPlan?: boolean;
83
+ isInternalSellingPlan?: boolean;
84
+ shopifySellingPlanId?: string | null;
85
+ creditsPerRebill?: number | null;
86
+ deliverySettings?: Record<string, any>;
87
+ priceSettings?: Record<string, any>;
88
+ variant?: OfferVariant | null;
89
+ }
90
+ /**
91
+ * Line item within an offer
92
+ */
93
+ export interface OfferLineItem {
94
+ id: string;
95
+ offerId?: string;
96
+ quantity: number;
97
+ priceId?: string;
98
+ variantId?: string;
99
+ productId?: string;
100
+ price: OfferLineItemPrice | null;
101
+ }
102
+ /**
103
+ * Summary item within an offer summary
104
+ */
105
+ export interface OfferSummaryItem {
106
+ id: string;
107
+ productId: string;
108
+ variantId: string;
109
+ priceId?: string;
8
110
  product: {
9
111
  name: string;
10
112
  description: string;
11
113
  };
12
114
  variant: {
13
115
  name: string;
14
- imageUrl: string;
116
+ description?: string;
117
+ imageUrl: string | null;
118
+ grams?: number | null;
15
119
  };
16
- quantity: number;
120
+ sku?: string;
17
121
  unitAmount: number;
122
+ quantity: number;
18
123
  amount: number;
19
124
  adjustedAmount: number;
20
125
  currency: string;
126
+ adjustments?: any[];
127
+ recurring?: boolean;
128
+ rebillMode?: string | null;
129
+ interval?: string;
130
+ intervalCount?: number;
131
+ totalBillingCycles?: number;
132
+ unitAmountAfterFirstCycle?: number;
133
+ isExternalSellingPlan?: boolean;
134
+ isInternalSellingPlan?: boolean;
135
+ deliverySettings?: Record<string, any>;
136
+ priceSettings?: Record<string, any>;
137
+ orderLineItemProduct?: {
138
+ name: string;
139
+ };
140
+ orderLineItemVariant?: {
141
+ name: string;
142
+ imageUrl: string | null;
143
+ };
144
+ metadata?: Record<string, any>;
21
145
  }
146
+ /**
147
+ * Offer summary containing items and totals
148
+ */
22
149
  export interface OfferSummary {
23
- items: OfferItem[];
150
+ items: OfferSummaryItem[];
151
+ totalWeight?: number;
152
+ subtotalAmount?: number;
153
+ lineItemsPromotionAmount?: number;
154
+ subtotalAdjustedAmount?: number;
155
+ totalPromotionAmount: number;
156
+ lineItemsTaxAmount?: number;
157
+ totalTaxAmount?: number;
158
+ shippingCost?: number;
159
+ shippingCostIsFree?: boolean;
24
160
  totalAmount: number;
25
161
  totalAdjustedAmount: number;
26
- totalPromotionAmount: number;
27
- currency: string;
28
162
  adjustments: {
29
163
  type: string;
30
164
  description: string;
31
165
  amount: number;
32
166
  }[];
167
+ currency: string;
168
+ _consolidated?: boolean;
169
+ }
170
+ /**
171
+ * Offer promotion association
172
+ */
173
+ export interface OfferPromotion {
174
+ id: string;
175
+ promotionId: string;
176
+ promotion?: {
177
+ id: string;
178
+ name: string;
179
+ code?: string | null;
180
+ automatic?: boolean;
181
+ };
33
182
  }
34
- import type { CurrencyOptions, VariantOption, OrderSummaryItem, OrderSummary, CheckoutSessionState } from './postPurchases';
35
- export type { CurrencyOptions, VariantOption, OrderSummaryItem, OrderSummary, CheckoutSessionState };
183
+ /**
184
+ * Main Offer type
185
+ */
36
186
  export interface Offer {
37
187
  id: string;
188
+ createdAt?: string;
189
+ updatedAt?: string;
190
+ storeId?: string;
191
+ type?: string;
38
192
  titleTrans: Record<string, string>;
193
+ expiryDate?: string | null;
39
194
  summaries: OfferSummary[];
195
+ offerLineItems?: OfferLineItem[];
196
+ promotions?: OfferPromotion[];
40
197
  }
41
198
  export declare class OffersResource {
42
199
  private apiClient;
43
200
  constructor(apiClient: ApiClient);
201
+ /**
202
+ * Get a single offer by ID
203
+ */
204
+ getOfferById(offerId: string): Promise<Offer>;
44
205
  /**
45
206
  * Get offers for a store
46
207
  */
@@ -70,6 +231,19 @@ export declare class OffersResource {
70
231
  */
71
232
  payOffer(offerId: string, orderId?: string): Promise<any>;
72
233
  /**
234
+ * Transform offer to checkout session with dynamic variant selection
235
+ * Uses lineItems from the offer to create a new checkout session
236
+ */
237
+ transformToCheckoutSession(offerId: string, lineItems: {
238
+ variantId: string;
239
+ quantity: number;
240
+ }[], mainOrderId: string, returnUrl?: string): Promise<{
241
+ checkoutUrl: string;
242
+ checkoutSessionId?: string;
243
+ customerId?: string;
244
+ }>;
245
+ /**
246
+ * @deprecated Use transformToCheckoutSession instead
73
247
  * Transform offer to checkout session with dynamic variant selection
74
248
  */
75
249
  transformToCheckout(offerId: string, returnUrl?: string): Promise<{