@funnelfox/payments-sdk 0.2.0-beta.1 → 0.2.0-beta.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.
package/dist/index.cjs CHANGED
@@ -33,10 +33,44 @@ var CheckoutError = class extends Error {
33
33
  this.name = "CheckoutError";
34
34
  }
35
35
  };
36
+ var CHECKOUT_BLOCK_REASONS = ["duplicated_subscription", "duplicated_payment"];
37
+ function isCheckoutBlockReason(reason) {
38
+ return CHECKOUT_BLOCK_REASONS.some((known) => known === reason);
39
+ }
40
+ var CheckoutBlockedError = class extends CheckoutError {
41
+ constructor(reason, buyerMessage) {
42
+ super(`Checkout blocked: ${reason}`);
43
+ this.reason = reason;
44
+ this.buyerMessage = buyerMessage;
45
+ this.name = "CheckoutBlockedError";
46
+ }
47
+ reason;
48
+ buyerMessage;
49
+ };
36
50
 
37
51
  // src/providers/stripe/index.ts
38
52
  var import_stripe_js = require("@stripe/stripe-js");
39
53
 
54
+ // src/providers/stripe/return-url.ts
55
+ var RETURNED_SESSION_PARAM = "ff_checkout_session";
56
+ var SESSION_ID_PLACEHOLDER = "{CHECKOUT_SESSION_ID}";
57
+ var SESSION_ID = /^cs_\w+$/;
58
+ function withReturnedSessionParam(href) {
59
+ const url = new URL(href);
60
+ url.searchParams.set(RETURNED_SESSION_PARAM, SESSION_ID_PLACEHOLDER);
61
+ return url.href.replace(encodeURIComponent(SESSION_ID_PLACEHOLDER), SESSION_ID_PLACEHOLDER);
62
+ }
63
+ function popSessionIdFromUrl() {
64
+ const url = new URL(window.location.href);
65
+ const sessionId = url.searchParams.get(RETURNED_SESSION_PARAM);
66
+ if (sessionId === null) {
67
+ return null;
68
+ }
69
+ url.searchParams.delete(RETURNED_SESSION_PARAM);
70
+ window.history.replaceState(window.history.state, "", url.href);
71
+ return SESSION_ID.test(sessionId) ? sessionId : null;
72
+ }
73
+
40
74
  // src/providers/stripe/helpers.ts
41
75
  function buildStripeCheckoutSessionBody(params) {
42
76
  return {
@@ -62,6 +96,7 @@ function buildStripeCheckoutSessionBody(params) {
62
96
  }
63
97
  },
64
98
  ...params.upgrade && { upgrade: params.upgrade },
99
+ ...params.isSubscription !== void 0 && { is_subscription: params.isSubscription },
65
100
  ...params.walletFirst && { wallet_first: params.walletFirst },
66
101
  ...params.uiMode && { ui_mode: params.uiMode }
67
102
  };
@@ -119,6 +154,12 @@ var StripeCheckoutService = class extends HttpService {
119
154
  `/stripe/purchase${query}`,
120
155
  buildStripeCheckoutSessionBody(params)
121
156
  );
157
+ if (data.status === "failed") {
158
+ if (isCheckoutBlockReason(data.reason)) {
159
+ throw new CheckoutBlockedError(data.reason, data.message || void 0);
160
+ }
161
+ throw new CheckoutError(`Checkout session refused: ${data.reason}`);
162
+ }
122
163
  if (!data.client_secret) {
123
164
  throw new CheckoutError("Backend returned no client_secret");
124
165
  }
