Skip to content

Add PayPal App Switch Overlay #2484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/three-domain-secure/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
getPayPalDomainRegex,
} from "@paypal/sdk-client/src";

import { Overlay } from "../ui/overlay";
import { Overlay } from "../ui/overlay/three-domain-secure";

import type { TDSProps } from "./types";

Expand Down
12 changes: 12 additions & 0 deletions src/ui/buttons/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,15 @@ export type ButtonExtensions = {|
resume: () => void,
|};

type ShowPayPalAppSwitchOverlay = {|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also need to accept the universal link to render the continue link to app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added that logic in the focus function in setupOverlays

I can update the focus function to take the args props.redirect & url.href and call props.redirect(url.href) from inside the overlay component

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ravishekhar , I attempted a solution that took the url as an argument and ran into issues running the karma tests with the redirect call now being a part of overlay.jsx

With the Karma framework, there is no way to mock location.href or location.redirect. When the redirect is called, the karma test runner is interrupted and the tests fail.

focus: () => void,
close: () => void,
|};

type HidePayPalAppSwitchOverlay = {|
close: () => void,
|};

export type ButtonProps = {|
// app switch properties
appSwitchWhenAvailable: string,
Expand All @@ -515,6 +524,9 @@ export type ButtonProps = {|
// Not passed to child iframe
visibilityChangeHandler: () => void,

showPayPalAppSwitchOverlay: (args: ShowPayPalAppSwitchOverlay) => void,
hidePayPalAppSwitchOverlay: (args: HidePayPalAppSwitchOverlay) => void,

fundingSource?: ?$Values<typeof FUNDING>,
intent: $Values<typeof INTENT>,
createOrder: CreateOrder,
Expand Down
File renamed without changes.
150 changes: 150 additions & 0 deletions src/ui/overlay/paypal-app-switch/overlay.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/* @flow */
/** @jsx node */

import {
isIos,
isIpadOs,
isFirefox,
animate,
noop,
supportsPopups,
} from "@krakenjs/belter/src";
import { node, type ElementNode } from "@krakenjs/jsx-pragmatic/src";
import { LOGO_COLOR, PayPalRebrandLogo } from "@paypal/sdk-logos/src";
import { type ZalgoPromise } from "@krakenjs/zalgo-promise/src";

import { getContainerStyle, getSandboxStyle } from "./style";

type OverlayProps = {|
buttonSessionID: string,
close: () => ZalgoPromise<void>,
focus: () => ZalgoPromise<void>,
|};

export function PayPalAppSwitchOverlay({
close,
focus,
buttonSessionID,
}: OverlayProps): ElementNode {
const uid = `paypal-overlay-${buttonSessionID}`;
const overlayIframeName = `__paypal_checkout_sandbox_${uid}__`;

function closeCheckout(e) {
e.preventDefault();
e.stopPropagation();
close();
// const body = document.getElementsByTagName("body")?.[0];
const overlay = document.getElementsByName(uid)?.[0];

if (overlay) {
overlay.remove();
}
}

function displayFocusWarning() {
const overlayIframe: ?HTMLIFrameElement =
// $FlowFixMe
document.getElementsByName(overlayIframeName)?.[0];
const iframeDocument = overlayIframe?.contentWindow.document;
const warningElement = iframeDocument?.getElementsByClassName(
"paypal-checkout-focus-warning"
)?.[0];

if (!warningElement) {
return;
}
warningElement.innerText = `Still can't see it? Select "Window" in your toolbar to find "Log in to your PayPal account"`;
}

function focusCheckout(e) {
e.preventDefault();
e.stopPropagation();

if (!supportsPopups()) {
return;
}

if (isIos() || isIpadOs()) {
// Note: alerts block the event loop until they are closed.
// eslint-disable-next-line no-alert
window.alert("Please switch tabs to reactivate the PayPal window");
} else if (isFirefox()) {
displayFocusWarning();
}
focus();
}

const setupAnimations = (name) => {
return (el) => {
animate(el, `show-${name}`, noop);
};
};

const nonce = "";
const context = "popup";
const content = {
windowMessage: "To finish, go back to the PayPal app.",
continueMessage: "Return to PayPal",
};

return (
<div
id={uid}
name={uid}
onRender={setupAnimations("container")}
class="paypal-checkout-sandbox"
>
<style nonce={nonce}>{getSandboxStyle({ uid })}</style>
<iframe
title="PayPal Checkout Overlay"
name={overlayIframeName}
scrolling="no"
class="paypal-checkout-sandbox-iframe"
>
<html>
<body>
<div
dir="auto"
id={uid}
onClick={focusCheckout}
class={`paypal-overlay-context-${context} paypal-checkout-overlay`}
>
<a
href="#"
class="paypal-checkout-close"
onClick={closeCheckout}
aria-label="close"
role="button"
/>
<div class="paypal-checkout-modal">
<div class="paypal-checkout-logo" dir="ltr">
<PayPalRebrandLogo logoColor={LOGO_COLOR.WHITE} />
</div>
{content.windowMessage && (
<div class="paypal-checkout-message">
{content.windowMessage}
</div>
)}
<div class="paypal-checkout-focus-warning" />
{content.continueMessage && (
<div class="paypal-checkout-continue">
{/* This handler should be guarded with e.stopPropagation.
This will stop the event from bubbling up to the overlay click handler
and causing unexpected behavior. */}
<a onClick={focusCheckout} href="#">
{content.continueMessage}
</a>
</div>
)}
<div class="paypal-checkout-loader">
<div class="paypal-spinner" />
</div>
</div>
<style nonce={nonce}>{getContainerStyle({ uid })}</style>
</div>
</body>
</html>
</iframe>
</div>
);
}
Loading
Loading