@funnelsgrove/runtime 0.11.2 → 0.11.3

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 (123) hide show
  1. package/README.md +51 -0
  2. package/dist/components/ManageSubscriptionScreen.js +7 -2
  3. package/dist/services/api.service.d.ts +28 -104
  4. package/dist/services/api.service.js +24 -519
  5. package/dist/services/funnel-sdk/checkout/chargeOneClickPayment.d.ts +2 -0
  6. package/dist/services/funnel-sdk/checkout/chargeOneClickPayment.js +17 -0
  7. package/dist/services/funnel-sdk/checkout/createHostedCheckoutSession.d.ts +2 -0
  8. package/dist/services/funnel-sdk/checkout/createHostedCheckoutSession.js +9 -0
  9. package/dist/services/funnel-sdk/checkout/createOneTimeCheckout.d.ts +2 -0
  10. package/dist/services/funnel-sdk/checkout/createOneTimeCheckout.js +18 -0
  11. package/dist/services/funnel-sdk/checkout/createOneTimePaymentIntent.d.ts +3 -0
  12. package/dist/services/funnel-sdk/checkout/createOneTimePaymentIntent.js +10 -0
  13. package/dist/services/funnel-sdk/checkout/createSubscriptionCheckout.d.ts +2 -0
  14. package/dist/services/funnel-sdk/checkout/createSubscriptionCheckout.js +19 -0
  15. package/dist/services/funnel-sdk/checkout/requests.d.ts +126 -0
  16. package/dist/services/funnel-sdk/checkout/requests.js +111 -0
  17. package/dist/services/funnel-sdk/checkout/types.d.ts +183 -0
  18. package/dist/services/funnel-sdk/checkout/types.js +1 -0
  19. package/dist/services/funnel-sdk/checkout/updateSubscriptionCheckoutPlan.d.ts +2 -0
  20. package/dist/services/funnel-sdk/checkout/updateSubscriptionCheckoutPlan.js +18 -0
  21. package/dist/services/funnel-sdk/checkout/verifySubscriptionCheckout.d.ts +2 -0
  22. package/dist/services/funnel-sdk/checkout/verifySubscriptionCheckout.js +18 -0
  23. package/dist/services/funnel-sdk/shared/http.d.ts +20 -0
  24. package/dist/services/funnel-sdk/shared/http.js +65 -0
  25. package/dist/services/funnel-sdk/shared/runtimeConfig.d.ts +17 -0
  26. package/dist/services/funnel-sdk/shared/runtimeConfig.js +42 -0
  27. package/dist/services/funnel-sdk/shared/types.d.ts +9 -0
  28. package/dist/services/funnel-sdk/shared/types.js +1 -0
  29. package/dist/services/funnel-sdk/subscriptions/listSubscriptions.d.ts +3 -0
  30. package/dist/services/funnel-sdk/subscriptions/listSubscriptions.js +17 -0
  31. package/dist/services/funnel-sdk/subscriptions/types.d.ts +24 -0
  32. package/dist/services/funnel-sdk/subscriptions/types.js +1 -0
  33. package/dist/services/funnel-sdk/subscriptions/updateSubscription.d.ts +2 -0
  34. package/dist/services/funnel-sdk/subscriptions/updateSubscription.js +33 -0
  35. package/dist/services/funnel-sdk/subscriptions/validator.d.ts +2 -0
  36. package/dist/services/funnel-sdk/subscriptions/validator.js +15 -0
  37. package/dist/services/funnel-sdk/uploads/preparePhotoUpload.d.ts +4 -0
  38. package/dist/services/funnel-sdk/uploads/preparePhotoUpload.js +19 -0
  39. package/dist/services/funnel-sdk/uploads/types.d.ts +32 -0
  40. package/dist/services/funnel-sdk/uploads/types.js +1 -0
  41. package/dist/services/funnel-sdk/uploads/uploadPhoto.d.ts +7 -0
  42. package/dist/services/funnel-sdk/uploads/uploadPhoto.js +18 -0
  43. package/dist/services/funnel-sdk/uploads/uploadPhotoFile.d.ts +2 -0
  44. package/dist/services/funnel-sdk/uploads/uploadPhotoFile.js +26 -0
  45. package/dist/services/funnel-sdk/users/bootstrapUser.d.ts +3 -0
  46. package/dist/services/funnel-sdk/users/bootstrapUser.js +15 -0
  47. package/dist/services/funnel-sdk/users/captureEmail.d.ts +2 -0
  48. package/dist/services/funnel-sdk/users/captureEmail.js +15 -0
  49. package/dist/services/funnel-sdk/users/claimUserSubscription.d.ts +3 -0
  50. package/dist/services/funnel-sdk/users/claimUserSubscription.js +7 -0
  51. package/dist/services/funnel-sdk/users/getCurrentUser.d.ts +3 -0
  52. package/dist/services/funnel-sdk/users/getCurrentUser.js +13 -0
  53. package/dist/services/funnel-sdk/users/getUserSubscriptionStatus.d.ts +3 -0
  54. package/dist/services/funnel-sdk/users/getUserSubscriptionStatus.js +14 -0
  55. package/dist/services/funnel-sdk/users/types.d.ts +57 -0
  56. package/dist/services/funnel-sdk/users/types.js +1 -0
  57. package/dist/services/funnel-sdk/users/updateFunnelUser.d.ts +3 -0
  58. package/dist/services/funnel-sdk/users/updateFunnelUser.js +18 -0
  59. package/dist/services/funnel-sdk.service.d.ts +26 -319
  60. package/dist/services/funnel-sdk.service.js +38 -456
  61. package/dist/services/session/bootstrap/bootstrapSession.d.ts +8 -0
  62. package/dist/services/session/bootstrap/bootstrapSession.js +23 -0
  63. package/dist/services/session/bootstrap/currentUser.d.ts +1 -0
  64. package/dist/services/session/bootstrap/currentUser.js +37 -0
  65. package/dist/services/session/bootstrap/idempotency.d.ts +9 -0
  66. package/dist/services/session/bootstrap/idempotency.js +97 -0
  67. package/dist/services/session/bootstrap/requests.d.ts +23 -0
  68. package/dist/services/session/bootstrap/requests.js +25 -0
  69. package/dist/services/session/bootstrap/responses.d.ts +3 -0
  70. package/dist/services/session/bootstrap/responses.js +10 -0
  71. package/dist/services/session/bootstrap/types.d.ts +11 -0
  72. package/dist/services/session/bootstrap/types.js +1 -0
  73. package/dist/services/session/emailCapture/captureEmail.d.ts +8 -0
  74. package/dist/services/session/emailCapture/captureEmail.js +13 -0
  75. package/dist/services/session/emailCapture/requests.d.ts +12 -0
  76. package/dist/services/session/emailCapture/requests.js +6 -0
  77. package/dist/services/session/emailCapture/responses.d.ts +3 -0
  78. package/dist/services/session/emailCapture/responses.js +14 -0
  79. package/dist/services/session/emailCapture/types.d.ts +8 -0
  80. package/dist/services/session/emailCapture/types.js +1 -0
  81. package/dist/services/session/emailCapture/validator.d.ts +16 -0
  82. package/dist/services/session/emailCapture/validator.js +37 -0
  83. package/dist/services/session/identity.d.ts +6 -0
  84. package/dist/services/session/identity.js +48 -0
  85. package/dist/services/session/ping/pingUserContext.d.ts +2 -0
  86. package/dist/services/session/ping/pingUserContext.js +10 -0
  87. package/dist/services/session/ping/transport.d.ts +2 -0
  88. package/dist/services/session/ping/transport.js +41 -0
  89. package/dist/services/session/shared/values.d.ts +6 -0
  90. package/dist/services/session/shared/values.js +25 -0
  91. package/dist/services/session/subscriptions/getManageSubscriptions.d.ts +3 -0
  92. package/dist/services/session/subscriptions/getManageSubscriptions.js +8 -0
  93. package/dist/services/session/subscriptions/identity.d.ts +3 -0
  94. package/dist/services/session/subscriptions/identity.js +8 -0
  95. package/dist/services/session/subscriptions/types.d.ts +35 -0
  96. package/dist/services/session/subscriptions/types.js +1 -0
  97. package/dist/services/session/subscriptions/updateSubscription.d.ts +10 -0
  98. package/dist/services/session/subscriptions/updateSubscription.js +7 -0
  99. package/dist/services/session/subscriptions/validator.d.ts +6 -0
  100. package/dist/services/session/subscriptions/validator.js +13 -0
  101. package/dist/services/session/uploads/localPhoto.d.ts +2 -0
  102. package/dist/services/session/uploads/localPhoto.js +10 -0
  103. package/dist/services/session/uploads/requests.d.ts +10 -0
  104. package/dist/services/session/uploads/requests.js +7 -0
  105. package/dist/services/session/uploads/types.d.ts +7 -0
  106. package/dist/services/session/uploads/types.js +1 -0
  107. package/dist/services/session/uploads/uploadTempPhoto.d.ts +6 -0
  108. package/dist/services/session/uploads/uploadTempPhoto.js +13 -0
  109. package/dist/services/session/uploads/validator.d.ts +3 -0
  110. package/dist/services/session/uploads/validator.js +12 -0
  111. package/dist/services/session/users/mappers.d.ts +13 -0
  112. package/dist/services/session/users/mappers.js +33 -0
  113. package/dist/services/session/users/requests.d.ts +15 -0
  114. package/dist/services/session/users/requests.js +8 -0
  115. package/dist/services/session/users/syncUrlUserAttributes.d.ts +10 -0
  116. package/dist/services/session/users/syncUrlUserAttributes.js +21 -0
  117. package/dist/services/session/users/types.d.ts +20 -0
  118. package/dist/services/session/users/types.js +1 -0
  119. package/dist/services/session/users/updateUser.d.ts +6 -0
  120. package/dist/services/session/users/updateUser.js +18 -0
  121. package/package.json +1 -1
  122. /package/dist/services/{funnel-sdk-payments.functions.d.ts → funnel-sdk/checkout/selection.d.ts} +0 -0
  123. /package/dist/services/{funnel-sdk-payments.functions.js → funnel-sdk/checkout/selection.js} +0 -0
