@flopay/react 0.5.13 → 0.5.14

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.cts CHANGED
@@ -579,8 +579,10 @@ declare function FloPayAutomaticPaymentButton({ sessionId, createSession, paymen
579
579
  interface InAppBrowserNoticeProps {
580
580
  /** Custom message override. Defaults to a generic, non-platform-specific copy. */
581
581
  message?: React.ReactNode;
582
- /** Custom button label. Defaults to "Open in browser" / "Link copied". */
582
+ /** Custom button label. Default depends on the detected environment:
583
+ * in-app browser → "Copy link"; other → "Open in browser". */
583
584
  openButtonLabel?: string;
585
+ /** Custom copied-state button label. Default depends on environment. */
584
586
  copiedButtonLabel?: string;
585
587
  /** Custom CSS overrides for the container, message, and button. */
586
588
  containerStyle?: React.CSSProperties;
@@ -596,16 +598,43 @@ interface InAppBrowserNoticeProps {
596
598
  * — typically because the buyer is in an in-app browser (Instagram, Facebook,
597
599
  * TikTok, etc.) or behind an ad-blocker. The card form remains fully
598
600
  * functional; this notice just informs the buyer about the unavailable
599
- * methods and offers a one-click attempt to relaunch in the device browser.
601
+ * methods and gives them a way to escape to a real browser.
600
602
  *
601
- * The "Open in browser" button is best-effort:
602
- * - Tries `window.open(url, '_blank')` first (works in some webviews).
603
- * - Falls back to copying the URL to clipboard so the buyer can paste it
604
- * into Safari/Chrome themselves.
603
+ * Action behavior is environment-dependent because `window.open` is a known
604
+ * liar inside in-app WebViews:
605
605
  *
606
- * No reliable cross-platform JS API exists to *force* an in-app webview to
607
- * defer to the system browser, so we don't pretend otherwise.
606
+ * - **Detected in-app browser** (Facebook/Instagram/TikTok/etc. via UA):
607
+ * skip `window.open` entirely. It returns a non-null window but the new
608
+ * page opens *inside the same WebView*, which defeats the entire point
609
+ * and makes us mis-report `opened: true` to `onOpenInBrowser`. Instead,
610
+ * copy the URL to clipboard and surface clear paste-in-Safari/Chrome
611
+ * messaging. There is no reliable cross-platform JS API to force a
612
+ * WebView to defer to the system browser; the buyer must use the host
613
+ * app's "..." / "⋮" menu.
614
+ * - **Other environments** (desktop with ad-blocker, unknown UA):
615
+ * `window.open` may genuinely succeed — try it, fall back to clipboard.
608
616
  */
609
617
  declare function InAppBrowserNotice({ message, openButtonLabel, copiedButtonLabel, containerStyle, buttonStyle, onOpenInBrowser, }: InAppBrowserNoticeProps): react_jsx_runtime.JSX.Element;
610
618
 
611
- export { AddressElement, CardCvcElement, CardElement, CardExpiryElement, CardNumberElement, CheckoutForm, type CheckoutFormProps, type CheckoutFormRef, type CheckoutState, type ElementComponentProps, FloPayAutomaticPaymentButton, type FloPayAutomaticPaymentButtonProps, type FloPayAutomaticPaymentSuccessEvent, FloPayCheckout, type FloPayCheckoutProps, FloPayProvider, type FloPayProviderProps, InAppBrowserNotice, type InAppBrowserNoticeProps, PayPalButton, type PayPalButtonProps, PaymentElement, SplitCardForm, type SplitCardFormProps, type SplitCardFormRef, useCheckout, useElements, useFloPay, usePayPalFloPay };
619
+ /**
620
+ * Heuristic detector for in-app browser / WebView environments where PayPal
621
+ * and digital wallets often fail to initialize.
622
+ *
623
+ * Pairs with the load-state classifier in `SplitCardForm`: this gives us a
624
+ * proactive signal (we know we're in a hostile environment before Stripe
625
+ * even tries), while load state covers the long tail (ad-blockers, new
626
+ * WebViews we haven't seen, account-misconfig surfacing as `unavailable`).
627
+ *
628
+ * Tradeoffs:
629
+ * - False positives possible. Some modern WebViews (iOS SFSafariViewController
630
+ * on recent iOS, Chrome Custom Tabs) handle PayPal fine but match these
631
+ * patterns. The notice is non-disruptive — card form keeps working — so we
632
+ * accept it.
633
+ * - False negatives possible. New social-app WebViews ship without us knowing.
634
+ * That's why we also keep the reactive load-state signal.
635
+ *
636
+ * SSR-safe: returns `false` when `navigator` is undefined.
637
+ */
638
+ declare function isInAppBrowser(userAgent?: string): boolean;
639
+
640
+ export { AddressElement, CardCvcElement, CardElement, CardExpiryElement, CardNumberElement, CheckoutForm, type CheckoutFormProps, type CheckoutFormRef, type CheckoutState, type ElementComponentProps, FloPayAutomaticPaymentButton, type FloPayAutomaticPaymentButtonProps, type FloPayAutomaticPaymentSuccessEvent, FloPayCheckout, type FloPayCheckoutProps, FloPayProvider, type FloPayProviderProps, InAppBrowserNotice, type InAppBrowserNoticeProps, PayPalButton, type PayPalButtonProps, PaymentElement, SplitCardForm, type SplitCardFormProps, type SplitCardFormRef, isInAppBrowser, useCheckout, useElements, useFloPay, usePayPalFloPay };
package/dist/index.d.ts CHANGED
@@ -579,8 +579,10 @@ declare function FloPayAutomaticPaymentButton({ sessionId, createSession, paymen
579
579
  interface InAppBrowserNoticeProps {
580
580
  /** Custom message override. Defaults to a generic, non-platform-specific copy. */
581
581
  message?: React.ReactNode;
582
- /** Custom button label. Defaults to "Open in browser" / "Link copied". */
582
+ /** Custom button label. Default depends on the detected environment:
583
+ * in-app browser → "Copy link"; other → "Open in browser". */
583
584
  openButtonLabel?: string;
585
+ /** Custom copied-state button label. Default depends on environment. */
584
586
  copiedButtonLabel?: string;
585
587
  /** Custom CSS overrides for the container, message, and button. */
586
588
  containerStyle?: React.CSSProperties;
@@ -596,16 +598,43 @@ interface InAppBrowserNoticeProps {
596
598
  * — typically because the buyer is in an in-app browser (Instagram, Facebook,
597
599
  * TikTok, etc.) or behind an ad-blocker. The card form remains fully
598
600
  * functional; this notice just informs the buyer about the unavailable
599
- * methods and offers a one-click attempt to relaunch in the device browser.
601
+ * methods and gives them a way to escape to a real browser.
600
602
  *
601
- * The "Open in browser" button is best-effort:
602
- * - Tries `window.open(url, '_blank')` first (works in some webviews).
603
- * - Falls back to copying the URL to clipboard so the buyer can paste it
604
- * into Safari/Chrome themselves.
603
+ * Action behavior is environment-dependent because `window.open` is a known
604
+ * liar inside in-app WebViews:
605
605
  *
606
- * No reliable cross-platform JS API exists to *force* an in-app webview to
607
- * defer to the system browser, so we don't pretend otherwise.
606
+ * - **Detected in-app browser** (Facebook/Instagram/TikTok/etc. via UA):
607
+ * skip `window.open` entirely. It returns a non-null window but the new
608
+ * page opens *inside the same WebView*, which defeats the entire point
609
+ * and makes us mis-report `opened: true` to `onOpenInBrowser`. Instead,
610
+ * copy the URL to clipboard and surface clear paste-in-Safari/Chrome
611
+ * messaging. There is no reliable cross-platform JS API to force a
612
+ * WebView to defer to the system browser; the buyer must use the host
613
+ * app's "..." / "⋮" menu.
614
+ * - **Other environments** (desktop with ad-blocker, unknown UA):
615
+ * `window.open` may genuinely succeed — try it, fall back to clipboard.
608
616
  */
609
617
  declare function InAppBrowserNotice({ message, openButtonLabel, copiedButtonLabel, containerStyle, buttonStyle, onOpenInBrowser, }: InAppBrowserNoticeProps): react_jsx_runtime.JSX.Element;
610
618
 
611
- export { AddressElement, CardCvcElement, CardElement, CardExpiryElement, CardNumberElement, CheckoutForm, type CheckoutFormProps, type CheckoutFormRef, type CheckoutState, type ElementComponentProps, FloPayAutomaticPaymentButton, type FloPayAutomaticPaymentButtonProps, type FloPayAutomaticPaymentSuccessEvent, FloPayCheckout, type FloPayCheckoutProps, FloPayProvider, type FloPayProviderProps, InAppBrowserNotice, type InAppBrowserNoticeProps, PayPalButton, type PayPalButtonProps, PaymentElement, SplitCardForm, type SplitCardFormProps, type SplitCardFormRef, useCheckout, useElements, useFloPay, usePayPalFloPay };
619
+ /**
620
+ * Heuristic detector for in-app browser / WebView environments where PayPal
621
+ * and digital wallets often fail to initialize.
622
+ *
623
+ * Pairs with the load-state classifier in `SplitCardForm`: this gives us a
624
+ * proactive signal (we know we're in a hostile environment before Stripe
625
+ * even tries), while load state covers the long tail (ad-blockers, new
626
+ * WebViews we haven't seen, account-misconfig surfacing as `unavailable`).
627
+ *
628
+ * Tradeoffs:
629
+ * - False positives possible. Some modern WebViews (iOS SFSafariViewController
630
+ * on recent iOS, Chrome Custom Tabs) handle PayPal fine but match these
631
+ * patterns. The notice is non-disruptive — card form keeps working — so we
632
+ * accept it.
633
+ * - False negatives possible. New social-app WebViews ship without us knowing.
634
+ * That's why we also keep the reactive load-state signal.
635
+ *
636
+ * SSR-safe: returns `false` when `navigator` is undefined.
637
+ */
638
+ declare function isInAppBrowser(userAgent?: string): boolean;
639
+
640
+ export { AddressElement, CardCvcElement, CardElement, CardExpiryElement, CardNumberElement, CheckoutForm, type CheckoutFormProps, type CheckoutFormRef, type CheckoutState, type ElementComponentProps, FloPayAutomaticPaymentButton, type FloPayAutomaticPaymentButtonProps, type FloPayAutomaticPaymentSuccessEvent, FloPayCheckout, type FloPayCheckoutProps, FloPayProvider, type FloPayProviderProps, InAppBrowserNotice, type InAppBrowserNoticeProps, PayPalButton, type PayPalButtonProps, PaymentElement, SplitCardForm, type SplitCardFormProps, type SplitCardFormRef, isInAppBrowser, useCheckout, useElements, useFloPay, usePayPalFloPay };
package/dist/index.mjs CHANGED
@@ -100,7 +100,7 @@ function FloPayProvider({
100
100
  }
101
101
 
102
102
  // src/flopay-checkout.tsx
103
- import React7, { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
103
+ import React7, { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef3, useState as useState4 } from "react";
104
104
  import { PaymentAPI as PaymentAPI2 } from "@flopay/js";
105
105
  import { SDK_VERSION, FloPayError as FloPayError4, resolveBillingApiUrl as resolveBillingApiUrl3, buildCheckoutDisplayData, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme2 } from "@flopay/shared";
106
106
 
@@ -248,7 +248,7 @@ import {
248
248
  isAVSFieldVisible,
249
249
  getStateFromPostalCode
250
250
  } from "@flopay/shared";
251
- import { forwardRef, useCallback as useCallback2, useContext as useContext3, useEffect as useEffect3, useImperativeHandle, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
251
+ import { forwardRef, useCallback as useCallback2, useContext as useContext3, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
252
252
 
253
253
  // src/hooks.ts
254
254
  import { useContext as useContext2 } from "react";
@@ -622,27 +622,54 @@ function wasSessionRecentlyCompleted(sessionId) {
622
622
  }
623
623
 
624
624
  // src/in-app-browser-notice.tsx
625
- import { useCallback, useState as useState2 } from "react";
625
+ import { useCallback, useEffect as useEffect3, useState as useState2 } from "react";
626
+
627
+ // src/in-app-browser.ts
628
+ function isInAppBrowser(userAgent) {
629
+ const ua = userAgent ?? (typeof navigator !== "undefined" ? navigator.userAgent : "");
630
+ if (!ua) return false;
631
+ if (/FBAN|FBAV|FB_IAB|FBIOS|Instagram|musical_ly|BytedanceWebview|TikTok/i.test(ua)) {
632
+ return true;
633
+ }
634
+ if (/Twitter|Snapchat|Pinterest|LinkedInApp|Line\/|MicroMessenger|GSA\//i.test(ua)) {
635
+ return true;
636
+ }
637
+ if (/Android.*;\s?wv\)/.test(ua)) return true;
638
+ if (/(iPhone|iPad|iPod).*AppleWebKit(?!.*Safari)/.test(ua)) return true;
639
+ return false;
640
+ }
641
+
642
+ // src/in-app-browser-notice.tsx
626
643
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
627
644
  var DEFAULT_MESSAGE = "PayPal and some digital wallets aren't supported in this browser. Open this page in your device's browser to use them.";
628
645
  function InAppBrowserNotice({
629
646
  message,
630
- openButtonLabel = "Open in browser",
631
- copiedButtonLabel = "Link copied \u2014 paste in your browser",
647
+ openButtonLabel,
648
+ copiedButtonLabel,
632
649
  containerStyle,
633
650
  buttonStyle,
634
651
  onOpenInBrowser
635
652
  }) {
636
653
  const [copied, setCopied] = useState2(false);
654
+ const [inAppBrowser, setInAppBrowser] = useState2(false);
655
+ useEffect3(() => {
656
+ setInAppBrowser(isInAppBrowser());
657
+ }, []);
658
+ const defaultOpenLabel = inAppBrowser ? "Copy link" : "Open in browser";
659
+ const defaultCopiedLabel = inAppBrowser ? "Link copied \u2014 open Safari/Chrome and paste" : "Link copied \u2014 paste in your browser";
660
+ const effectiveOpenLabel = openButtonLabel ?? defaultOpenLabel;
661
+ const effectiveCopiedLabel = copiedButtonLabel ?? defaultCopiedLabel;
637
662
  const handleClick = useCallback(async () => {
638
663
  if (typeof window === "undefined") return;
639
664
  const url = window.location.href;
640
665
  let opened = false;
641
- try {
642
- const win = window.open(url, "_blank");
643
- opened = !!win;
644
- } catch {
645
- opened = false;
666
+ if (!inAppBrowser) {
667
+ try {
668
+ const win = window.open(url, "_blank");
669
+ opened = !!win;
670
+ } catch {
671
+ opened = false;
672
+ }
646
673
  }
647
674
  let didCopy = false;
648
675
  if (!opened) {
@@ -657,13 +684,14 @@ function InAppBrowserNotice({
657
684
  }
658
685
  }
659
686
  onOpenInBrowser?.({ opened, copied: didCopy });
660
- }, [onOpenInBrowser]);
687
+ }, [inAppBrowser, onOpenInBrowser]);
661
688
  return /* @__PURE__ */ jsxs3(
662
689
  "div",
663
690
  {
664
691
  role: "status",
665
692
  "data-testid": "flopay-in-app-browser-notice",
666
693
  style: {
694
+ marginTop: "0.75rem",
667
695
  padding: "0.75rem 0.875rem",
668
696
  background: "#FFFBEB",
669
697
  border: "1px solid #FDE68A",
@@ -695,7 +723,7 @@ function InAppBrowserNotice({
695
723
  cursor: "pointer",
696
724
  ...buttonStyle
697
725
  },
698
- children: copied ? copiedButtonLabel : openButtonLabel
726
+ children: copied ? effectiveCopiedLabel : effectiveOpenLabel
699
727
  }
700
728
  )
701
729
  ]
@@ -824,17 +852,17 @@ function PayPalButtonInner({
824
852
  const elements = useStripeElements();
825
853
  const [loadState, setLoadState] = useState3("loading");
826
854
  const onLoadStateChangeRef = useRef2(onLoadStateChange);
827
- useEffect3(() => {
855
+ useEffect4(() => {
828
856
  onLoadStateChangeRef.current = onLoadStateChange;
829
857
  }, [onLoadStateChange]);
830
- useEffect3(() => {
858
+ useEffect4(() => {
831
859
  onLoadStateChangeRef.current?.(loadState);
832
860
  }, [loadState]);
833
861
  const [submitting, setSubmitting] = useState3(false);
834
862
  const paypalResumeAttempted = useRef2(false);
835
863
  const beforeClickRef = useRef2(null);
836
864
  const baseUrl = billingApiUrl.replace(/\/+$/, "");
837
- useEffect3(() => {
865
+ useEffect4(() => {
838
866
  if (!stripe || paypalResumeAttempted.current) return;
839
867
  const params = new URLSearchParams(window.location.search);
840
868
  const paymentIntentId = params.get("payment_intent");
@@ -1037,10 +1065,10 @@ function WalletButtonInner({
1037
1065
  const elements = useStripeElements();
1038
1066
  const [loadState, setLoadState] = useState3("loading");
1039
1067
  const onLoadStateChangeRef = useRef2(onLoadStateChange);
1040
- useEffect3(() => {
1068
+ useEffect4(() => {
1041
1069
  onLoadStateChangeRef.current = onLoadStateChange;
1042
1070
  }, [onLoadStateChange]);
1043
- useEffect3(() => {
1071
+ useEffect4(() => {
1044
1072
  onLoadStateChangeRef.current?.(loadState);
1045
1073
  }, [loadState]);
1046
1074
  const [submitting, setSubmitting] = useState3(false);
@@ -1264,7 +1292,7 @@ function SplitCardFormInner({
1264
1292
  setViewState("collapsing");
1265
1293
  setTimeout(() => setViewState("buttons"), TRANSITION_MS);
1266
1294
  }, []);
1267
- useEffect3(() => {
1295
+ useEffect4(() => {
1268
1296
  if (layout === "buttons" && initialCardOpen) {
1269
1297
  setViewState("card");
1270
1298
  }
@@ -1340,11 +1368,15 @@ function SplitCardFormInner({
1340
1368
  const showWallets = showApplePay || showGooglePay;
1341
1369
  const [paypalLoadState, setPaypalLoadState] = useState3("loading");
1342
1370
  const [walletLoadState, setWalletLoadState] = useState3("loading");
1371
+ const [inAppBrowserDetected, setInAppBrowserDetected] = useState3(false);
1372
+ useEffect4(() => {
1373
+ setInAppBrowserDetected(isInAppBrowser());
1374
+ }, []);
1343
1375
  const expectedPayPal = showPayPal && !!paypalStripeInstance;
1344
1376
  const expectedWallets = showWallets && !!stripeInstance;
1345
- const paypalLoadFailed = expectedPayPal && paypalLoadState === "load_error";
1377
+ const paypalLoadFailed = expectedPayPal && (paypalLoadState === "load_error" || paypalLoadState === "unavailable");
1346
1378
  const walletsLoadFailed = expectedWallets && walletLoadState === "load_error";
1347
- const showInAppBrowserNotice = (expectedPayPal || expectedWallets) && (!expectedPayPal || paypalLoadFailed) && (!expectedWallets || walletsLoadFailed) && (paypalLoadFailed || walletsLoadFailed);
1379
+ const showInAppBrowserNotice = (expectedPayPal || expectedWallets) && (inAppBrowserDetected || paypalLoadFailed || walletsLoadFailed);
1348
1380
  const handleNameChange = useCallback2((value) => {
1349
1381
  setFullName(value);
1350
1382
  onFullNameChange?.(value);
@@ -1632,7 +1664,7 @@ function SplitCardFormInner({
1632
1664
  }
1633
1665
  }
1634
1666
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
1635
- useEffect3(() => {
1667
+ useEffect4(() => {
1636
1668
  if (typeof window === "undefined") return;
1637
1669
  const stored = localStorage.getItem(WALLET_RESUME_KEY);
1638
1670
  if (!stored) return;
@@ -3031,7 +3063,7 @@ function FloPayCheckout({
3031
3063
  onDeclineRef.current = onDecline;
3032
3064
  const onSessionCompletedRef = useRef3(onSessionCompleted);
3033
3065
  onSessionCompletedRef.current = onSessionCompleted;
3034
- useEffect4(() => {
3066
+ useEffect5(() => {
3035
3067
  console.info("[FloPay] Checkout initialized", {
3036
3068
  sdk_version: SDK_VERSION,
3037
3069
  checkout_type: checkoutType,
@@ -3059,11 +3091,11 @@ function FloPayCheckout({
3059
3091
  } : void 0,
3060
3092
  [effectiveCreateSessionBase, effectiveCreateSessionMode]
3061
3093
  );
3062
- useEffect4(() => {
3094
+ useEffect5(() => {
3063
3095
  setCreateSessionPatch(void 0);
3064
3096
  setCreateSessionPatchBaseHash(baseCreateSessionHash);
3065
3097
  }, [baseCreateSessionHash]);
3066
- useEffect4(() => {
3098
+ useEffect5(() => {
3067
3099
  setModeError(initialErrorMessage);
3068
3100
  }, [initialErrorMessage]);
3069
3101
  const emitDecline = useCallback3(
@@ -3179,7 +3211,7 @@ function FloPayCheckout({
3179
3211
  resolvedBillingUrl
3180
3212
  ]
3181
3213
  );
3182
- useEffect4(() => {
3214
+ useEffect5(() => {
3183
3215
  if (typeof window === "undefined" || paypalResumeAttempted.current) {
3184
3216
  return;
3185
3217
  }
@@ -3326,10 +3358,10 @@ function FloPayCheckout({
3326
3358
  );
3327
3359
  const createSessionParamsRef = useRef3(effectiveCreateSession);
3328
3360
  createSessionParamsRef.current = effectiveCreateSession;
3329
- useEffect4(() => {
3361
+ useEffect5(() => {
3330
3362
  setResolvedSessionId(sessionIdProp ?? "");
3331
3363
  }, [sessionIdProp]);
3332
- useEffect4(() => {
3364
+ useEffect5(() => {
3333
3365
  autoCheckoutAttempted.current = false;
3334
3366
  setModeError(initialErrorMessage);
3335
3367
  setModeOverlayError(null);
@@ -3456,7 +3488,7 @@ function FloPayCheckout({
3456
3488
  resolvedSessionId,
3457
3489
  session
3458
3490
  ]);
3459
- useEffect4(() => {
3491
+ useEffect5(() => {
3460
3492
  let cancelled = false;
3461
3493
  setLoadError(null);
3462
3494
  if (createSessionHash) {
@@ -3902,7 +3934,7 @@ function InterimButtonsView({
3902
3934
  }) {
3903
3935
  const [showCardForm, setShowCardForm] = useState4(false);
3904
3936
  const isCardOpenControlled = typeof cardOpen === "boolean";
3905
- useEffect4(() => {
3937
+ useEffect5(() => {
3906
3938
  if (isCardOpenControlled) {
3907
3939
  setShowCardForm(cardOpen);
3908
3940
  }
@@ -4086,7 +4118,7 @@ function InterimButtonsView({
4086
4118
 
4087
4119
  // src/checkout-form.tsx
4088
4120
  import { FloPayError as FloPayError5 } from "@flopay/shared";
4089
- import { forwardRef as forwardRef2, useCallback as useCallback4, useEffect as useEffect5, useImperativeHandle as useImperativeHandle2, useState as useState5 } from "react";
4121
+ import { forwardRef as forwardRef2, useCallback as useCallback4, useEffect as useEffect6, useImperativeHandle as useImperativeHandle2, useState as useState5 } from "react";
4090
4122
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
4091
4123
  var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
4092
4124
  var CheckoutForm = forwardRef2(
@@ -4294,7 +4326,7 @@ function CheckoutFormInner({
4294
4326
  }
4295
4327
  }
4296
4328
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
4297
- useEffect5(() => {
4329
+ useEffect6(() => {
4298
4330
  if (typeof window === "undefined") return;
4299
4331
  const stored = localStorage.getItem(WALLET_RESUME_KEY2);
4300
4332
  if (!stored) return;
@@ -4432,7 +4464,7 @@ function CheckoutFormInner({
4432
4464
 
4433
4465
  // src/paypal-button.tsx
4434
4466
  import { FloPayError as FloPayError6 } from "@flopay/shared";
4435
- import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef4, useState as useState6 } from "react";
4467
+ import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef4, useState as useState6 } from "react";
4436
4468
  import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
4437
4469
  function PayPalButton({
4438
4470
  sessionId,
@@ -4497,7 +4529,7 @@ function PayPalButton({
4497
4529
  },
4498
4530
  [onTokenizedBody, processPaymentInternal]
4499
4531
  );
4500
- useEffect6(() => {
4532
+ useEffect7(() => {
4501
4533
  if (!flopay || paypalResumeAttempted.current) return;
4502
4534
  const params = new URLSearchParams(window.location.search);
4503
4535
  const paymentIntentId = params.get("payment_intent");
@@ -4624,7 +4656,7 @@ function PayPalButton({
4624
4656
  }
4625
4657
 
4626
4658
  // src/automatic-payment-button.tsx
4627
- import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef5, useState as useState7 } from "react";
4659
+ import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef5, useState as useState7 } from "react";
4628
4660
  import { PaymentAPI as PaymentAPI3 } from "@flopay/js";
4629
4661
  import { FloPayError as FloPayError7, resolveBillingApiUrl as resolveBillingApiUrl4, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme3 } from "@flopay/shared";
4630
4662
  import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
@@ -4792,25 +4824,25 @@ function FloPayAutomaticPaymentButton({
4792
4824
  const onSuccessRef = useRef5(onSuccess);
4793
4825
  const onErrorRef = useRef5(onError);
4794
4826
  const onDeclineRef = useRef5(onDecline);
4795
- useEffect7(() => {
4827
+ useEffect8(() => {
4796
4828
  fallbackSessionRef.current = fallbackSession;
4797
4829
  }, [fallbackSession]);
4798
- useEffect7(() => {
4830
+ useEffect8(() => {
4799
4831
  onSuccessRef.current = onSuccess;
4800
4832
  }, [onSuccess]);
4801
- useEffect7(() => {
4833
+ useEffect8(() => {
4802
4834
  onErrorRef.current = onError;
4803
4835
  }, [onError]);
4804
- useEffect7(() => {
4836
+ useEffect8(() => {
4805
4837
  onDeclineRef.current = onDecline;
4806
4838
  }, [onDecline]);
4807
- useEffect7(() => {
4839
+ useEffect8(() => {
4808
4840
  isMountedRef.current = true;
4809
4841
  return () => {
4810
4842
  isMountedRef.current = false;
4811
4843
  };
4812
4844
  }, []);
4813
- useEffect7(() => {
4845
+ useEffect8(() => {
4814
4846
  if (!fallbackSession || typeof window === "undefined") {
4815
4847
  return;
4816
4848
  }
@@ -5141,7 +5173,7 @@ function FloPayAutomaticPaymentButton({
5141
5173
  const handleFallbackDecline = useCallback6((decline) => {
5142
5174
  onDeclineRef.current?.(decline);
5143
5175
  }, []);
5144
- useEffect7(() => {
5176
+ useEffect8(() => {
5145
5177
  if (typeof window === "undefined" || resumeAttemptedRef.current) {
5146
5178
  return;
5147
5179
  }
@@ -5405,6 +5437,7 @@ export {
5405
5437
  PayPalButton,
5406
5438
  PaymentElement,
5407
5439
  SplitCardForm,
5440
+ isInAppBrowser,
5408
5441
  useCheckout,
5409
5442
  useElements,
5410
5443
  useFloPay,