@funnelsgrove/runtime 0.1.45 → 0.1.46

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/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from './runtime/subscription-handoff.js';
21
21
  export * from './runtime/url-user-attributes.js';
22
22
  export * from './runtime/use-url-user-attributes-sync.js';
23
23
  export * from './services/api.service.js';
24
+ export * from './services/funnel-sdk.service.js';
24
25
  export * from './services/funnel-state.service.js';
25
26
  export * from './services/logger.js';
26
27
  export * from './services/preview-frame.service.js';
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ export * from './runtime/url-user-attributes.js';
25
25
  export * from './runtime/use-url-user-attributes-sync.js';
26
26
  // Browser-safe services.
27
27
  export * from './services/api.service.js';
28
+ export * from './services/funnel-sdk.service.js';
28
29
  export * from './services/funnel-state.service.js';
29
30
  export * from './services/logger.js';
30
31
  export * from './services/preview-frame.service.js';
@@ -2,6 +2,7 @@ import type { RuntimeMode } from '../services/runtime-mode.service.js';
2
2
  export type GeneratedOfferSetItemKind = 'plan' | 'upsell' | 'downsell';
3
3
  export type GeneratedOfferSetPlanModeConfig = {
4
4
  readonly providerPlanId?: string | null;
5
+ readonly paymentMode?: string | null;
5
6
  readonly priceLabel?: string | null;
6
7
  readonly perDayAmount?: string | null;
7
8
  readonly perDayLabel?: string | null;
@@ -63,6 +64,7 @@ export type OfferSetRuntimeDiscount = {
63
64
  export type OfferSetRuntimePlanOverrides = {
64
65
  projectPlanId?: string;
65
66
  providerPlanId?: string;
67
+ paymentMode?: 'one_time';
66
68
  analyticsPurchaseValue?: number;
67
69
  title?: string;
68
70
  description?: string;
@@ -34,6 +34,15 @@ const readPositiveInteger = (value) => {
34
34
  const numeric = readFiniteNumber(value);
35
35
  return numeric !== null && numeric > 0 ? Math.round(numeric) : null;
36
36
  };
37
+ const readOneTimePaymentMode = (value) => {
38
+ if (typeof value !== 'string') {
39
+ return null;
40
+ }
41
+ const normalized = value.trim().toLowerCase().replace(/[\s-]+/g, '_');
42
+ return normalized === 'one_time' || normalized === 'payment' || normalized === 'payment_intent'
43
+ ? 'one_time'
44
+ : null;
45
+ };
37
46
  const getOfferSetItemKind = (item) => {
38
47
  var _a;
39
48
  const metadata = isRecord(item.metadata) ? item.metadata : {};
@@ -117,7 +126,7 @@ const readGeneratedPlanNumberValue = (item, mode, itemKeys, metadataKeys = itemK
117
126
  return ((_b = (_a = readFiniteNumberField(modeConfig, metadataKeys)) !== null && _a !== void 0 ? _a : itemValue) !== null && _b !== void 0 ? _b : readFiniteNumberField(metadata, metadataKeys));
118
127
  };
119
128
  const buildGeneratedPlanOverrides = (item, mode, isDefault) => {
120
- var _a, _b, _c, _d, _e, _f;
129
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
121
130
  const metadata = readOfferItemMetadata(item);
122
131
  const providerPlanId = readProviderPlanId(item, mode);
123
132
  const projectPlanId = (_a = readString(item.projectBillingPlanId)) !== null && _a !== void 0 ? _a : readStringField(metadata, ['projectBillingPlanId', 'projectPlanId', 'project_billing_plan_id']);
@@ -130,10 +139,12 @@ const buildGeneratedPlanOverrides = (item, mode, isDefault) => {
130
139
  const billingInterval = readGeneratedPlanStringValue(item, mode, ['billingInterval']);
131
140
  const billingIntervalCount = readGeneratedPlanNumberValue(item, mode, ['billingIntervalCount']);
132
141
  const amountCents = readGeneratedPlanNumberValue(item, mode, ['amountCents']);
133
- const analyticsPurchaseValue = (_d = readFiniteNumber(item.analyticsPurchaseValue)) !== null && _d !== void 0 ? _d : readFiniteNumberField(metadata, ['analyticsPurchaseValue']);
134
- const featuredTag = (_e = readString(item.featuredTag)) !== null && _e !== void 0 ? _e : readStringField(metadata, ['featuredTag']);
135
- const oldPriceLabel = (_f = readString(item.oldPriceLabel)) !== null && _f !== void 0 ? _f : readStringField(metadata, ['oldPriceLabel']);
136
- return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (projectPlanId ? { projectPlanId } : {})), (providerPlanId ? { providerPlanId } : {})), (analyticsPurchaseValue !== null ? { analyticsPurchaseValue } : {})), (title ? { title } : {})), (description ? { description } : {})), (priceLabel ? { priceLabel } : {})), (perDayAmount ? { perDayAmount } : {})), (perDayLabel ? { perDayLabel } : {})), (amountCents !== null ? { amountCents: Math.max(0, Math.round(amountCents)) } : {})), (checkoutSummaryLabel ? { checkoutSummaryLabel } : {})), (billingInterval ? { billingInterval } : {})), (billingIntervalCount !== null ? { billingIntervalCount: Math.max(1, Math.round(billingIntervalCount)) } : {})), (featuredTag ? { featuredTag } : {})), (oldPriceLabel ? { oldPriceLabel } : {})), (isDefault ? { isDefault: true } : {}));
142
+ const modeConfig = readGeneratedModeConfig(item, mode);
143
+ const paymentMode = (_k = (_j = (_h = (_g = (_f = (_e = (_d = readOneTimePaymentMode(modeConfig.paymentMode)) !== null && _d !== void 0 ? _d : readOneTimePaymentMode(modeConfig.payment_mode)) !== null && _e !== void 0 ? _e : readOneTimePaymentMode(modeConfig.checkoutMode)) !== null && _f !== void 0 ? _f : readOneTimePaymentMode(modeConfig.checkout_mode)) !== null && _g !== void 0 ? _g : readOneTimePaymentMode(metadata.paymentMode)) !== null && _h !== void 0 ? _h : readOneTimePaymentMode(metadata.payment_mode)) !== null && _j !== void 0 ? _j : readOneTimePaymentMode(metadata.checkoutMode)) !== null && _k !== void 0 ? _k : readOneTimePaymentMode(metadata.checkout_mode);
144
+ const analyticsPurchaseValue = (_l = readFiniteNumber(item.analyticsPurchaseValue)) !== null && _l !== void 0 ? _l : readFiniteNumberField(metadata, ['analyticsPurchaseValue']);
145
+ const featuredTag = (_m = readString(item.featuredTag)) !== null && _m !== void 0 ? _m : readStringField(metadata, ['featuredTag']);
146
+ const oldPriceLabel = (_o = readString(item.oldPriceLabel)) !== null && _o !== void 0 ? _o : readStringField(metadata, ['oldPriceLabel']);
147
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (projectPlanId ? { projectPlanId } : {})), (providerPlanId ? { providerPlanId } : {})), (paymentMode ? { paymentMode } : {})), (analyticsPurchaseValue !== null ? { analyticsPurchaseValue } : {})), (title ? { title } : {})), (description ? { description } : {})), (priceLabel ? { priceLabel } : {})), (perDayAmount ? { perDayAmount } : {})), (perDayLabel ? { perDayLabel } : {})), (amountCents !== null ? { amountCents: Math.max(0, Math.round(amountCents)) } : {})), (checkoutSummaryLabel ? { checkoutSummaryLabel } : {})), (billingInterval ? { billingInterval } : {})), (billingIntervalCount !== null ? { billingIntervalCount: Math.max(1, Math.round(billingIntervalCount)) } : {})), (featuredTag ? { featuredTag } : {})), (oldPriceLabel ? { oldPriceLabel } : {})), (isDefault ? { isDefault: true } : {}));
137
148
  };
