@flopay/js 1.2.1 → 1.2.5
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/README.md +24 -7
- package/dist/index.cjs +65 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -5
- package/dist/index.d.ts +31 -5
- package/dist/index.mjs +65 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -11
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,12 @@ declare class FloPay {
|
|
|
64
64
|
sessionId: string;
|
|
65
65
|
email: string;
|
|
66
66
|
returnUrl: string;
|
|
67
|
+
/**
|
|
68
|
+
* Session-bound checkout token. Forwarded as `x-checkout-session-token`
|
|
69
|
+
* on the internal `POST /v1/checkouts/payments/intents` call so post-#640
|
|
70
|
+
* backends don't reject the round-trip with a 401.
|
|
71
|
+
*/
|
|
72
|
+
nonce?: string;
|
|
67
73
|
}): Promise<ConfirmCardPaymentResult>;
|
|
68
74
|
/** Resume a PayPal payment after redirect return. Returns null if no PayPal params in URL. */
|
|
69
75
|
resumePayPalPayment(): Promise<ConfirmCardPaymentResult | null>;
|
|
@@ -148,6 +154,7 @@ declare class StripeAdapter implements PaymentProviderAdapter {
|
|
|
148
154
|
sessionId: string;
|
|
149
155
|
email: string;
|
|
150
156
|
returnUrl: string;
|
|
157
|
+
nonce?: string;
|
|
151
158
|
}): Promise<ConfirmCardPaymentResult>;
|
|
152
159
|
resumePayPalPayment(): Promise<ConfirmCardPaymentResult | null>;
|
|
153
160
|
getRawProvider(): unknown;
|
|
@@ -281,11 +288,9 @@ declare class PaymentAPI {
|
|
|
281
288
|
*
|
|
282
289
|
* `nonce` is the session-bound checkout token returned when the session
|
|
283
290
|
* was created. When supplied it is sent as the `x-checkout-session-token`
|
|
284
|
-
* header
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* session reads so the UUID alone is no longer sufficient to fetch a
|
|
288
|
-
* session. Backends that don't yet enforce it ignore the extra header.
|
|
291
|
+
* header that post-#640 backends match against `checkout_session.nonce`
|
|
292
|
+
* before returning the row — the UUID alone is no longer sufficient.
|
|
293
|
+
* Backends that don't yet enforce it ignore the extra header.
|
|
289
294
|
*/
|
|
290
295
|
getCheckoutSession(checkoutSessionId: string, nonce?: string): Promise<BillingResponse<RawCheckoutSession>>;
|
|
291
296
|
/**
|
|
@@ -329,6 +334,12 @@ declare class PaymentAPI {
|
|
|
329
334
|
* The backend will either succeed, return `type: '3ds_required'`
|
|
330
335
|
* (with a `threeDSecureToken`), or return `type: 'paypal_redirect_required'`.
|
|
331
336
|
*
|
|
337
|
+
* Hits the session-scoped route `POST /v1/checkouts/sessions/:id/process`
|
|
338
|
+
* and forwards `data.nonce` as `x-checkout-session-token`. Backend
|
|
339
|
+
* `TeamFloPay/backend#640` rejects callers without a matching nonce with a
|
|
340
|
+
* 401 — this method throws synchronously when `data.nonce` is missing so the
|
|
341
|
+
* problem surfaces before the network round trip.
|
|
342
|
+
*
|
|
332
343
|
* @param userId Vestigial — backend's GatewayInterceptor routes via session,
|
|
333
344
|
* not headers, so this value is no longer sent on the wire. Kept in the
|
|
334
345
|
* signature for back-compat with existing callers; will be removed in a
|
|
@@ -342,16 +353,26 @@ declare class PaymentAPI {
|
|
|
342
353
|
*
|
|
343
354
|
* Used by the Stripe flow to create a server-side PaymentIntent
|
|
344
355
|
* with the client's payment method attached.
|
|
356
|
+
*
|
|
357
|
+
* Forwards `options.nonce` as `x-checkout-session-token` when supplied — the
|
|
358
|
+
* session-bound checkout token returned by session creation. Post-#640
|
|
359
|
+
* backends reject this call with a 401 when the header is missing or does
|
|
360
|
+
* not match the session's stored nonce.
|
|
345
361
|
*/
|
|
346
362
|
createPaymentIntent(sessionId: string, email: string, paymentMethodType: string, options?: {
|
|
347
363
|
signal?: AbortSignal;
|
|
348
364
|
isPaypal?: string;
|
|
365
|
+
nonce?: string;
|
|
349
366
|
}): Promise<Response>;
|
|
350
367
|
/**
|
|
351
368
|
* Create a SetupIntent for saving payment methods.
|
|
369
|
+
*
|
|
370
|
+
* Forwards `options.nonce` as `x-checkout-session-token` when supplied —
|
|
371
|
+
* required by post-#640 backends, ignored by earlier versions.
|
|
352
372
|
*/
|
|
353
373
|
createSetupIntent(sessionId: string, email: string, paymentMethodType: 'card' | 'google_pay' | 'apple_pay' | 'paypal_express_checkout', options?: {
|
|
354
374
|
signal?: AbortSignal;
|
|
375
|
+
nonce?: string;
|
|
355
376
|
}): Promise<Response>;
|
|
356
377
|
/**
|
|
357
378
|
* Fetch user's prior payments by email.
|
|
@@ -380,6 +401,11 @@ declare class PaymentAPI {
|
|
|
380
401
|
waitForCheckoutSessionCompletion(checkoutSessionId: string, options?: {
|
|
381
402
|
initialDelayMs?: number;
|
|
382
403
|
timeoutMs?: number;
|
|
404
|
+
/**
|
|
405
|
+
* Session-bound checkout token; forwarded on the poll's
|
|
406
|
+
* `GET /v1/checkouts/sessions/:id`. Required by post-#640 backends.
|
|
407
|
+
*/
|
|
408
|
+
nonce?: string;
|
|
383
409
|
}): Promise<NormalizedCheckoutSession>;
|
|
384
410
|
/** Normalize a raw session into a provider-agnostic shape. */
|
|
385
411
|
private normalizeRawSession;
|
package/dist/index.mjs
CHANGED
|
@@ -63,7 +63,16 @@ var StripeAdapter = class {
|
|
|
63
63
|
if (typeof window === "undefined") {
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
let loadStripe;
|
|
67
|
+
try {
|
|
68
|
+
({ loadStripe } = await import("@stripe/stripe-js"));
|
|
69
|
+
} catch {
|
|
70
|
+
throw new FloPayError(
|
|
71
|
+
"Failed to load @stripe/stripe-js. Reinstall @flopay/js.",
|
|
72
|
+
"api_error",
|
|
73
|
+
{ code: "StripeJsLoadFailed" }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
67
76
|
const stripe = await loadStripe(config.publishableKey, {
|
|
68
77
|
locale: config.locale ?? "auto"
|
|
69
78
|
});
|
|
@@ -318,9 +327,11 @@ var StripeAdapter = class {
|
|
|
318
327
|
};
|
|
319
328
|
}
|
|
320
329
|
}
|
|
330
|
+
const intentHeaders = { "Content-Type": "application/json" };
|
|
331
|
+
if (params.nonce) intentHeaders["x-checkout-session-token"] = params.nonce;
|
|
321
332
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
322
333
|
method: "POST",
|
|
323
|
-
headers:
|
|
334
|
+
headers: intentHeaders,
|
|
324
335
|
body: JSON.stringify({
|
|
325
336
|
sessionId: params.sessionId,
|
|
326
337
|
email: params.email,
|
|
@@ -605,15 +616,13 @@ var PaymentAPI = class {
|
|
|
605
616
|
*
|
|
606
617
|
* `nonce` is the session-bound checkout token returned when the session
|
|
607
618
|
* was created. When supplied it is sent as the `x-checkout-session-token`
|
|
608
|
-
* header
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
* session reads so the UUID alone is no longer sufficient to fetch a
|
|
612
|
-
* session. Backends that don't yet enforce it ignore the extra header.
|
|
619
|
+
* header that post-#640 backends match against `checkout_session.nonce`
|
|
620
|
+
* before returning the row — the UUID alone is no longer sufficient.
|
|
621
|
+
* Backends that don't yet enforce it ignore the extra header.
|
|
613
622
|
*/
|
|
614
623
|
async getCheckoutSession(checkoutSessionId, nonce) {
|
|
615
624
|
const response = await fetchWithNetworkRetry(
|
|
616
|
-
`${this.baseUrl}/v1/checkouts/sessions/${checkoutSessionId}`,
|
|
625
|
+
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(checkoutSessionId)}`,
|
|
617
626
|
nonce ? { headers: { "x-checkout-session-token": nonce } } : void 0
|
|
618
627
|
);
|
|
619
628
|
if (!response.ok) {
|
|
@@ -668,34 +677,58 @@ var PaymentAPI = class {
|
|
|
668
677
|
* The backend will either succeed, return `type: '3ds_required'`
|
|
669
678
|
* (with a `threeDSecureToken`), or return `type: 'paypal_redirect_required'`.
|
|
670
679
|
*
|
|
680
|
+
* Hits the session-scoped route `POST /v1/checkouts/sessions/:id/process`
|
|
681
|
+
* and forwards `data.nonce` as `x-checkout-session-token`. Backend
|
|
682
|
+
* `TeamFloPay/backend#640` rejects callers without a matching nonce with a
|
|
683
|
+
* 401 — this method throws synchronously when `data.nonce` is missing so the
|
|
684
|
+
* problem surfaces before the network round trip.
|
|
685
|
+
*
|
|
671
686
|
* @param userId Vestigial — backend's GatewayInterceptor routes via session,
|
|
672
687
|
* not headers, so this value is no longer sent on the wire. Kept in the
|
|
673
688
|
* signature for back-compat with existing callers; will be removed in a
|
|
674
689
|
* future major version.
|
|
675
690
|
*/
|
|
676
691
|
async processPayment(_userId, data, options) {
|
|
692
|
+
if (!data.nonce) {
|
|
693
|
+
throw new FloPayError3(
|
|
694
|
+
"processPayment requires `nonce` \u2014 pass the value returned from session creation.",
|
|
695
|
+
"validation_error",
|
|
696
|
+
{ code: "MissingCheckoutSessionToken", param: "nonce" }
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
const { nonce, ...processBody } = data;
|
|
677
700
|
const response = await fetch(
|
|
678
|
-
`${this.baseUrl}/v1/checkouts/sessions/process`,
|
|
701
|
+
`${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(data.sessionId)}/process`,
|
|
679
702
|
{
|
|
680
703
|
method: "POST",
|
|
681
|
-
headers: {
|
|
682
|
-
|
|
704
|
+
headers: {
|
|
705
|
+
"Content-Type": "application/json",
|
|
706
|
+
"x-checkout-session-token": nonce
|
|
707
|
+
},
|
|
708
|
+
body: JSON.stringify(processBody)
|
|
683
709
|
}
|
|
684
710
|
);
|
|
685
|
-
return this.resolveProcessResponse(response, data.sessionId, options);
|
|
711
|
+
return this.resolveProcessResponse(response, data.sessionId, { ...options, nonce });
|
|
686
712
|
}
|
|
687
713
|
/**
|
|
688
714
|
* Create a PaymentIntent on the backend.
|
|
689
715
|
*
|
|
690
716
|
* Used by the Stripe flow to create a server-side PaymentIntent
|
|
691
717
|
* with the client's payment method attached.
|
|
718
|
+
*
|
|
719
|
+
* Forwards `options.nonce` as `x-checkout-session-token` when supplied — the
|
|
720
|
+
* session-bound checkout token returned by session creation. Post-#640
|
|
721
|
+
* backends reject this call with a 401 when the header is missing or does
|
|
722
|
+
* not match the session's stored nonce.
|
|
692
723
|
*/
|
|
693
724
|
async createPaymentIntent(sessionId, email, paymentMethodType, options) {
|
|
725
|
+
const headers = { "Content-Type": "application/json" };
|
|
726
|
+
if (options?.nonce) headers["x-checkout-session-token"] = options.nonce;
|
|
694
727
|
return fetch(
|
|
695
728
|
`${this.baseUrl}/v1/checkouts/payments/intents`,
|
|
696
729
|
{
|
|
697
730
|
method: "POST",
|
|
698
|
-
headers
|
|
731
|
+
headers,
|
|
699
732
|
body: JSON.stringify({
|
|
700
733
|
sessionId,
|
|
701
734
|
email,
|
|
@@ -708,13 +741,18 @@ var PaymentAPI = class {
|
|
|
708
741
|
}
|
|
709
742
|
/**
|
|
710
743
|
* Create a SetupIntent for saving payment methods.
|
|
744
|
+
*
|
|
745
|
+
* Forwards `options.nonce` as `x-checkout-session-token` when supplied —
|
|
746
|
+
* required by post-#640 backends, ignored by earlier versions.
|
|
711
747
|
*/
|
|
712
748
|
async createSetupIntent(sessionId, email, paymentMethodType, options) {
|
|
749
|
+
const headers = { "Content-Type": "application/json" };
|
|
750
|
+
if (options?.nonce) headers["x-checkout-session-token"] = options.nonce;
|
|
713
751
|
return fetch(
|
|
714
752
|
`${this.baseUrl}/v1/checkouts/payments/setup-intents`,
|
|
715
753
|
{
|
|
716
754
|
method: "POST",
|
|
717
|
-
headers
|
|
755
|
+
headers,
|
|
718
756
|
body: JSON.stringify({ sessionId, email, paymentMethodType }),
|
|
719
757
|
signal: options?.signal
|
|
720
758
|
}
|
|
@@ -856,7 +894,7 @@ var PaymentAPI = class {
|
|
|
856
894
|
throw createCheckoutProcessingTimeoutError();
|
|
857
895
|
}
|
|
858
896
|
}
|
|
859
|
-
const session = await this.getUnifiedCheckoutSession(checkoutSessionId);
|
|
897
|
+
const session = await this.getUnifiedCheckoutSession(checkoutSessionId, options?.nonce);
|
|
860
898
|
const status = session.data.session?.status;
|
|
861
899
|
if (status === "complete" || status === "expired") {
|
|
862
900
|
return session;
|
|
@@ -977,7 +1015,8 @@ var PaymentAPI = class {
|
|
|
977
1015
|
const pending = this.toCheckoutProcessingPending(payload, response, checkoutSessionId);
|
|
978
1016
|
const session = await this.waitForCheckoutSessionCompletion(pending.sessionId, {
|
|
979
1017
|
initialDelayMs: pending.retryAfterMs,
|
|
980
|
-
timeoutMs: options?.pollTimeoutMs
|
|
1018
|
+
timeoutMs: options?.pollTimeoutMs,
|
|
1019
|
+
nonce: options?.nonce
|
|
981
1020
|
});
|
|
982
1021
|
if (session.data.session?.status === "complete") {
|
|
983
1022
|
return new Response(null, { status: 204, statusText: "No Content" });
|
|
@@ -1321,9 +1360,17 @@ async function createCheckoutSession(options) {
|
|
|
1321
1360
|
}
|
|
1322
1361
|
if (status === 201) {
|
|
1323
1362
|
const uuid = body?.data?.uuid;
|
|
1363
|
+
const nonce = body?.data?.nonce;
|
|
1324
1364
|
if (!uuid) {
|
|
1325
1365
|
throw new Error("Checkout session created but no UUID was returned by the billing API");
|
|
1326
1366
|
}
|
|
1367
|
+
if (!nonce) {
|
|
1368
|
+
throw new FloPayError6(
|
|
1369
|
+
"Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.",
|
|
1370
|
+
"api_error",
|
|
1371
|
+
{ code: "MissingCheckoutSessionToken" }
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1327
1374
|
if (wireProducts.length || sessionCurrency) {
|
|
1328
1375
|
cacheSessionDisplayData(uuid, {
|
|
1329
1376
|
currency: sessionCurrency,
|
|
@@ -1346,11 +1393,12 @@ async function createCheckoutSession(options) {
|
|
|
1346
1393
|
const checkoutData = JSON.stringify({ origin_url: cancelUrl });
|
|
1347
1394
|
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
|
1348
1395
|
document.cookie = `checkout_data=${encodeURIComponent(checkoutData)}; domain=.${domain}; path=/; max-age=3600; SameSite=Lax; Secure;`;
|
|
1396
|
+
document.cookie = `flopay_checkout_token=${encodeURIComponent(nonce)}; domain=.${domain}; path=/; max-age=3600; SameSite=Lax; Secure;`;
|
|
1349
1397
|
}
|
|
1350
1398
|
if (typeof window !== "undefined") {
|
|
1351
1399
|
window.location.href = redirectUrl.toString();
|
|
1352
1400
|
}
|
|
1353
|
-
return { status: 201, redirectUrl: redirectUrl.toString() };
|
|
1401
|
+
return { status: 201, redirectUrl: redirectUrl.toString(), nonce };
|
|
1354
1402
|
}
|
|
1355
1403
|
if (status === 204) {
|
|
1356
1404
|
if (typeof window !== "undefined") {
|