@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 +108 -15
- package/dist/index.d.cts +17 -6
- package/dist/index.d.ts +17 -6
- package/dist/index.js +108 -15
- package/package.json +2 -2
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 ===
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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(
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 ===
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
24
|
+
"@stripe/stripe-js": "^9.14.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"tsup": "^8.4.0",
|