@@ -1,457 +1,39 @@
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
- import { CURRENT_STEP_CONTRACT_VERSION } from '../steps/step-contract.js';
4
- import { toV2CheckoutSelection, toV2CheckoutSessionRequest } from './funnel-sdk-payments.functions.js';
5
- const trimTrailingSlash = (value) => value.replace(/\/+$/, '');
6
- const toNormalizedPath = (path) => (path.startsWith('/') ? path : `/${path}`);
7
- const asTrimmedStringOrNull = (value) => {
8
- if (typeof value !== 'string') {
9
- return null;
10
- }
11
- const trimmed = value.trim();
12
- return trimmed || null;
1
+ import { bootstrapUser } from './funnel-sdk/users/bootstrapUser.js';
2
+ import { captureEmail } from './funnel-sdk/users/captureEmail.js';
3
+ import { getCurrentUser } from './funnel-sdk/users/getCurrentUser.js';
4
+ import { getUserSubscriptionStatus } from './funnel-sdk/users/getUserSubscriptionStatus.js';
5
+ import { claimUserSubscription } from './funnel-sdk/users/claimUserSubscription.js';
6
+ import { updateFunnelUser } from './funnel-sdk/users/updateFunnelUser.js';
7
+ import { uploadPhoto } from './funnel-sdk/uploads/uploadPhoto.js';
8
+ import { preparePhotoUpload } from './funnel-sdk/uploads/preparePhotoUpload.js';
9
+ import { uploadPhotoFile } from './funnel-sdk/uploads/uploadPhotoFile.js';
10
+ import { createHostedCheckoutSession } from './funnel-sdk/checkout/createHostedCheckoutSession.js';
11
+ import { createSubscriptionCheckout } from './funnel-sdk/checkout/createSubscriptionCheckout.js';
12
+ import { createOneTimeCheckout } from './funnel-sdk/checkout/createOneTimeCheckout.js';
13
+ import { updateSubscriptionCheckoutPlan } from './funnel-sdk/checkout/updateSubscriptionCheckoutPlan.js';
14
+ import { verifySubscriptionCheckout } from './funnel-sdk/checkout/verifySubscriptionCheckout.js';
15
+ import { createOneTimePaymentIntent } from './funnel-sdk/checkout/createOneTimePaymentIntent.js';
16
+ import { chargeOneClickPayment } from './funnel-sdk/checkout/chargeOneClickPayment.js';
17
+ import { updateSubscription } from './funnel-sdk/subscriptions/updateSubscription.js';
18
+ import { listSubscriptions } from './funnel-sdk/subscriptions/listSubscriptions.js';
19
+ // Stable public facade; each use case owns its request and sequencing.
20
+ export const funnelSdkService = {
21
+ bootstrapUser,
22
+ captureEmail,
23
+ getCurrentUser,
24
+ getUserSubscriptionStatus,
25
+ claimUserSubscription,
26
+ updateFunnelUser,
27
+ uploadPhoto,
28
+ preparePhotoUpload,
29
+ uploadPhotoFile,
30
+ createHostedCheckoutSession,
31
+ createSubscriptionCheckout,
32
+ createOneTimeCheckout,
33
+ updateSubscriptionCheckoutPlan,
34
+ verifySubscriptionCheckout,
35
+ createOneTimePaymentIntent,
36
+ chargeOneClickPayment,
37
+ updateSubscription,
38
+ listSubscriptions,
13
39
  };
14
- const removeEmptyFields = (input) => {
15
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== null && value !== ''));
16
- };
17
- const resolveRuntimeConfig = (runtimeConfig) => {
18
- var _a, _b, _c;
19
- return ({
20
- apiBaseUrl: asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.apiBaseUrl),
21
- funnelId: (_a = asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelId)) !== null && _a !== void 0 ? _a : asTrimmedStringOrNull(FUNNEL_ID),
22
- funnelVersionId: (_b = asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelVersionId)) !== null && _b !== void 0 ? _b : asTrimmedStringOrNull(FUNNEL_VERSION_ID),
23
- funnelSdkPublishableKey: resolvePreviewPaymentPublishableKey((_c = asTrimmedStringOrNull(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey)) !== null && _c !== void 0 ? _c : asTrimmedStringOrNull(FUNNEL_SDK_PUBLISHABLE_KEY)) || null,
24
- paymentsApiVersion: (runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.paymentsApiVersion) === 'v2' ? 'v2' : 'v1',
25
- });
26
- };
27
- const buildApiUrl = (path, runtimeConfig) => {
28
- const { apiBaseUrl } = resolveRuntimeConfig(runtimeConfig);
29
- if (!apiBaseUrl) {
30
- return buildMainApiUrl(path);
31
- }
32
- return `${trimTrailingSlash(apiBaseUrl)}${toNormalizedPath(path)}`;
33
- };
34
- const buildHeaders = (runtimeConfig, headers) => {
35
- const { funnelSdkPublishableKey } = resolveRuntimeConfig(runtimeConfig);
36
- if (!funnelSdkPublishableKey) {
37
- return Object.assign({}, (headers !== null && headers !== void 0 ? headers : {}));
38
- }
39
- return Object.assign({ 'x-sdk-publishable-key': funnelSdkPublishableKey }, (headers !== null && headers !== void 0 ? headers : {}));
40
- };
41
- const withRuntimeFields = (runtimeConfig, body, options) => {
42
- const { funnelId, funnelVersionId, funnelSdkPublishableKey } = resolveRuntimeConfig(runtimeConfig);
43
- return removeEmptyFields(Object.assign({ publishableKey: (options === null || options === void 0 ? void 0 : options.includePublishableKey) === false
44
- ? undefined
45
- : funnelSdkPublishableKey || undefined, funnelId: funnelId || undefined, funnelVersionId: funnelVersionId || undefined }, (body !== null && body !== void 0 ? body : {})));
46
- };
47
- const parseErrorMessage = async (response, fallbackMessage) => {
48
- try {
49
- const payload = (await response.json());
50
- const apiError = asTrimmedStringOrNull(payload.error);
51
- if (apiError) {
52
- return apiError;
53
- }
54
- }
55
- catch (_a) {
56
- // ignore JSON parsing errors
57
- }
58
- try {
59
- const text = await response.text();
60
- if (text.trim()) {
61
- return text;
62
- }
63
- }
64
- catch (_b) {
65
- // ignore text parsing errors
66
- }
67
- return fallbackMessage;
68
- };
69
- class FunnelSdkService {
70
- async getJson(path, input) {
71
- var _a;
72
- const url = new URL(buildApiUrl(path, input.runtimeConfig));
73
- for (const [key, value] of Object.entries((_a = input.searchParams) !== null && _a !== void 0 ? _a : {})) {
74
- const normalizedValue = asTrimmedStringOrNull(value);
75
- if (normalizedValue) {
76
- url.searchParams.set(key, normalizedValue);
77
- }
78
- }
79
- const response = await fetch(url.toString(), {
80
- method: 'GET',
81
- headers: buildHeaders(input.runtimeConfig),
82
- });
83
- if (!response.ok) {
84
- throw new Error(await parseErrorMessage(response, input.errorMessage));
85
- }
86
- return (await response.json());
87
- }
88
- async writeJson(method, path, input) {
89
- var _a;
90
- const response = await fetch(buildApiUrl(path, input.runtimeConfig), {
91
- method,
92
- headers: buildHeaders(input.runtimeConfig, {
93
- 'Content-Type': 'application/json',
94
- }),
95
- body: JSON.stringify(input.includeRuntimeFields === false
96
- ? removeEmptyFields((_a = input.body) !== null && _a !== void 0 ? _a : {})
97
- : withRuntimeFields(input.runtimeConfig, input.body, {
98
- includePublishableKey: input.includePublishableKey,
99
- })),
100
- });
101
- if (!response.ok) {
102
- throw new Error(await parseErrorMessage(response, input.errorMessage));
103
- }
104
- return (await response.json());
105
- }
106
- postJson(path, input) {
107
- return this.writeJson('POST', path, input);
108
- }
109
- async patchJson(path, input) {
110
- return this.writeJson('PATCH', path, input);
111
- }
112
- async bootstrapUser(input = {}) {
113
- return this.postJson('/sdk/public/users/bootstrap', {
114
- runtimeConfig: input.runtimeConfig,
115
- body: {
116
- user_id: input.userId,
117
- idempotencyKey: input.idempotencyKey,
118
- email: input.email,
119
- fullName: input.fullName,
120
- metadata: input.metadata,
121
- attribution: input.attribution,
122
- },
123
- errorMessage: 'Failed to bootstrap user session',
124
- });
125
- }
126
- async captureEmail(input) {
127
- return this.postJson('/sdk/public/users/email-capture', {
128
- runtimeConfig: input.runtimeConfig,
129
- body: {
130
- user_id: input.userId,
131
- email: input.email,
132
- environment: input.environment,
133
- answers: input.answers,
134
- stepContractVersion: CURRENT_STEP_CONTRACT_VERSION,
135
- },
136
- errorMessage: 'Failed to capture email',
137
- });
138
- }
139
- async getCurrentUser(input) {
140
- const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
141
- return this.getJson('/sdk/public/users/me', {
142
- runtimeConfig: input.runtimeConfig,
143
- searchParams: {
144
- funnelId,
145
- user_id: input.userId,
146
- },
147
- errorMessage: 'Failed to load user',
148
- });
149
- }
150
- async getUserSubscriptionStatus(input = {}) {
151
- const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
152
- return this.getJson('/sdk/public/users/subscription', {
153
- runtimeConfig: input.runtimeConfig,
154
- searchParams: {
155
- funnelId,
156
- user_id: input.userId,
157
- email: input.email,
158
- },
159
- errorMessage: 'Failed to load user subscription status',
160
- });
161
- }
162
- async claimUserSubscription(input) {
163
- return this.postJson(`/sdk/public/users/${encodeURIComponent(input.userId)}/claim_subscription`, {
164
- runtimeConfig: input.runtimeConfig,
165
- errorMessage: 'Failed to claim user subscription',
166
- });
167
- }
168
- async updateFunnelUser(input) {
169
- return this.postJson('/public/funnel-users/upsert', {
170
- runtimeConfig: input.runtimeConfig,
171
- body: {
172
- user_id: input.userId,
173
- email: input.email,
174
- fullName: input.fullName,
175
- document: input.document,
176
- progress: input.progress,
177
- metadata: input.metadata,
178
- attribution: input.attribution,
179
- subscription: input.subscription,
180
- subscriptionStatus: input.subscriptionStatus,
181
- },
182
- errorMessage: 'Failed to update user',
183
- });
184
- }
185
- async uploadPhoto(input) {
186
- return this.postJson('/sdk/public/uploads/photos', {
187
- runtimeConfig: input.runtimeConfig,
188
- body: {
189
- user_id: input.userId,
190
- fileName: input.fileName,
191
- mimeType: input.mimeType,
192
- imageDataUrl: input.imageDataUrl,
193
- source: input.source,
194
- },
195
- errorMessage: 'Failed to upload photo',
196
- });
197
- }
198
- async preparePhotoUpload(input) {
199
- const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
200
- return this.postJson('/sdk/public/uploads/photos/prepare', {
201
- runtimeConfig: input.runtimeConfig,
202
- body: {
203
- funnelId,
204
- userId: input.userId,
205
- fileName: input.fileName,
206
- contentType: input.contentType,
207
- sizeBytes: input.sizeBytes,
208
- source: input.source,
209
- },
210
- includeRuntimeFields: false,
211
- includePublishableKey: false,
212
- errorMessage: 'Failed to prepare photo upload',
213
- });
214
- }
215
- async uploadPhotoFile(input) {
216
- const prepared = await this.preparePhotoUpload({
217
- fileName: input.fileName,
218
- contentType: input.contentType,
219
- sizeBytes: input.file.size,
220
- source: input.source,
221
- userId: input.userId,
222
- runtimeConfig: input.runtimeConfig,
223
- });
224
- const response = await fetch(prepared.uploadUrl, {
225
- method: 'PUT',
226
- headers: prepared.uploadHeaders,
227
- body: input.file,
228
- });
229
- if (!response.ok) {
230
- throw new Error(`Failed to upload photo (${response.status})`);
231
- }
232
- return {
233
- key: prepared.key,
234
- publicUrl: prepared.publicUrl,
235
- contentType: prepared.contentType,
236
- sizeBytes: prepared.sizeBytes,
237
- uploadedAt: new Date().toISOString(),
238
- };
239
- }
240
- async createHostedCheckoutSession(input) {
241
- return this.postJson('/sdk/public/payments/checkout-session', {
242
- runtimeConfig: input.runtimeConfig,
243
- body: {
244
- offerSetId: input.offerSetId,
245
- offerSetKey: input.offerSetKey,
246
- planId: input.planId,
247
- providerPlanId: input.providerPlanId,
248
- title: input.title,
249
- description: input.description,
250
- amountCents: input.amountCents,
251
- billingInterval: input.billingInterval,
252
- billingIntervalCount: input.billingIntervalCount,
253
- couponId: input.couponId,
254
- analyticsMetadata: input.analyticsMetadata,
255
- successUrl: input.successUrl,
256
- cancelUrl: input.cancelUrl,
257
- customerEmail: input.customerEmail,
258
- user_id: input.userId,
259
- environment: input.environment,
260
- },
261
- errorMessage: 'Unable to create checkout session',
262
- });
263
- }
264
- async createSubscriptionCheckout(input) {
265
- const { paymentsApiVersion } = resolveRuntimeConfig(input.runtimeConfig);
266
- if (paymentsApiVersion === 'v2') {
267
- return this.postJson('/sdk/public/v2/payments/checkout-sessions', {
268
- runtimeConfig: input.runtimeConfig,
269
- body: toV2CheckoutSessionRequest(input, 'subscription'),
270
- errorMessage: 'Unable to create subscription checkout',
271
- });
272
- }
273
- return this.postJson('/sdk/public/payments/subscriptions', {
274
- runtimeConfig: input.runtimeConfig,
275
- body: {
276
- offerSetId: input.offerSetId,
277
- offerSetKey: input.offerSetKey,
278
- planId: input.planId,
279
- providerPlanId: input.providerPlanId,
280
- title: input.title,
281
- description: input.description,
282
- amountCents: input.amountCents,
283
- billingInterval: input.billingInterval,
284
- billingIntervalCount: input.billingIntervalCount,
285
- followUpProviderPlanId: input.followUpProviderPlanId,
286
- followUpPlanTitle: input.followUpPlanTitle,
287
- followUpBillingInterval: input.followUpBillingInterval,
288
- followUpBillingIntervalCount: input.followUpBillingIntervalCount,
289
- introIterations: input.introIterations,
290
- couponId: input.couponId,
291
- analyticsMetadata: input.analyticsMetadata,
292
- customerEmail: input.customerEmail,
293
- returnUrl: input.returnUrl,
294
- user_id: input.userId,
295
- anonymousUserId: input.anonymousUserId,
296
- idempotencyKey: input.idempotencyKey,
297
- environment: input.environment,
298
- },
299
- errorMessage: 'Unable to create subscription checkout',
300
- });
301
- }
302
- async createOneTimeCheckout(input) {
303
- if (resolveRuntimeConfig(input.runtimeConfig).paymentsApiVersion === 'v2') {
304
- return this.postJson('/sdk/public/v2/payments/checkout-sessions', {
305
- runtimeConfig: input.runtimeConfig,
306
- body: toV2CheckoutSessionRequest(input, 'one_time'),
307
- errorMessage: 'Unable to create one-time checkout',
308
- });
309
- }
310
- return this.postJson('/sdk/public/payments/one-time-checkout', {
311
- runtimeConfig: input.runtimeConfig,
312
- body: {
313
- offerSetId: input.offerSetId,
314
- offerSetKey: input.offerSetKey,
315
- planId: input.planId,
316
- providerPlanId: input.providerPlanId,
317
- title: input.title,
318
- description: input.description,
319
- amountCents: input.amountCents,
320
- couponId: input.couponId,
321
- analyticsMetadata: input.analyticsMetadata,
322
- customerEmail: input.customerEmail,
323
- returnUrl: input.returnUrl,
324
- user_id: input.userId,
325
- environment: input.environment,
326
- },
327
- errorMessage: 'Unable to create one-time checkout',
328
- });
329
- }
330
- async updateSubscriptionCheckoutPlan(input) {
331
- const { paymentsApiVersion } = resolveRuntimeConfig(input.runtimeConfig);
332
- if (paymentsApiVersion === 'v2') {
333
- return this.patchJson(`/sdk/public/v2/payments/checkout-sessions/${encodeURIComponent(input.checkoutSessionId)}`, {
334
- runtimeConfig: input.runtimeConfig,
335
- body: Object.assign(Object.assign({}, toV2CheckoutSelection(input)), { couponId: input.couponId, analyticsMetadata: input.analyticsMetadata, environment: input.environment }),
336
- errorMessage: 'Unable to update subscription checkout',
337
- });
338
- }
339
- return this.postJson('/sdk/public/payments/subscriptions/update-plan', {
340
- runtimeConfig: input.runtimeConfig,
341
- body: {
342
- checkoutSessionId: input.checkoutSessionId,
343
- offerSetId: input.offerSetId,
344
- offerSetKey: input.offerSetKey,
345
- planId: input.planId,
346
- providerPlanId: input.providerPlanId,
347
- title: input.title,
348
- description: input.description,
349
- amountCents: input.amountCents,
350
- billingInterval: input.billingInterval,
351
- billingIntervalCount: input.billingIntervalCount,
352
- couponId: input.couponId,
353
- analyticsMetadata: input.analyticsMetadata,
354
- customerEmail: input.customerEmail,
355
- user_id: input.userId,
356
- environment: input.environment,
357
- },
358
- errorMessage: 'Unable to update subscription checkout',
359
- });
360
- }
361
- async verifySubscriptionCheckout(input) {
362
- const { funnelId, paymentsApiVersion } = resolveRuntimeConfig(input.runtimeConfig);
363
- if (paymentsApiVersion === 'v2') {
364
- return this.getJson(`/sdk/public/v2/payments/checkout-sessions/${encodeURIComponent(input.checkoutSessionId)}`, {
365
- runtimeConfig: input.runtimeConfig,
366
- searchParams: { funnelId },
367
- errorMessage: 'Unable to verify subscription checkout',
368
- });
369
- }
370
- return this.postJson('/sdk/public/payments/subscriptions/checkout-session-status', {
371
- runtimeConfig: input.runtimeConfig,
372
- body: {
373
- checkoutSessionId: input.checkoutSessionId,
374
- environment: input.environment,
375
- },
376
- errorMessage: 'Unable to verify subscription checkout',
377
- });
378
- }
379
- /**
380
- * @deprecated Use createOneTimeCheckout for one-time paywall checkout.
381
- */
382
- async createOneTimePaymentIntent(input) {
383
- return this.postJson('/sdk/public/payments/intents', {
384
- runtimeConfig: input.runtimeConfig,
385
- body: {
386
- offerSetId: input.offerSetId,
387
- offerSetKey: input.offerSetKey,
388
- planId: input.planId,
389
- amountCents: input.amountCents,
390
- couponId: input.couponId,
391
- analyticsMetadata: input.analyticsMetadata,
392
- user_id: input.userId,
393
- environment: input.environment,
394
- },
395
- errorMessage: 'Unable to create payment intent',
396
- });
397
- }
398
- async chargeOneClickPayment(input) {
399
- var _a, _b, _c;
400
- if (resolveRuntimeConfig(input.runtimeConfig).paymentsApiVersion === 'v2') {
401
- const selection = toV2CheckoutSelection(input);
402
- if (!((_a = input.checkoutSessionId) === null || _a === void 0 ? void 0 : _a.trim()) || !((_b = input.userId) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = input.idempotencyKey) === null || _c === void 0 ? void 0 : _c.trim())) {
403
- throw new Error('V2 one-click requires checkoutSessionId, userId and idempotencyKey');
404
- }
405
- return this.postJson('/sdk/public/v2/payments/one-click', {
406
- runtimeConfig: input.runtimeConfig,
407
- body: Object.assign(Object.assign({}, selection), { checkoutSessionId: input.checkoutSessionId, userId: input.userId, idempotencyKey: input.idempotencyKey, analyticsMetadata: input.analyticsMetadata, environment: input.environment }),
408
- errorMessage: 'Unable to charge one-click payment',
409
- });
410
- }
411
- return this.postJson('/sdk/public/payments/one-click', {
412
- runtimeConfig: input.runtimeConfig,
413
- body: {
414
- planId: input.planId,
415
- providerPlanId: input.providerPlanId,
416
- amountCents: input.amountCents,
417
- currency: input.currency,
418
- description: input.description,
419
- analyticsMetadata: input.analyticsMetadata,
420
- user_id: input.userId,
421
- stripe_customer_id: input.stripeCustomerId,
422
- checkoutSessionId: input.checkoutSessionId,
423
- idempotencyKey: input.idempotencyKey,
424
- environment: input.environment,
425
- },
426
- errorMessage: 'Unable to charge one-click payment',
427
- });
428
- }
429
- async updateSubscription(input) {
430
- return this.postJson(`/sdk/public/subscriptions/${encodeURIComponent(input.subscriptionId)}/${input.action}`, {
431
- runtimeConfig: input.runtimeConfig,
432
- body: {
433
- user_id: input.userId,
434
- stripe_customer_id: input.stripeCustomerId,
435
- funnelId: input.funnelId,
436
- coupon_id: input.couponId,
437
- },
438
- includeRuntimeFields: false,
439
- includePublishableKey: false,
440
- errorMessage: 'Failed to update subscription',
441
- });
442
- }
443
- async listSubscriptions(input = {}) {
444
- var _a;
445
- const { funnelId } = resolveRuntimeConfig(input.runtimeConfig);
446
- return this.getJson('/sdk/public/subscriptions', {
447
- runtimeConfig: input.runtimeConfig,
448
- searchParams: {
449
- user_id: input.userId,
450
- stripe_customer_id: input.stripeCustomerId,
451
- funnelId: (_a = input.funnelId) !== null && _a !== void 0 ? _a : funnelId,
452
- },
453
- errorMessage: 'Failed to load subscriptions',
454
- });
455
- }
456
- }
457
- export const funnelSdkService = new FunnelSdkService();
@@ -0,0 +1,8 @@
1
+ import type { FunnelUserAttribution } from '../../../runtime/funnel-attribution.js';
2
+ import type { AppUser } from '../users/types.js';
3
+ export declare function bootstrapSession(input?: {
4
+ userId?: string;
5
+ email?: string;
6
+ name?: string;
7
+ attribution?: FunnelUserAttribution;
8
+ }): Promise<AppUser>;
@@ -0,0 +1,23 @@
1
+ import { FUNNEL_ID, FUNNEL_VERSION_ID, getFunnelSdkPublishableKey } from '../../runtime-api.config.js';
2
+ import { getRuntimeMode } from '../../runtime-mode.service.js';
3
+ import { funnelSdkService } from '../../funnel-sdk.service.js';
4
+ import { resolveUrlUserAttributes } from '../../../runtime/url-user-attributes.js';
5
+ import { persistUserId } from '../identity.js';
6
+ import { resolveBootstrapUserId } from './currentUser.js';
7
+ import { digestBootstrapRequest, getOrCreateBootstrapIdempotencyRecord, clearBootstrapIdempotencyRecord } from './idempotency.js';
8
+ import { toBootstrapProfile, toBootstrapRequest, toBootstrapFingerprint } from './requests.js';
9
+ import { resolveBootstrapResponseUserId, toBootstrapUser } from './responses.js';
10
+ export async function bootstrapSession(input) {
11
+ const urlUserAttributes = resolveUrlUserAttributes();
12
+ const profile = toBootstrapProfile(input);
13
+ const userId = await resolveBootstrapUserId((input === null || input === void 0 ? void 0 : input.userId) || urlUserAttributes.userId);
14
+ if (!getFunnelSdkPublishableKey())
15
+ return toBootstrapUser(userId, profile);
16
+ const request = toBootstrapRequest(userId, profile, getRuntimeMode());
17
+ const fingerprint = await digestBootstrapRequest(toBootstrapFingerprint(request, FUNNEL_VERSION_ID));
18
+ const record = getOrCreateBootstrapIdempotencyRecord(fingerprint);
19
+ const payload = await funnelSdkService.bootstrapUser(Object.assign(Object.assign({}, request), { idempotencyKey: record.key }));
20
+ clearBootstrapIdempotencyRecord(record);
21
+ const persistedUserId = persistUserId(resolveBootstrapResponseUserId(payload, userId), FUNNEL_ID) || userId;
22
+ return toBootstrapUser(persistedUserId, profile, payload);
23
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveBootstrapUserId(candidateUserId?: string | null): Promise<string>;
@@ -0,0 +1,37 @@
1
+ import { FUNNEL_ID, getFunnelSdkPublishableKey } from '../../runtime-api.config.js';
2
+ import { funnelSdkService } from '../../funnel-sdk.service.js';
3
+ import { asString, normalizeUserId } from '../shared/values.js';
4
+ import { readStoredUserId, generateUserId, persistUserId } from '../identity.js';
5
+ async function fetchCurrentUserState(userId) {
6
+ const publishableKey = getFunnelSdkPublishableKey();
7
+ if (!publishableKey || !FUNNEL_ID) {
8
+ return null;
9
+ }
10
+ try {
11
+ return await funnelSdkService.getCurrentUser({
12
+ userId,
13
+ });
14
+ }
15
+ catch (_a) {
16
+ return null;
17
+ }
18
+ }
19
+ export async function resolveBootstrapUserId(candidateUserId) {
20
+ var _a;
21
+ const storedUserId = readStoredUserId(FUNNEL_ID);
22
+ const fallbackUserId = storedUserId || generateUserId();
23
+ const normalizedCandidateUserId = normalizeUserId(candidateUserId);
24
+ const publishableKey = getFunnelSdkPublishableKey();
25
+ if (!normalizedCandidateUserId) {
26
+ return persistUserId(fallbackUserId, FUNNEL_ID);
27
+ }
28
+ if (normalizedCandidateUserId === storedUserId) {
29
+ return persistUserId(normalizedCandidateUserId, FUNNEL_ID);
30
+ }
31
+ if (!publishableKey || !FUNNEL_ID) {
32
+ return persistUserId(fallbackUserId, FUNNEL_ID);
33
+ }
34
+ const existingUser = await fetchCurrentUserState(normalizedCandidateUserId);
35
+ const resolvedCandidateUserId = normalizeUserId(asString((_a = existingUser === null || existingUser === void 0 ? void 0 : existingUser.user) === null || _a === void 0 ? void 0 : _a.user_id) || asString(existingUser === null || existingUser === void 0 ? void 0 : existingUser.user_id));
36
+ return persistUserId(resolvedCandidateUserId || fallbackUserId, FUNNEL_ID);
37
+ }
@@ -0,0 +1,9 @@
1
+ type BootstrapIdempotencyRecord = {
2
+ version: 1;
3
+ key: string;
4
+ fingerprint: string;
5
+ };
6
+ export declare const digestBootstrapRequest: (value: unknown) => Promise<string>;
7
+ export declare const getOrCreateBootstrapIdempotencyRecord: (fingerprint: string) => BootstrapIdempotencyRecord;
8
+ export declare const clearBootstrapIdempotencyRecord: (completed: BootstrapIdempotencyRecord) => void;
9
+ export {};
@@ -0,0 +1,97 @@
1
+ import { FUNNEL_ID } from '../../runtime-api.config.js';
2
+ import { asString, canUseDom, isRecord } from '../shared/values.js';
3
+ const DEFAULT_BOOTSTRAP_IDEMPOTENCY_KEY_STORAGE_KEY = 'funnel:bootstrap-idempotency-key';
4
+ const buildBootstrapIdempotencyKeyStorageKey = (funnelId = FUNNEL_ID) => {
5
+ const normalizedFunnelId = asString(funnelId);
6
+ return normalizedFunnelId
7
+ ? `funnel:${normalizedFunnelId}:bootstrap-idempotency-key`
8
+ : DEFAULT_BOOTSTRAP_IDEMPOTENCY_KEY_STORAGE_KEY;
9
+ };
10
+ const generateBootstrapIdempotencyKey = () => {
11
+ var _a;
12
+ if (canUseDom() && typeof ((_a = window.crypto) === null || _a === void 0 ? void 0 : _a.randomUUID) === 'function') {
13
+ return window.crypto.randomUUID();
14
+ }
15
+ return `${Date.now()}_${Math.random().toString(16).slice(2, 10)}`;
16
+ };
17
+ const parseBootstrapIdempotencyRecord = (value) => {
18
+ if (!value) {
19
+ return null;
20
+ }
21
+ try {
22
+ const parsed = JSON.parse(value);
23
+ const key = asString(parsed.key);
24
+ const fingerprint = asString(parsed.fingerprint);
25
+ return parsed.version === 1 && key && fingerprint
26
+ ? { version: 1, key, fingerprint }
27
+ : null;
28
+ }
29
+ catch (_a) {
30
+ return null;
31
+ }
32
+ };
33
+ const toStableJsonValue = (value) => {
34
+ if (Array.isArray(value)) {
35
+ return value.map(toStableJsonValue);
36
+ }
37
+ if (!isRecord(value)) {
38
+ return value;
39
+ }
40
+ return Object.keys(value).sort().reduce((result, key) => {
41
+ if (value[key] !== undefined) {
42
+ result[key] = toStableJsonValue(value[key]);
43
+ }
44
+ return result;
45
+ }, {});
46
+ };
47
+ const buildFallbackDigest = (value) => {
48
+ var _a;
49
+ const bytes = new TextEncoder().encode(value);
50
+ const hashes = Array.from({ length: 8 }, (_, index) => ((0x811c9dc5 ^ Math.imul(index + 1, 0x9e3779b1)) >>> 0));
51
+ for (const byte of bytes) {
52
+ for (let index = 0; index < hashes.length; index += 1) {
53
+ hashes[index] = Math.imul(((_a = hashes[index]) !== null && _a !== void 0 ? _a : 0) ^ byte, 0x01000193 + index * 2) >>> 0;
54
+ }
55
+ }
56
+ return hashes.map((hash) => hash.toString(16).padStart(8, '0')).join('');
57
+ };
58
+ export const digestBootstrapRequest = async (value) => {
59
+ var _a;
60
+ const serialized = JSON.stringify(toStableJsonValue(value));
61
+ const bytes = new TextEncoder().encode(serialized);
62
+ const subtle = (_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.subtle;
63
+ if (!subtle) {
64
+ return `fallback:${buildFallbackDigest(serialized)}`;
65
+ }
66
+ const digest = await subtle.digest('SHA-256', bytes);
67
+ const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
68
+ return `sha256:${hex}`;
69
+ };
70
+ export const getOrCreateBootstrapIdempotencyRecord = (fingerprint) => {
71
+ const created = () => ({
72
+ version: 1,
73
+ key: generateBootstrapIdempotencyKey(),
74
+ fingerprint,
75
+ });
76
+ if (!canUseDom()) {
77
+ return created();
78
+ }
79
+ const storageKey = buildBootstrapIdempotencyKeyStorageKey();
80
+ const existing = parseBootstrapIdempotencyRecord(window.localStorage.getItem(storageKey));
81
+ if ((existing === null || existing === void 0 ? void 0 : existing.fingerprint) === fingerprint) {
82
+ return existing;
83
+ }
84
+ const next = created();
85
+ window.localStorage.setItem(storageKey, JSON.stringify(next));
86
+ return next;
87
+ };
88
+ export const clearBootstrapIdempotencyRecord = (completed) => {
89
+ if (!canUseDom()) {
90
+ return;
91
+ }
92
+ const storageKey = buildBootstrapIdempotencyKeyStorageKey();
93
+ const current = parseBootstrapIdempotencyRecord(window.localStorage.getItem(storageKey));
94
+ if ((current === null || current === void 0 ? void 0 : current.key) === completed.key && current.fingerprint === completed.fingerprint) {
95
+ window.localStorage.removeItem(storageKey);
96
+ }
97
+ };