138
149
  export const applyGeneratedOfferSetToPlanCatalog = (baseCatalog, mode, offerSet) => {
139
150
  var _a, _b, _c;
@@ -1,4 +1,5 @@
1
- import { buildMainApiUrl, buildSdkHeaders, FUNNEL_ID, FUNNEL_VERSION_ID, getFunnelSdkPublishableKey, } from './runtime-api.config.js';
1
+ import { buildSdkHeaders, FUNNEL_ID, getFunnelSdkPublishableKey, } from './runtime-api.config.js';
2
+ import { funnelSdkService } from './funnel-sdk.service.js';
2
3
  import { mergeFunnelUserAttribution, } from '../runtime/funnel-attribution.js';
3
4
  import { applyUrlUserAttributesToUser, hasUrlUserProfileAttributes, resolveUrlUserAttributes, } from '../runtime/url-user-attributes.js';
4
5
  const DEFAULT_USER_ID_STORAGE_KEY = 'funnel:user-id';
@@ -144,28 +145,6 @@ const toAppUser = (input) => {
144
145
  document: Object.keys(apiDocument).length > 0 ? apiDocument : ((_b = input.fallbackDocument) !== null && _b !== void 0 ? _b : {}),
145
146
  };
146
147
  };
147
- const parseErrorMessage = async (response, fallbackMessage) => {
148
- try {
149
- const payload = (await response.json());
150
- const apiError = asString(payload.error);
151
- if (apiError) {
152
- return apiError;
153
- }
154
- }
155
- catch (_a) {
156
- // ignore JSON parsing errors
157
- }
158
- try {
159
- const errorText = await response.text();
160
- if (errorText.trim()) {
161
- return errorText;
162
- }
163
- }
164
- catch (_b) {
165
- // ignore text parsing errors
166
- }
167
- return fallbackMessage;
168
- };
169
148
  class ApiService {
170
149
  readFileAsDataUrl(file) {
171
150
  return new Promise((resolve, reject) => {
@@ -209,18 +188,10 @@ class ApiService {
209
188
  if (!publishableKey || !FUNNEL_ID) {
210
189
  return null;
211
190
  }
212
- const url = new URL(buildMainApiUrl('/sdk/public/users/me'));
213
- url.searchParams.set('funnelId', FUNNEL_ID);
214
- url.searchParams.set('user_id', userId);
215
191
  try {
216
- const response = await fetch(url.toString(), {
217
- method: 'GET',
218
- headers: buildSdkHeaders({}),
192
+ return await funnelSdkService.getCurrentUser({
193
+ userId,
219
194
  });
220
- if (!response.ok) {
221
- return null;
222
- }
223
- return (await response.json());
224
195
  }
225
196
  catch (_a) {
226
197
  return null;
@@ -260,30 +231,16 @@ class ApiService {
260
231
  fallbackDocument: withMergedAttributionDocument({}, input === null || input === void 0 ? void 0 : input.attribution),
261
232
  });
262
233
  }
263
- const response = await fetch(buildMainApiUrl('/sdk/public/users/bootstrap'), {
264
- method: 'POST',
265
- headers: buildSdkHeaders({
266
- 'Content-Type': 'application/json',
267
- }),
268
- body: JSON.stringify({
269
- publishableKey,
270
- funnelId: FUNNEL_ID || undefined,
271
- funnelVersionId: FUNNEL_VERSION_ID || undefined,
272
- user_id: userId,
273
- email: inputEmail,
274
- fullName: inputName,
275
- metadata: {
276
- source: 'funnel-session',
277
- environment: resolveRuntimeAnalyticsEnvironment(),
278
- },
279
- attribution: input === null || input === void 0 ? void 0 : input.attribution,
280
- }),
234
+ const payload = await funnelSdkService.bootstrapUser({
235
+ userId,
236
+ email: inputEmail,
237
+ fullName: inputName,
238
+ metadata: {
239
+ source: 'funnel-session',
240
+ environment: resolveRuntimeAnalyticsEnvironment(),
241
+ },
242
+ attribution: input === null || input === void 0 ? void 0 : input.attribution,
281
243
  });
282
- if (!response.ok) {
283
- const errorMessage = await parseErrorMessage(response, 'Failed to bootstrap user session');
284
- throw new Error(errorMessage);
285
- }
286
- const payload = (await response.json());
287
244
  const persistedUserId = persistUserId(asString((_a = payload.user) === null || _a === void 0 ? void 0 : _a.user_id) ||
288
245
  asString(payload.user_id) ||
289
246
  userId, FUNNEL_ID) || userId;
@@ -307,33 +264,19 @@ class ApiService {
307
264
  fallbackDocument: withMergedAttributionDocument(user.document, options === null || options === void 0 ? void 0 : options.attribution),
308
265
  });
309
266
  }
310
- const response = await fetch(buildMainApiUrl('/public/funnel-users/upsert'), {
311
- method: 'POST',
312
- headers: buildSdkHeaders({
313
- 'Content-Type': 'application/json',
314
- }),
315
- body: JSON.stringify({
316
- publishableKey,
317
- funnelId: FUNNEL_ID || undefined,
318
- funnelVersionId: FUNNEL_VERSION_ID || undefined,
319
- user_id: persistedUserId,
320
- email: user.email || undefined,
321
- fullName: user.name || undefined,
322
- progress: {
323
- attributes: user.attributes || {},
324
- },
325
- metadata: {
326
- source: 'funnel',
327
- environment: resolveRuntimeAnalyticsEnvironment(),
328
- },
329
- attribution: options === null || options === void 0 ? void 0 : options.attribution,
330
- }),
267
+ const payload = await funnelSdkService.updateFunnelUser({
268
+ userId: persistedUserId,
269
+ email: user.email || undefined,
270
+ fullName: user.name || undefined,
271
+ progress: {
272
+ attributes: user.attributes || {},
273
+ },
274
+ metadata: {
275
+ source: 'funnel',
276
+ environment: resolveRuntimeAnalyticsEnvironment(),
277
+ },
278
+ attribution: options === null || options === void 0 ? void 0 : options.attribution,
331
279
  });
332
- if (!response.ok) {
333
- const errorMessage = await parseErrorMessage(response, 'Failed to update user');
334
- throw new Error(errorMessage);
335
- }
336
- const payload = (await response.json());
337
280
  return toAppUser({
338
281
  apiUser: payload.user,
339
282
  userId: persistedUserId,
@@ -407,26 +350,13 @@ class ApiService {
407
350
  uploadedAt: new Date().toISOString(),
408
351
  };
409
352
  }
410
- const response = await fetch(buildMainApiUrl('/sdk/public/uploads/photos'), {
411
- method: 'POST',
412
- headers: buildSdkHeaders({
413
- 'Content-Type': 'application/json',
414
- }),
415
- body: JSON.stringify({
416
- publishableKey,
417
- funnelId: FUNNEL_ID || undefined,
418
- user_id: userId,
419
- fileName: input.file.name || 'capture.jpg',
420
- mimeType: input.file.type || undefined,
421
- imageDataUrl,
422
- source: input.source || 'upload',
423
- }),
353
+ const payload = await funnelSdkService.uploadPhoto({
354
+ userId,
355
+ fileName: input.file.name || 'capture.jpg',
356
+ mimeType: input.file.type || undefined,
357
+ imageDataUrl,
358
+ source: input.source || 'upload',
424
359
  });
425
- if (!response.ok) {
426
- const errorMessage = await parseErrorMessage(response, 'Failed to upload photo');
427
- throw new Error(errorMessage);
428
- }
429
- const payload = (await response.json());
430
360
  const publicUrl = asString(payload.publicUrl);
431
361
  const key = asString(payload.key);
432
362
  const contentType = asString(payload.contentType) || input.file.type || 'image/jpeg';
@@ -446,48 +376,23 @@ class ApiService {
446
376
  async getManageSubscriptions() {
447
377
  const userId = this.getSubscriptionManagementUserId();
448
378
  const stripeCustomerId = this.getSubscriptionManagementStripeCustomerId();
449
- const query = new URLSearchParams();
450
- if (userId) {
451
- query.set('user_id', userId);
452
- }
453
- if (stripeCustomerId) {
454
- query.set('stripe_customer_id', stripeCustomerId);
455
- }
456
- if (FUNNEL_ID) {
457
- query.set('funnelId', FUNNEL_ID);
458
- }
459
- const response = await fetch(buildMainApiUrl(`/sdk/public/subscriptions?${query.toString()}`), {
460
- method: 'GET',
461
- cache: 'no-store',
462
- headers: buildSdkHeaders(),
379
+ return funnelSdkService.listSubscriptions({
380
+ userId,
381
+ stripeCustomerId,
382
+ funnelId: FUNNEL_ID || undefined,
463
383
  });
464
- if (!response.ok) {
465
- const errorMessage = await parseErrorMessage(response, 'Failed to load subscriptions');
466
- throw new Error(errorMessage);
467
- }
468
- return (await response.json());
469
384
  }
470
385
  async updateSubscription(input) {
471
- var _a;
472
386
  const userId = this.getSubscriptionManagementUserId();
473
387
  const stripeCustomerId = this.getSubscriptionManagementStripeCustomerId();
474
- const response = await fetch(buildMainApiUrl(`/sdk/public/subscriptions/${encodeURIComponent(input.subscriptionId)}/${input.action}`), {
475
- method: 'POST',
476
- headers: buildSdkHeaders({
477
- 'Content-Type': 'application/json',
478
- }),
479
- body: JSON.stringify({
480
- user_id: userId || undefined,
481
- stripe_customer_id: stripeCustomerId || undefined,
482
- funnelId: FUNNEL_ID || undefined,
483
- coupon_id: ((_a = input.couponId) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
484
- }),
388
+ return funnelSdkService.updateSubscription({
389
+ subscriptionId: input.subscriptionId,
390
+ action: input.action,
391
+ couponId: input.couponId,
392
+ userId,
393
+ stripeCustomerId,
394
+ funnelId: FUNNEL_ID || undefined,
485
395
  });
486
- if (!response.ok) {
487
- const errorMessage = await parseErrorMessage(response, 'Failed to update subscription');
488
- throw new Error(errorMessage);
489
- }
490
- return (await response.json());
491
396
  }
492
397
  }
493
398
  export const apiService = new ApiService();
@@ -0,0 +1,207 @@
1
+ export type FunnelSdkRuntimeMode = 'test' | 'live';
2
+ export type FunnelSdkRuntimeConfigOverrides = {
3
+ apiBaseUrl?: string | null;
4
+ funnelId?: string | null;
5
+ funnelVersionId?: string | null;
6
+ funnelSdkPublishableKey?: string | null;
7
+ };
8
+ export type FunnelSdkPaymentPlan = Record<string, unknown>;
9
+ export type FunnelSdkJsonObject = Record<string, unknown>;
10
+ export type FunnelSdkListPaymentPlansResponse = {
11
+ funnelId?: string;
12
+ environment?: FunnelSdkRuntimeMode;
13
+ plans: FunnelSdkPaymentPlan[];
14
+ };
15
+ export type FunnelSdkBootstrapUserInput = {
16
+ userId?: string | null;
17
+ email?: string | null;
18
+ fullName?: string | null;
19
+ metadata?: FunnelSdkJsonObject | null;
20
+ attribution?: FunnelSdkJsonObject | null;
21
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
22
+ };
23
+ export type FunnelSdkGetCurrentUserInput = {
24
+ userId: string;
25
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
26
+ };
27
+ export type FunnelSdkGetUserSubscriptionStatusInput = {
28
+ userId?: string | null;
29
+ email?: string | null;
30
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
31
+ };
32
+ export type FunnelSdkClaimUserSubscriptionInput = {
33
+ userId: string;
34
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
35
+ };
36
+ export type FunnelSdkUpdateFunnelUserInput = {
37
+ userId: string;
38
+ email?: string | null;
39
+ fullName?: string | null;
40
+ document?: FunnelSdkJsonObject | null;
41
+ progress?: FunnelSdkJsonObject | null;
42
+ metadata?: FunnelSdkJsonObject | null;
43
+ attribution?: FunnelSdkJsonObject | null;
44
+ subscription?: FunnelSdkJsonObject | null;
45
+ subscriptionStatus?: string | null;
46
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
47
+ };
48
+ export type FunnelSdkUploadPhotoInput = {
49
+ fileName: string;
50
+ imageDataUrl: string;
51
+ mimeType?: string | null;
52
+ source?: 'camera' | 'upload' | string | null;
53
+ userId?: string | null;
54
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
55
+ };
56
+ export type FunnelSdkCreateHostedCheckoutSessionInput = {
57
+ planId: string;
58
+ amountCents: number;
59
+ successUrl: string;
60
+ cancelUrl: string;
61
+ analyticsMetadata?: Record<string, unknown> | null;
62
+ billingInterval?: string | null;
63
+ billingIntervalCount?: number | null;
64
+ couponId?: string | null;
65
+ customerEmail?: string | null;
66
+ description?: string | null;
67
+ environment?: FunnelSdkRuntimeMode;
68
+ providerPlanId?: string | null;
69
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
70
+ title?: string | null;
71
+ userId?: string | null;
72
+ };
73
+ export type FunnelSdkCreateHostedCheckoutSessionResponse = {
74
+ sessionId?: string;
75
+ url: string;
76
+ environment?: FunnelSdkRuntimeMode;
77
+ };
78
+ export type FunnelSdkCreateSubscriptionCheckoutInput = {
79
+ planId: string;
80
+ amountCents: number;
81
+ returnUrl: string;
82
+ analyticsMetadata?: Record<string, unknown> | null;
83
+ billingInterval?: string | null;
84
+ billingIntervalCount?: number | null;
85
+ couponId?: string | null;
86
+ customerEmail?: string | null;
87
+ description?: string | null;
88
+ environment?: FunnelSdkRuntimeMode;
89
+ providerPlanId?: string | null;
90
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
91
+ title?: string | null;
92
+ userId?: string | null;
93
+ };
94
+ export type FunnelSdkCreateSubscriptionCheckoutResponse = {
95
+ clientSecret: string;
96
+ checkoutSessionId?: string;
97
+ subscriptionId?: string;
98
+ stripePublishableKey?: string;
99
+ environment?: FunnelSdkRuntimeMode;
100
+ };
101
+ export type FunnelSdkUpdateSubscriptionCheckoutPlanInput = {
102
+ checkoutSessionId: string;
103
+ planId: string;
104
+ amountCents: number;
105
+ analyticsMetadata?: Record<string, unknown> | null;
106
+ billingInterval?: string | null;
107
+ billingIntervalCount?: number | null;
108
+ couponId?: string | null;
109
+ customerEmail?: string | null;
110
+ description?: string | null;
111
+ environment?: FunnelSdkRuntimeMode;
112
+ providerPlanId?: string | null;
113
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
114
+ title?: string | null;
115
+ userId?: string | null;
116
+ };
117
+ export type FunnelSdkUpdateSubscriptionCheckoutPlanResponse = {
118
+ checkoutSessionId: string;
119
+ environment?: FunnelSdkRuntimeMode;
120
+ };
121
+ export type FunnelSdkVerifySubscriptionCheckoutInput = {
122
+ checkoutSessionId: string;
123
+ environment?: FunnelSdkRuntimeMode;
124
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
125
+ };
126
+ export type FunnelSdkVerifySubscriptionCheckoutResponse = {
127
+ checkoutSessionId: string;
128
+ paid: boolean;
129
+ paymentStatus?: string | null;
130
+ status?: string | null;
131
+ environment?: FunnelSdkRuntimeMode;
132
+ analyticsMetadata?: Record<string, unknown>;
133
+ };
134
+ export type FunnelSdkCreateOneTimePaymentIntentInput = {
135
+ planId: string;
136
+ amountCents: number;
137
+ analyticsMetadata?: Record<string, unknown> | null;
138
+ couponId?: string | null;
139
+ environment?: FunnelSdkRuntimeMode;
140
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
141
+ userId?: string | null;
142
+ };
143
+ export type FunnelSdkCreateOneTimePaymentIntentResponse = {
144
+ clientSecret: string;
145
+ stripePublishableKey?: string;
146
+ environment?: FunnelSdkRuntimeMode;
147
+ };
148
+ export type FunnelSdkChargeOneClickPaymentInput = {
149
+ planId: string;
150
+ amountCents: number;
151
+ analyticsMetadata?: Record<string, unknown> | null;
152
+ checkoutSessionId?: string | null;
153
+ currency?: string | null;
154
+ environment?: FunnelSdkRuntimeMode;
155
+ idempotencyKey?: string | null;
156
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
157
+ stripeCustomerId?: string | null;
158
+ userId?: string | null;
159
+ };
160
+ export type FunnelSdkChargeOneClickPaymentResponse = {
161
+ paymentIntentId: string;
162
+ status: string;
163
+ amountCents?: number;
164
+ clientSecret?: string;
165
+ currency?: string;
166
+ environment?: FunnelSdkRuntimeMode;
167
+ stripePublishableKey?: string;
168
+ };
169
+ export type FunnelSdkUpdateSubscriptionInput = {
170
+ subscriptionId: string;
171
+ action: 'cancel' | 'renew' | 'apply-discount';
172
+ couponId?: string | null;
173
+ funnelId?: string | null;
174
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
175
+ stripeCustomerId?: string | null;
176
+ userId?: string | null;
177
+ };
178
+ export type FunnelSdkListSubscriptionsInput = {
179
+ funnelId?: string | null;
180
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
181
+ stripeCustomerId?: string | null;
182
+ userId?: string | null;
183
+ };
184
+ declare class FunnelSdkService {
185
+ private getJson;
186
+ private postJson;
187
+ bootstrapUser<ResponsePayload = FunnelSdkJsonObject>(input?: FunnelSdkBootstrapUserInput): Promise<ResponsePayload>;
188
+ getCurrentUser<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkGetCurrentUserInput): Promise<ResponsePayload>;
189
+ getUserSubscriptionStatus<ResponsePayload = FunnelSdkJsonObject>(input?: FunnelSdkGetUserSubscriptionStatusInput): Promise<ResponsePayload>;
190
+ claimUserSubscription<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkClaimUserSubscriptionInput): Promise<ResponsePayload>;
191
+ updateFunnelUser<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkUpdateFunnelUserInput): Promise<ResponsePayload>;
192
+ uploadPhoto<ResponsePayload = FunnelSdkJsonObject>(input: FunnelSdkUploadPhotoInput): Promise<ResponsePayload>;
193
+ listPaymentPlans(input?: {
194
+ environment?: FunnelSdkRuntimeMode;
195
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
196
+ }): Promise<FunnelSdkListPaymentPlansResponse>;
197
+ createHostedCheckoutSession(input: FunnelSdkCreateHostedCheckoutSessionInput): Promise<FunnelSdkCreateHostedCheckoutSessionResponse>;
198
+ createSubscriptionCheckout(input: FunnelSdkCreateSubscriptionCheckoutInput): Promise<FunnelSdkCreateSubscriptionCheckoutResponse>;
199
+ updateSubscriptionCheckoutPlan(input: FunnelSdkUpdateSubscriptionCheckoutPlanInput): Promise<FunnelSdkUpdateSubscriptionCheckoutPlanResponse>;
200
+ verifySubscriptionCheckout(input: FunnelSdkVerifySubscriptionCheckoutInput): Promise<FunnelSdkVerifySubscriptionCheckoutResponse>;
201
+ createOneTimePaymentIntent(input: FunnelSdkCreateOneTimePaymentIntentInput): Promise<FunnelSdkCreateOneTimePaymentIntentResponse>;
202
+ chargeOneClickPayment(input: FunnelSdkChargeOneClickPaymentInput): Promise<FunnelSdkChargeOneClickPaymentResponse>;
203
+ updateSubscription<ResponsePayload>(input: FunnelSdkUpdateSubscriptionInput): Promise<ResponsePayload>;
204
+ listSubscriptions<ResponsePayload = FunnelSdkJsonObject>(input?: FunnelSdkListSubscriptionsInput): Promise<ResponsePayload>;
205
+ }
206
+ export declare const funnelSdkService: FunnelSdkService;
207
+ export {};
@@ -0,0 +1,319 @@
1
+ import { buildMainApiUrl, FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, FUNNEL_VERSION_ID, } from './runtime-api.config.js';
2
+ import { resolvePreviewPaymentPublishableKey } from './public-env.js';
3
+ const trimTrailingSlash = (value) => value.replace(/\/+$/, '');
4
+ const toNormalizedPath = (path) => (path.startsWith('/') ? path : `/${path}`);
5
+ const asTrimmedStringOrNull = (value) => {
6
+ if (typeof value !== 'string') {
7
+ return null;
8
+ }
9
+ const trimmed = value.trim();
10
+ return trimmed || null;
11
+ };
12
+ const removeEmptyFields = (input) => {
13
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== null && value !== ''));
14
+ };
15
+ const resolveRuntimeConfig = (runtimeConfig) => {
16
+ var _a, _b, _c;
17
+ return ({
18
+ apiBaseUrl: asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.apiBaseUrl),
19
+ funnelId: (_a = asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelId)) !== null && _a !== void 0 ? _a : asTrimmedStringOrNull(FUNNEL_ID),
20
+ funnelVersionId: (_b = asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelVersionId)) !== null && _b !== void 0 ? _b : asTrimmedStringOrNull(FUNNEL_VERSION_ID),
21
+ funnelSdkPublishableKey: resolvePreviewPaymentPublishableKey((_c = asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey)) !== null && _c !== void 0 ? _c : asTrimmedStringOrNull(FUNNEL_SDK_PUBLISHABLE_KEY)) || null,
22
+ });
23
+ };
24
+ const buildApiUrl = (path, runtimeConfig) => {
25
+ const { apiBaseUrl } = resolveRuntimeConfig(runtimeConfig);
26
+ if (!apiBaseUrl) {
27
+ return buildMainApiUrl(path);
28
+ }
29
+ return `${trimTrailingSlash(apiBaseUrl)}${toNormalizedPath(path)}`;
30
+ };
31
+ const buildHeaders = (runtimeConfig, headers) => {
32
+ const { funnelSdkPublishableKey } = resolveRuntimeConfig(runtimeConfig);
33
+ if (!funnelSdkPublishableKey) {
34
+ return Object.assign({}, (headers !== null && headers !== void 0 ? headers : {}));
35
+ }
36
+ return Object.assign({ 'x-sdk-publishable-key': funnelSdkPublishableKey }, (headers !== null && headers !== void 0 ? headers : {}));
37
+ };
38
+ const withRuntimeFields = (runtimeConfig, body, options) => {
39
+ const { funnelId, funnelVersionId, funnelSdkPublishableKey } = resolveRuntimeConfig(runtimeConfig);
40
+ return removeEmptyFields(Object.assign({ publishableKey: (options === null || options === void 0 ? void 0 : options.includePublishableKey) === false
41
+ ? undefined
42
+ : funnelSdkPublishableKey || undefined, funnelId: funnelId || undefined, funnelVersionId: funnelVersionId || undefined }, (body !== null && body !== void 0 ? body : {})));
43
+ };
44
+ const parseErrorMessage = async (response, fallbackMessage) => {
45
+ try {
46
+ const payload = (await response.json());
47
+ const apiError = asTrimmedStringOrNull(payload.error);
48
+ if (apiError) {
49
+ return apiError;
50
+ }
51
+ }
52
+ catch (_a) {
53
+ // ignore JSON parsing errors
54
+ }
55
+ try {
56
+ const text = await response.text();
57
+ if (text.trim()) {
58
+ return text;
59
+ }
60
+ }
61
+ catch (_b) {
62
+ // ignore text parsing errors
63
+ }
64
+ return fallbackMessage;
65
+ };
66
+ class FunnelSdkService {
67
+ async getJson(path, input) {
68
+ var _a;
69
+ const url = new URL(buildApiUrl(path, input.runtimeConfig));
70
+ for (const [key, value] of Object.entries((_a = input.searchParams) !== null && _a !== void 0 ? _a : {})) {
71
+ const normalizedValue = asTrimmedStringOrNull(value);
72
+ if (normalizedValue) {
73
+ url.searchParams.set(key, normalizedValue);
74
+ }
75
+ }
76
+ const response = await fetch(url.toString(), {
77
+ method: 'GET',
78
+ headers: buildHeaders(input.runtimeConfig),
79
+ });
80
+ if (!response.ok) {
81
+ throw new Error(await parseErrorMessage(response, input.errorMessage));
82
+ }
83
+ return (await response.json());
84
+ }
85
+ async postJson(path, input) {
86
+ var _a;
87
+ const response = await fetch(buildApiUrl(path, input.runtimeConfig), {
88
+ method: 'POST',
89
+ headers: buildHeaders(input.runtimeConfig, {
90
+ 'Content-Type': 'application/json',
91
+ }),
92
+ body: JSON.stringify(input.includeRuntimeFields === false
93
+ ? removeEmptyFields((_a = input.body) !== null && _a !== void 0 ? _a : {})
94
+ : withRuntimeFields(input.runtimeConfig, input.body, {
95
+ includePublishableKey: input.includePublishableKey,
96
+ })),
97
+ });
98
+ if (!response.ok) {
99
+ throw new Error(await parseErrorMessage(response, input.errorMessage));
100
+ }
101
+ return (await response.json());
102
+ }
103
+ async bootstrapUser(input = {}) {
104
+ return this.postJson('/sdk/public/users/bootstrap', {
105
+ runtimeConfig: input.runtimeConfig,
106
+ body: {
107
+ user_id: input.userId,
108
+ email: input.email,
109
+ fullName: input.fullName,
110
+ metadata: input.metadata,
111
+ attribution: input.attribution,
112
+ },
113
+ errorMessage: 'Failed to bootstrap user session',
114
+ });
115
+ }
116
+ async getCurrentUser(input) {
117
+ const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
118
+ return this.getJson('/sdk/public/users/me', {
119
+ runtimeConfig: input.runtimeConfig,
120
+ searchParams: {
121
+ funnelId,
122
+ user_id: input.userId,
123
+ },
124
+ errorMessage: 'Failed to load user',
125
+ });
126
+ }
127
+ async getUserSubscriptionStatus(input = {}) {
128
+ const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
129
+ return this.getJson('/sdk/public/users/subscription', {
130
+ runtimeConfig: input.runtimeConfig,
131
+ searchParams: {
132
+ funnelId,
133
+ user_id: input.userId,
134
+ email: input.email,
135
+ },
136
+ errorMessage: 'Failed to load user subscription status',
137
+ });
138
+ }
139
+ async claimUserSubscription(input) {
140
+ return this.postJson(`/sdk/public/users/${encodeURIComponent(input.userId)}/claim_subscription`, {
141
+ runtimeConfig: input.runtimeConfig,
142
+ errorMessage: 'Failed to claim user subscription',
143
+ });
144
+ }
145
+ async updateFunnelUser(input) {
146
+ return this.postJson('/public/funnel-users/upsert', {
147
+ runtimeConfig: input.runtimeConfig,
148
+ body: {
149
+ user_id: input.userId,
150
+ email: input.email,
151
+ fullName: input.fullName,
152
+ document: input.document,
153
+ progress: input.progress,
154
+ metadata: input.metadata,
155
+ attribution: input.attribution,
156
+ subscription: input.subscription,
157
+ subscriptionStatus: input.subscriptionStatus,
158
+ },
159
+ errorMessage: 'Failed to update user',
160
+ });
161
+ }
162
+ async uploadPhoto(input) {
163
+ return this.postJson('/sdk/public/uploads/photos', {
164
+ runtimeConfig: input.runtimeConfig,
165
+ body: {
166
+ user_id: input.userId,
167
+ fileName: input.fileName,
168
+ mimeType: input.mimeType,
169
+ imageDataUrl: input.imageDataUrl,
170
+ source: input.source,
171
+ },
172
+ errorMessage: 'Failed to upload photo',
173
+ });
174
+ }
175
+ async listPaymentPlans(input = {}) {
176
+ const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
177
+ return this.getJson('/sdk/public/payments/plans', {
178
+ runtimeConfig: input.runtimeConfig,
179
+ searchParams: {
180
+ environment: input.environment,
181
+ funnelId,
182
+ },
183
+ errorMessage: 'Unable to load payment plans',
184
+ });
185
+ }
186
+ async createHostedCheckoutSession(input) {
187
+ return this.postJson('/sdk/public/payments/checkout-session', {
188
+ runtimeConfig: input.runtimeConfig,
189
+ body: {
190
+ planId: input.planId,
191
+ providerPlanId: input.providerPlanId,
192
+ title: input.title,
193
+ description: input.description,
194
+ amountCents: input.amountCents,
195
+ billingInterval: input.billingInterval,
196
+ billingIntervalCount: input.billingIntervalCount,
197
+ couponId: input.couponId,
198
+ analyticsMetadata: input.analyticsMetadata,
199
+ successUrl: input.successUrl,
200
+ cancelUrl: input.cancelUrl,
201
+ customerEmail: input.customerEmail,
202
+ user_id: input.userId,
203
+ environment: input.environment,
204
+ },
205
+ errorMessage: 'Unable to create checkout session',
206
+ });
207
+ }
208
+ async createSubscriptionCheckout(input) {
209
+ return this.postJson('/sdk/public/payments/subscriptions', {
210
+ runtimeConfig: input.runtimeConfig,
211
+ body: {
212
+ planId: input.planId,
213
+ providerPlanId: input.providerPlanId,
214
+ title: input.title,
215
+ description: input.description,
216
+ amountCents: input.amountCents,
217
+ billingInterval: input.billingInterval,
218
+ billingIntervalCount: input.billingIntervalCount,
219
+ couponId: input.couponId,
220
+ analyticsMetadata: input.analyticsMetadata,
221
+ customerEmail: input.customerEmail,
222
+ returnUrl: input.returnUrl,
223
+ user_id: input.userId,
224
+ environment: input.environment,
225
+ },
226
+ errorMessage: 'Unable to create subscription checkout',
227
+ });
228
+ }
229
+ async updateSubscriptionCheckoutPlan(input) {
230
+ return this.postJson('/sdk/public/payments/subscriptions/update-plan', {
231
+ runtimeConfig: input.runtimeConfig,
232
+ body: {
233
+ checkoutSessionId: input.checkoutSessionId,
234
+ planId: input.planId,
235
+ providerPlanId: input.providerPlanId,
236
+ title: input.title,
237
+ description: input.description,
238
+ amountCents: input.amountCents,
239
+ billingInterval: input.billingInterval,
240
+ billingIntervalCount: input.billingIntervalCount,
241
+ couponId: input.couponId,
242
+ analyticsMetadata: input.analyticsMetadata,
243
+ customerEmail: input.customerEmail,
244
+ user_id: input.userId,
245
+ environment: input.environment,
246
+ },
247
+ errorMessage: 'Unable to update subscription checkout',
248
+ });
249
+ }
250
+ async verifySubscriptionCheckout(input) {
251
+ return this.postJson('/sdk/public/payments/subscriptions/checkout-session-status', {
252
+ runtimeConfig: input.runtimeConfig,
253
+ body: {
254
+ checkoutSessionId: input.checkoutSessionId,
255
+ environment: input.environment,
256
+ },
257
+ errorMessage: 'Unable to verify subscription checkout',
258
+ });
259
+ }
260
+ async createOneTimePaymentIntent(input) {
261
+ return this.postJson('/sdk/public/payments/intents', {
262
+ runtimeConfig: input.runtimeConfig,
263
+ body: {
264
+ planId: input.planId,
265
+ amountCents: input.amountCents,
266
+ couponId: input.couponId,
267
+ analyticsMetadata: input.analyticsMetadata,
268
+ user_id: input.userId,
269
+ environment: input.environment,
270
+ },
271
+ errorMessage: 'Unable to create payment intent',
272
+ });
273
+ }
274
+ async chargeOneClickPayment(input) {
275
+ return this.postJson('/sdk/public/payments/one-click', {
276
+ runtimeConfig: input.runtimeConfig,
277
+ body: {
278
+ planId: input.planId,
279
+ amountCents: input.amountCents,
280
+ currency: input.currency,
281
+ analyticsMetadata: input.analyticsMetadata,
282
+ user_id: input.userId,
283
+ stripe_customer_id: input.stripeCustomerId,
284
+ checkoutSessionId: input.checkoutSessionId,
285
+ idempotencyKey: input.idempotencyKey,
286
+ environment: input.environment,
287
+ },
288
+ errorMessage: 'Unable to charge one-click payment',
289
+ });
290
+ }
291
+ async updateSubscription(input) {
292
+ return this.postJson(`/sdk/public/subscriptions/${encodeURIComponent(input.subscriptionId)}/${input.action}`, {
293
+ runtimeConfig: input.runtimeConfig,
294
+ body: {
295
+ user_id: input.userId,
296
+ stripe_customer_id: input.stripeCustomerId,
297
+ funnelId: input.funnelId,
298
+ coupon_id: input.couponId,
299
+ },
300
+ includeRuntimeFields: false,
301
+ includePublishableKey: false,
302
+ errorMessage: 'Failed to update subscription',
303
+ });
304
+ }
305
+ async listSubscriptions(input = {}) {
306
+ var _a;
307
+ const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
308
+ return this.getJson('/sdk/public/subscriptions', {
309
+ runtimeConfig: input.runtimeConfig,
310
+ searchParams: {
311
+ user_id: input.userId,
312
+ stripe_customer_id: input.stripeCustomerId,
313
+ funnelId: (_a = input.funnelId) !== null && _a !== void 0 ? _a : funnelId,
314
+ },
315
+ errorMessage: 'Failed to load subscriptions',
316
+ });
317
+ }
318
+ }
319
+ export const funnelSdkService = new FunnelSdkService();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/runtime",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",