@@ -151,6 +192,7 @@ var StripeEmbeddedPageUi = class {
151
192
 
152
193
  // src/providers/stripe/ui/form.ts
153
194
  var CHECKOUT_FORM_BETA = "custom_checkout_payment_form_1";
195
+ var EXPRESS_CHECKOUT_SOURCE = "checkout-form-ece";
154
196
  var StripeFormUi = class {
155
197
  constructor(options = {}) {
156
198
  this.options = options;
@@ -206,17 +248,30 @@ var StripeFormUi = class {
206
248
  }
207
249
  return result.actions;
208
250
  }
251
+ async isFormValid(actions) {
252
+ try {
253
+ const validation = await actions.validateElements();
254
+ return validation.type !== "error";
255
+ } catch {
256
+ return true;
257
+ }
258
+ }
209
259
  async confirm(actions, event, host) {
210
- const express = event.source === "checkout-form-ece";
260
+ const express = event.source === EXPRESS_CHECKOUT_SOURCE;
211
261
  const paymentMethodType = express ? void 0 : event.paymentMethodType ?? void 0;
212
- host.confirmed({ paymentMethodType, express });
213
262
  try {
263
+ const attempted = express || await this.isFormValid(actions);
264
+ if (attempted) {
265
+ host.confirmed({ paymentMethodType, express });
266
+ }
214
267
  const result = await actions.confirm({ formConfirmEvent: event, redirect: "if_required" });
215
268
  if (result.type === "error") {
216
- host.paymentFailed(result.error, {
217
- declineCode: result.error.code === "paymentFailed" ? result.error.paymentFailed.declineCode : null,
218
- message: result.error.message
219
- });
269
+ if (result.error.code === "paymentFailed") {
270
+ host.paymentFailed(result.error, {
271
+ declineCode: result.error.paymentFailed.declineCode,
272
+ message: result.error.message
273
+ });
274
+ }
220
275
  return;
221
276
  }
222
277
  host.succeeded(fromSession(result.session));
@@ -266,6 +321,7 @@ var StripeProvider = class {
266
321
  ui;
267
322
  stripe = null;
268
323
  mounted = false;
324
+ blocked = false;
269
325
  clientSecret = null;
270
326
  snapshot = {};
271
327
  async init() {
@@ -285,7 +341,17 @@ var StripeProvider = class {
285
341
  throw new CheckoutError("Already rendered \u2014 call destroy() before rendering again");
286
342
  }
287
343
  this.mounted = true;
288
- await this.ui.mount(this.stripe, container, this.host());
344
+ this.blocked = false;
345
+ const returned = await this.takeReturnedSession();
346
+ if (returned) {
347
+ this.reportComplete(returned);
348
+ return;
349
+ }
350
+ try {
351
+ await this.ui.mount(this.stripe, container, this.host());
352
+ } catch (err) {
353
+ if (!this.blocked) throw err;
354
+ }
289
355
  }
290
356
  async reset() {
291
357
  this.destroy();
@@ -309,18 +375,42 @@ var StripeProvider = class {
309
375
  provider: "stripe",
310
376
  paymentMethodType: this.snapshot.paymentMethodType
311
377
  }),
312
- renderFailed: (error, detail) => this.emitError("render", error, detail),
378
+ renderFailed: (error, detail) => {
379
+ if (!this.blocked) this.emitError("render", error, detail);
380
+ },
313
381
  paymentFailed: (error, detail) => this.emitError("payment", error, detail),
314
382
  succeeded: (outcome) => void this.emitComplete(outcome)
315
383
  };
316
384
  }
317
385
  async createSession() {
318
- const secret = await this.service.createCheckoutSession({
319
- ...this.config,
320
- returnUrl: this.config.returnUrl ?? window.location.href
321
- });
322
- this.clientSecret = secret;
323
- return secret;
386
+ try {
387
+ const secret = await this.service.createCheckoutSession({
388
+ ...this.config,
389
+ returnUrl: this.config.returnUrl || withReturnedSessionParam(window.location.href)
390
+ });
391
+ this.clientSecret = secret;
392
+ return secret;
393
+ } catch (err) {
394
+ if (err instanceof CheckoutBlockedError && this.callbacks.onBlocked && this.mounted) {
395
+ this.blocked = true;
396
+ this.ui.destroy();
397
+ this.callbacks.onBlocked({
398
+ provider: "stripe",
399
+ reason: err.reason,
400
+ message: err.buyerMessage
401
+ });
402
+ }
403
+ throw err;
404
+ }
405
+ }
406
+ async takeReturnedSession() {
407
+ const sessionId = this.config.returnUrl ? null : popSessionIdFromUrl();
408
+ if (!sessionId) {
409
+ return null;
410
+ }
411
+ const lookup = () => this.service.getSession(sessionId, this.config.accountId);
412
+ const session = await lookup().catch(lookup).catch(() => null);
413
+ return session?.status === "complete" ? definedOnly(session) : null;
324
414
  }
325
415
  // Format: cs_test_{sessionId}_secret_{random}
326
416
  sessionId() {
@@ -360,10 +450,13 @@ var StripeProvider = class {
360
450
  session = {};
361
451
  }
362
452
  }
453
+ this.reportComplete({ ...fallback, ...session });
454
+ }
455
+ reportComplete(session) {
363
456
  this.callbacks.onComplete?.({
364
457
  provider: "stripe",
365
458
  paymentMethodType: this.snapshot.paymentMethodType,
366
- ...toCompleteFields({ ...fallback, ...session })
459
+ ...toCompleteFields(session)
367
460
  });
368
461
  }
369
462
  emitError(phase, error, detail) {
package/dist/index.d.cts CHANGED
@@ -49,7 +49,7 @@ interface StripeConfig {
49
49
  customerId?: string;
50
50
  /** New customer details — required when customerId is not provided */
51
51
  customer?: StripeCustomer;
52
- /** URL to redirect to after payment completes. Defaults to window.location.href for embedded checkout. */
52
+ /** Where redirect-based methods return the buyer. A custom URL completes the session itself. */
53
53
  returnUrl?: string;
54
54
  /** When true, Stripe displays a promotional code input field in the embedded checkout */
55
55
  allowPromoCodes?: boolean;
@@ -61,6 +61,8 @@ interface StripeConfig {
61
61
  trial?: StripeCheckoutTrial;
62
62
  /** When true, treats this as a subscription upgrade — requires customerId to be set */
63
63
  upgrade?: boolean;
64
+ /** Enables the backend's one-subscription-per-email limit. */
65
+ isSubscription?: boolean;
64
66
  /** When true, surfaces Apple Pay / Google Pay as the primary payment option (Wallet-First mode) */
65
67
  walletFirst?: boolean;
66
68
  /** Which checkout UI Stripe renders. Defaults to `embedded_page`. */
@@ -94,6 +96,12 @@ type ProviderConfigMap = {
94
96
  fastspring: FastSpringConfig;
95
97
  };
96
98
 
99
+ declare class CheckoutError extends Error {
100
+ constructor(message: string);
101
+ }
102
+ declare const CHECKOUT_BLOCK_REASONS: readonly ["duplicated_subscription", "duplicated_payment"];
103
+ type CheckoutBlockReason = (typeof CHECKOUT_BLOCK_REASONS)[number];
104
+
97
105
  interface CheckoutCompleteEvent {
98
106
  provider: ProviderName;
99
107
  paymentMethodType?: PaymentMethodType;
@@ -119,6 +127,12 @@ interface CheckoutErrorEvent {
119
127
  message?: string;
120
128
  error: unknown;
121
129
  }
130
+ /** The backend refused to open checkout for a repeat purchase. */
131
+ interface CheckoutBlockedEvent {
132
+ provider: ProviderName;
133
+ reason: CheckoutBlockReason;
134
+ message?: string;
135
+ }
122
136
  interface CheckoutCancelEvent {
123
137
  provider: ProviderName;
124
138
  paymentMethodType?: PaymentMethodType;
@@ -153,6 +167,7 @@ interface CheckoutCallbacks {
153
167
  onReady?: (event: CheckoutReadyEvent) => void;
154
168
  onConfirm?: (event: CheckoutConfirmEvent) => void;
155
169
  onChange?: (event: CheckoutChangeEvent) => void;
170
+ onBlocked?: (event: CheckoutBlockedEvent) => void;
156
171
  }
157
172
 
158
173
  interface RenderOptions {
@@ -179,13 +194,9 @@ type InitCheckoutConfig<T extends ProviderName> = {
179
194
  } & CheckoutCallbacks;
180
195
  declare function initCheckout<T extends ProviderName>(options: InitCheckoutConfig<T>): Promise<CheckoutInstance>;
181
196
 
182
- declare class CheckoutError extends Error {
183
- constructor(message: string);
184
- }
185
-
186
197
  type CreateCheckoutSessionParams = Omit<StripeConfig, 'publishableKey' | 'returnUrl' | 'form'> & {
187
198
  returnUrl: string;
188
199
  };
189
200
  declare function buildStripeCheckoutSessionBody(params: CreateCheckoutSessionParams): Record<string, unknown>;
190
201
 
191
- export { type CheckoutCallbacks, type CheckoutCancelEvent, type CheckoutChangeEvent, type CheckoutCompleteEvent, type CheckoutConfirmEvent, CheckoutError, type CheckoutErrorEvent, type CheckoutErrorPhase, type CheckoutInstance, type CheckoutReadyEvent, type CreateCheckoutSessionParams, type FastSpringConfig, type PaymentMethodType, type PaymentProvider, type ProviderConfigMap, type ProviderName, type RenderOptions, type StripeCheckoutTrial, type StripeConfig, type StripeFormOptions, type StripeUiMode, buildStripeCheckoutSessionBody, initCheckout };
202
+ export { type CheckoutBlockReason, type CheckoutBlockedEvent, type CheckoutCallbacks, type CheckoutCancelEvent, type CheckoutChangeEvent, type CheckoutCompleteEvent, type CheckoutConfirmEvent, CheckoutError, type CheckoutErrorEvent, type CheckoutErrorPhase, type CheckoutInstance, type CheckoutReadyEvent, type CreateCheckoutSessionParams, type FastSpringConfig, type PaymentMethodType, type PaymentProvider, type ProviderConfigMap, type ProviderName, type RenderOptions, type StripeCheckoutTrial, type StripeConfig, type StripeFormOptions, type StripeUiMode, buildStripeCheckoutSessionBody, initCheckout };
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ interface StripeConfig {
49
49
  customerId?: string;
50
50
  /** New customer details — required when customerId is not provided */
51
51
  customer?: StripeCustomer;
52
- /** URL to redirect to after payment completes. Defaults to window.location.href for embedded checkout. */
52
+ /** Where redirect-based methods return the buyer. A custom URL completes the session itself. */
53
53
  returnUrl?: string;
54
54
  /** When true, Stripe displays a promotional code input field in the embedded checkout */
55
55
  allowPromoCodes?: boolean;
@@ -61,6 +61,8 @@ interface StripeConfig {
61
61
  trial?: StripeCheckoutTrial;
62
62
  /** When true, treats this as a subscription upgrade — requires customerId to be set */
63
63
  upgrade?: boolean;
64
+ /** Enables the backend's one-subscription-per-email limit. */
65
+ isSubscription?: boolean;
64
66
  /** When true, surfaces Apple Pay / Google Pay as the primary payment option (Wallet-First mode) */
65
67
  walletFirst?: boolean;
66
68
  /** Which checkout UI Stripe renders. Defaults to `embedded_page`. */
@@ -94,6 +96,12 @@ type ProviderConfigMap = {
94
96
  fastspring: FastSpringConfig;
95
97
  };
96
98
 
99
+ declare class CheckoutError extends Error {
100
+ constructor(message: string);
101
+ }
102
+ declare const CHECKOUT_BLOCK_REASONS: readonly ["duplicated_subscription", "duplicated_payment"];
103
+ type CheckoutBlockReason = (typeof CHECKOUT_BLOCK_REASONS)[number];
104
+
97
105
  interface CheckoutCompleteEvent {
98
106
  provider: ProviderName;
99
107
  paymentMethodType?: PaymentMethodType;
@@ -119,6 +127,12 @@ interface CheckoutErrorEvent {
119
127
  message?: string;
120
128
  error: unknown;
121
129
  }
130
+ /** The backend refused to open checkout for a repeat purchase. */
131
+ interface CheckoutBlockedEvent {
132
+ provider: ProviderName;
133
+ reason: CheckoutBlockReason;
134
+ message?: string;
135
+ }
122
136
  interface CheckoutCancelEvent {
123
137
  provider: ProviderName;
124
138
  paymentMethodType?: PaymentMethodType;
@@ -153,6 +167,7 @@ interface CheckoutCallbacks {
153
167
  onReady?: (event: CheckoutReadyEvent) => void;
154
168
  onConfirm?: (event: CheckoutConfirmEvent) => void;
155
169
  onChange?: (event: CheckoutChangeEvent) => void;
170
+ onBlocked?: (event: CheckoutBlockedEvent) => void;
156
171
  }
157
172
 
158
173
  interface RenderOptions {
@@ -179,13 +194,9 @@ type InitCheckoutConfig<T extends ProviderName> = {
179
194
  } & CheckoutCallbacks;
180
195
  declare function initCheckout<T extends ProviderName>(options: InitCheckoutConfig<T>): Promise<CheckoutInstance>;
181
196
 
182
- declare class CheckoutError extends Error {
183
- constructor(message: string);
184
- }
185
-
186
197
  type CreateCheckoutSessionParams = Omit<StripeConfig, 'publishableKey' | 'returnUrl' | 'form'> & {
187
198
  returnUrl: string;
188
199
  };
189
200
  declare function buildStripeCheckoutSessionBody(params: CreateCheckoutSessionParams): Record<string, unknown>;
190
201
 
191
- export { type CheckoutCallbacks, type CheckoutCancelEvent, type CheckoutChangeEvent, type CheckoutCompleteEvent, type CheckoutConfirmEvent, CheckoutError, type CheckoutErrorEvent, type CheckoutErrorPhase, type CheckoutInstance, type CheckoutReadyEvent, type CreateCheckoutSessionParams, type FastSpringConfig, type PaymentMethodType, type PaymentProvider, type ProviderConfigMap, type ProviderName, type RenderOptions, type StripeCheckoutTrial, type StripeConfig, type StripeFormOptions, type StripeUiMode, buildStripeCheckoutSessionBody, initCheckout };
202
+ export { type CheckoutBlockReason, type CheckoutBlockedEvent, type CheckoutCallbacks, type CheckoutCancelEvent, type CheckoutChangeEvent, type CheckoutCompleteEvent, type CheckoutConfirmEvent, CheckoutError, type CheckoutErrorEvent, type CheckoutErrorPhase, type CheckoutInstance, type CheckoutReadyEvent, type CreateCheckoutSessionParams, type FastSpringConfig, type PaymentMethodType, type PaymentProvider, type ProviderConfigMap, type ProviderName, type RenderOptions, type StripeCheckoutTrial, type StripeConfig, type StripeFormOptions, type StripeUiMode, buildStripeCheckoutSessionBody, initCheckout };
package/dist/index.js CHANGED
@@ -5,10 +5,44 @@ var CheckoutError = class extends Error {
5
5
  this.name = "CheckoutError";
6
6
  }
7
7
  };
8
+ var CHECKOUT_BLOCK_REASONS = ["duplicated_subscription", "duplicated_payment"];
9
+ function isCheckoutBlockReason(reason) {
10
+ return CHECKOUT_BLOCK_REASONS.some((known) => known === reason);
11
+ }
12
+ var CheckoutBlockedError = class extends CheckoutError {
13
+ constructor(reason, buyerMessage) {
14
+ super(`Checkout blocked: ${reason}`);
15
+ this.reason = reason;
16
+ this.buyerMessage = buyerMessage;
17
+ this.name = "CheckoutBlockedError";
18
+ }
19
+ reason;
20
+ buyerMessage;
21
+ };
8
22
 
9
23
  // src/providers/stripe/index.ts
10
24
  import { loadStripe } from "@stripe/stripe-js";
11
25
 
26
+ // src/providers/stripe/return-url.ts
27
+ var RETURNED_SESSION_PARAM = "ff_checkout_session";
28
+ var SESSION_ID_PLACEHOLDER = "{CHECKOUT_SESSION_ID}";
29
+ var SESSION_ID = /^cs_\w+$/;
30
+ function withReturnedSessionParam(href) {
31
+ const url = new URL(href);
32
+ url.searchParams.set(RETURNED_SESSION_PARAM, SESSION_ID_PLACEHOLDER);
33
+ return url.href.replace(encodeURIComponent(SESSION_ID_PLACEHOLDER), SESSION_ID_PLACEHOLDER);
34
+ }
35
+ function popSessionIdFromUrl() {
36
+ const url = new URL(window.location.href);
37
+ const sessionId = url.searchParams.get(RETURNED_SESSION_PARAM);
38
+ if (sessionId === null) {
39
+ return null;
40
+ }
41
+ url.searchParams.delete(RETURNED_SESSION_PARAM);
42
+ window.history.replaceState(window.history.state, "", url.href);
43
+ return SESSION_ID.test(sessionId) ? sessionId : null;
44
+ }
45
+
12
46
  // src/providers/stripe/helpers.ts
13
47
  function buildStripeCheckoutSessionBody(params) {
14
48
  return {
@@ -34,6 +68,7 @@ function buildStripeCheckoutSessionBody(params) {
34
68
  }
35
69
  },
36
70
  ...params.upgrade && { upgrade: params.upgrade },
71
+ ...params.isSubscription !== void 0 && { is_subscription: params.isSubscription },
37
72
  ...params.walletFirst && { wallet_first: params.walletFirst },
38
73
  ...params.uiMode && { ui_mode: params.uiMode }
39
74
  };
@@ -91,6 +126,12 @@ var StripeCheckoutService = class extends HttpService {
91
126
  `/stripe/purchase${query}`,
92
127
  buildStripeCheckoutSessionBody(params)
93
128
  );
129
+ if (data.status === "failed") {
130
+ if (isCheckoutBlockReason(data.reason)) {
131
+ throw new CheckoutBlockedError(data.reason, data.message || void 0);
132
+ }
133
+ throw new CheckoutError(`Checkout session refused: ${data.reason}`);
134
+ }
94
135
  if (!data.client_secret) {
95
136
  throw new CheckoutError("Backend returned no client_secret");
96
137
  }
@@ -123,6 +164,7 @@ var StripeEmbeddedPageUi = class {
123
164
 
124
165
  // src/providers/stripe/ui/form.ts
125
166
  var CHECKOUT_FORM_BETA = "custom_checkout_payment_form_1";
167
+ var EXPRESS_CHECKOUT_SOURCE = "checkout-form-ece";
126
168
  var StripeFormUi = class {
127
169
  constructor(options = {}) {
128
170
  this.options = options;
@@ -178,17 +220,30 @@ var StripeFormUi = class {
178
220
  }
179
221
  return result.actions;
180
222
  }
223
+ async isFormValid(actions) {
224
+ try {
225
+ const validation = await actions.validateElements();
226
+ return validation.type !== "error";
227
+ } catch {
228
+ return true;
229
+ }
230
+ }
181
231
  async confirm(actions, event, host) {
182
- const express = event.source === "checkout-form-ece";
232
+ const express = event.source === EXPRESS_CHECKOUT_SOURCE;
183
233
  const paymentMethodType = express ? void 0 : event.paymentMethodType ?? void 0;
184
- host.confirmed({ paymentMethodType, express });
185
234
  try {
235
+ const attempted = express || await this.isFormValid(actions);
236
+ if (attempted) {
237
+ host.confirmed({ paymentMethodType, express });
238
+ }
186
239
  const result = await actions.confirm({ formConfirmEvent: event, redirect: "if_required" });
187
240
  if (result.type === "error") {
188
- host.paymentFailed(result.error, {
189
- declineCode: result.error.code === "paymentFailed" ? result.error.paymentFailed.declineCode : null,
190
- message: result.error.message
191
- });
241
+ if (result.error.code === "paymentFailed") {
242
+ host.paymentFailed(result.error, {
243
+ declineCode: result.error.paymentFailed.declineCode,
244
+ message: result.error.message
245
+ });
246
+ }
192
247
  return;
193
248
  }
194
249
  host.succeeded(fromSession(result.session));
@@ -238,6 +293,7 @@ var StripeProvider = class {
238
293
  ui;
239
294
  stripe = null;
240
295
  mounted = false;
296
+ blocked = false;
241
297
  clientSecret = null;
242
298
  snapshot = {};
243
299
  async init() {
@@ -257,7 +313,17 @@ var StripeProvider = class {
257
313
  throw new CheckoutError("Already rendered \u2014 call destroy() before rendering again");
258
314
  }
259
315
  this.mounted = true;
260
- await this.ui.mount(this.stripe, container, this.host());
316
+ this.blocked = false;
317
+ const returned = await this.takeReturnedSession();
318
+ if (returned) {
319
+ this.reportComplete(returned);
320
+ return;
321
+ }
322
+ try {
323
+ await this.ui.mount(this.stripe, container, this.host());
324
+ } catch (err) {
325
+ if (!this.blocked) throw err;
326
+ }
261
327
  }
262
328
  async reset() {
263
329
  this.destroy();
@@ -281,18 +347,42 @@ var StripeProvider = class {
281
347
  provider: "stripe",
282
348
  paymentMethodType: this.snapshot.paymentMethodType
283
349
  }),
284
- renderFailed: (error, detail) => this.emitError("render", error, detail),
350
+ renderFailed: (error, detail) => {
351
+ if (!this.blocked) this.emitError("render", error, detail);
352
+ },
285
353
  paymentFailed: (error, detail) => this.emitError("payment", error, detail),
286
354
  succeeded: (outcome) => void this.emitComplete(outcome)
287
355
  };
288
356
  }
289
357
  async createSession() {
290
- const secret = await this.service.createCheckoutSession({
291
- ...this.config,
292
- returnUrl: this.config.returnUrl ?? window.location.href
293
- });
294
- this.clientSecret = secret;
295
- return secret;
358
+ try {
359
+ const secret = await this.service.createCheckoutSession({
360
+ ...this.config,
361
+ returnUrl: this.config.returnUrl || withReturnedSessionParam(window.location.href)
362
+ });
363
+ this.clientSecret = secret;
364
+ return secret;
365
+ } catch (err) {
366
+ if (err instanceof CheckoutBlockedError && this.callbacks.onBlocked && this.mounted) {
367
+ this.blocked = true;
368
+ this.ui.destroy();
369
+ this.callbacks.onBlocked({
370
+ provider: "stripe",
371
+ reason: err.reason,
372
+ message: err.buyerMessage
373
+ });
374
+ }
375
+ throw err;
376
+ }
377
+ }
378
+ async takeReturnedSession() {
379
+ const sessionId = this.config.returnUrl ? null : popSessionIdFromUrl();
380
+ if (!sessionId) {
381
+ return null;
382
+ }
383
+ const lookup = () => this.service.getSession(sessionId, this.config.accountId);
384
+ const session = await lookup().catch(lookup).catch(() => null);
385
+ return session?.status === "complete" ? definedOnly(session) : null;
296
386
  }
297
387
  // Format: cs_test_{sessionId}_secret_{random}
298
388
  sessionId() {
@@ -332,10 +422,13 @@ var StripeProvider = class {
332
422
  session = {};
333
423
  }
334
424
  }
425
+ this.reportComplete({ ...fallback, ...session });
426
+ }
427
+ reportComplete(session) {
335
428
  this.callbacks.onComplete?.({
336
429
  provider: "stripe",
337
430
  paymentMethodType: this.snapshot.paymentMethodType,
338
- ...toCompleteFields({ ...fallback, ...session })
431
+ ...toCompleteFields(session)
339
432
  });
340
433
  }
341
434
  emitError(phase, error, detail) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelfox/payments-sdk",
3
- "version": "0.2.0-beta.1",
3
+ "version": "0.2.0-beta.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "dev": "tsup --watch"
22
22
  },
23
23
  "dependencies": {
24
- "@stripe/stripe-js": "^9.1.0"
24
+ "@stripe/stripe-js": "^9.14.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsup": "^8.4.0",