@flopay/js 1.2.1 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -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 the same token the backend already validates on session-scoped
285
- * mutations (e.g. the payment-intent-decline endpoint). Sending it on the
286
- * GET is forward-compatible with TeamFloPay/backend#640, which tightens
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
@@ -318,9 +318,11 @@ var StripeAdapter = class {
318
318
  };
319
319
  }
320
320
  }
321
+ const intentHeaders = { "Content-Type": "application/json" };
322
+ if (params.nonce) intentHeaders["x-checkout-session-token"] = params.nonce;
321
323
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
322
324
  method: "POST",
323
- headers: { "Content-Type": "application/json" },
325
+ headers: intentHeaders,
324
326
  body: JSON.stringify({
325
327
  sessionId: params.sessionId,
326
328
  email: params.email,
@@ -605,15 +607,13 @@ var PaymentAPI = class {
605
607
  *
606
608
  * `nonce` is the session-bound checkout token returned when the session
607
609
  * was created. When supplied it is sent as the `x-checkout-session-token`
608
- * header the same token the backend already validates on session-scoped
609
- * mutations (e.g. the payment-intent-decline endpoint). Sending it on the
610
- * GET is forward-compatible with TeamFloPay/backend#640, which tightens
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.
610
+ * header that post-#640 backends match against `checkout_session.nonce`
611
+ * before returning the row the UUID alone is no longer sufficient.
612
+ * Backends that don't yet enforce it ignore the extra header.
613
613
  */
614
614
  async getCheckoutSession(checkoutSessionId, nonce) {
615
615
  const response = await fetchWithNetworkRetry(
616
- `${this.baseUrl}/v1/checkouts/sessions/${checkoutSessionId}`,
616
+ `${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(checkoutSessionId)}`,
617
617
  nonce ? { headers: { "x-checkout-session-token": nonce } } : void 0
618
618
  );
619
619
  if (!response.ok) {
@@ -668,34 +668,58 @@ var PaymentAPI = class {
668
668
  * The backend will either succeed, return `type: '3ds_required'`
669
669
  * (with a `threeDSecureToken`), or return `type: 'paypal_redirect_required'`.
670
670
  *
671
+ * Hits the session-scoped route `POST /v1/checkouts/sessions/:id/process`
672
+ * and forwards `data.nonce` as `x-checkout-session-token`. Backend
673
+ * `TeamFloPay/backend#640` rejects callers without a matching nonce with a
674
+ * 401 — this method throws synchronously when `data.nonce` is missing so the
675
+ * problem surfaces before the network round trip.
676
+ *
671
677
  * @param userId Vestigial — backend's GatewayInterceptor routes via session,
672
678
  * not headers, so this value is no longer sent on the wire. Kept in the
673
679
  * signature for back-compat with existing callers; will be removed in a
674
680
  * future major version.
675
681
  */
676
682
  async processPayment(_userId, data, options) {
683
+ if (!data.nonce) {
684
+ throw new FloPayError3(
685
+ "processPayment requires `nonce` \u2014 pass the value returned from session creation.",
686
+ "validation_error",
687
+ { code: "MissingCheckoutSessionToken", param: "nonce" }
688
+ );
689
+ }
690
+ const { nonce, ...processBody } = data;
677
691
  const response = await fetch(
678
- `${this.baseUrl}/v1/checkouts/sessions/process`,
692
+ `${this.baseUrl}/v1/checkouts/sessions/${encodeURIComponent(data.sessionId)}/process`,
679
693
  {
680
694
  method: "POST",
681
- headers: { "Content-Type": "application/json" },
682
- body: JSON.stringify(data)
695
+ headers: {
696
+ "Content-Type": "application/json",
697
+ "x-checkout-session-token": nonce
698
+ },
699
+ body: JSON.stringify(processBody)
683
700
  }
684
701
  );
685
- return this.resolveProcessResponse(response, data.sessionId, options);
702
+ return this.resolveProcessResponse(response, data.sessionId, { ...options, nonce });
686
703
  }
687
704
  /**
688
705
  * Create a PaymentIntent on the backend.
689
706
  *
690
707
  * Used by the Stripe flow to create a server-side PaymentIntent
691
708
  * with the client's payment method attached.
709
+ *
710
+ * Forwards `options.nonce` as `x-checkout-session-token` when supplied — the
711
+ * session-bound checkout token returned by session creation. Post-#640
712
+ * backends reject this call with a 401 when the header is missing or does
713
+ * not match the session's stored nonce.
692
714
  */
693
715
  async createPaymentIntent(sessionId, email, paymentMethodType, options) {
716
+ const headers = { "Content-Type": "application/json" };
717
+ if (options?.nonce) headers["x-checkout-session-token"] = options.nonce;
694
718
  return fetch(
695
719
  `${this.baseUrl}/v1/checkouts/payments/intents`,
696
720
  {
697
721
  method: "POST",
698
- headers: { "Content-Type": "application/json" },
722
+ headers,
699
723
  body: JSON.stringify({
700
724
  sessionId,
701
725
  email,
@@ -708,13 +732,18 @@ var PaymentAPI = class {
708
732
  }
709
733
  /**
710
734
  * Create a SetupIntent for saving payment methods.
735
+ *
736
+ * Forwards `options.nonce` as `x-checkout-session-token` when supplied —
737
+ * required by post-#640 backends, ignored by earlier versions.
711
738
  */
712
739
  async createSetupIntent(sessionId, email, paymentMethodType, options) {
740
+ const headers = { "Content-Type": "application/json" };
741
+ if (options?.nonce) headers["x-checkout-session-token"] = options.nonce;
713
742
  return fetch(
714
743
  `${this.baseUrl}/v1/checkouts/payments/setup-intents`,
715
744
  {
716
745
  method: "POST",
717
- headers: { "Content-Type": "application/json" },
746
+ headers,
718
747
  body: JSON.stringify({ sessionId, email, paymentMethodType }),
719
748
  signal: options?.signal
720
749
  }
@@ -856,7 +885,7 @@ var PaymentAPI = class {
856
885
  throw createCheckoutProcessingTimeoutError();
857
886
  }
858
887
  }
859
- const session = await this.getUnifiedCheckoutSession(checkoutSessionId);
888
+ const session = await this.getUnifiedCheckoutSession(checkoutSessionId, options?.nonce);
860
889
  const status = session.data.session?.status;
861
890
  if (status === "complete" || status === "expired") {
862
891
  return session;
@@ -977,7 +1006,8 @@ var PaymentAPI = class {
977
1006
  const pending = this.toCheckoutProcessingPending(payload, response, checkoutSessionId);
978
1007
  const session = await this.waitForCheckoutSessionCompletion(pending.sessionId, {
979
1008
  initialDelayMs: pending.retryAfterMs,
980
- timeoutMs: options?.pollTimeoutMs
1009
+ timeoutMs: options?.pollTimeoutMs,
1010
+ nonce: options?.nonce
981
1011
  });
982
1012
  if (session.data.session?.status === "complete") {
983
1013
  return new Response(null, { status: 204, statusText: "No Content" });
@@ -1321,9 +1351,17 @@ async function createCheckoutSession(options) {
1321
1351
  }
1322
1352
  if (status === 201) {
1323
1353
  const uuid = body?.data?.uuid;
1354
+ const nonce = body?.data?.nonce;
1324
1355
  if (!uuid) {
1325
1356
  throw new Error("Checkout session created but no UUID was returned by the billing API");
1326
1357
  }
1358
+ if (!nonce) {
1359
+ throw new FloPayError6(
1360
+ "Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.",
1361
+ "api_error",
1362
+ { code: "MissingCheckoutSessionToken" }
1363
+ );
1364
+ }
1327
1365
  if (wireProducts.length || sessionCurrency) {
1328
1366
  cacheSessionDisplayData(uuid, {
1329
1367
  currency: sessionCurrency,
@@ -1346,11 +1384,12 @@ async function createCheckoutSession(options) {
1346
1384
  const checkoutData = JSON.stringify({ origin_url: cancelUrl });
1347
1385
  const domain = window.location.hostname.split(".").slice(-2).join(".");
1348
1386
  document.cookie = `checkout_data=${encodeURIComponent(checkoutData)}; domain=.${domain}; path=/; max-age=3600; SameSite=Lax; Secure;`;
1387
+ document.cookie = `flopay_checkout_token=${encodeURIComponent(nonce)}; domain=.${domain}; path=/; max-age=3600; SameSite=Lax; Secure;`;
1349
1388
  }
1350
1389
  if (typeof window !== "undefined") {
1351
1390
  window.location.href = redirectUrl.toString();
1352
1391
  }
1353
- return { status: 201, redirectUrl: redirectUrl.toString() };
1392
+ return { status: 201, redirectUrl: redirectUrl.toString(), nonce };
1354
1393
  }
1355
1394
  if (status === 204) {
1356
1395
  if (typeof window !== "undefined") {