Code Monkey home page Code Monkey logo

react-paypal-js's Introduction

react-paypal-js

React components for the PayPal JS SDK

build status coverage npm version bundle size npm downloads apache license storybook

Why use react-paypal-js?

The Problem

Developers integrating with PayPal are expected to add the JS SDK <script> to a website and then render components like the PayPal Buttons after the script loads. This architecture works great for simple websites but can be challenging when building single page apps.

React developers think in terms of components and not about loading external scripts from an index.html file. It's easy to end up with a React PayPal integration that's sub-optimal and hurts the buyer's user experience. For example, abstracting away all the implementation details of the PayPal Buttons into a single React component is an anti-pattern because it tightly couples script loading with rendering. It's also problematic when you need to render multiple different PayPal components that share the same global script parameters.

The Solution

react-paypal-js provides a solution to developers to abstract away complexities around loading the JS SDK. It enforces best practices by default so buyers get the best possible user experience.

Features

  • Enforce async loading the JS SDK upfront so when it's time to render the buttons to your buyer, they render immediately.
  • Abstract away the complexity around loading the JS SDK with the global PayPalScriptProvider component.
  • Support dispatching actions to reload the JS SDK and re-render components when global parameters like currency change.
  • Easy to use components for all the different Braintree/PayPal product offerings:

Installation

To get started, install react-paypal-js with npm.

npm install @paypal/react-paypal-js

Usage

This PayPal React library consists of two main parts:

  1. Context Provider - this <PayPalScriptProvider /> component manages loading the JS SDK script. Add it to the root of your React app. It uses the Context API for managing state and communicating to child components. It also supports reloading the script when parameters change.
  2. SDK Components - components like <PayPalButtons /> are used to render the UI for PayPal products served by the JS SDK.
// App.js
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";

export default function App() {
    return (
        <PayPalScriptProvider options={{ clientId: "test" }}>
            <PayPalButtons style={{ layout: "horizontal" }} />
        </PayPalScriptProvider>
    );
}

PayPalScriptProvider

Options

Use the PayPalScriptProvider options prop to configure the JS SDK. It accepts an object for passing query parameters and data attributes to the JS SDK script. Use camelCase for the object keys (clientId, dataClientToken, dataNamespace, etc...).

const initialOptions = {
    clientId: "test",
    currency: "USD",
    intent: "capture",
};

export default function App() {
    return (
        <PayPalScriptProvider options={initialOptions}>
            <PayPalButtons />
        </PayPalScriptProvider>
    );
}

The JS SDK Configuration guide contains the full list of query parameters and data attributes that can be used with the JS SDK.

deferLoading

Use the optional PayPalScriptProvider deferLoading prop to control when the JS SDK script loads.

  • This prop is set to false by default since we usually know all the sdk script params upfront and want to load the script right away so components like <PayPalButtons /> render immediately.
  • This prop can be set to true to prevent loading the JS SDK script when the PayPalScriptProvider renders. Use deferLoading={true} initially and then dispatch an action later on in the app's life cycle to load the sdk script.
<PayPalScriptProvider deferLoading={true} options={initialOptions}>
    <PayPalButtons />
</PayPalScriptProvider>

To learn more, check out the defer loading example in storybook.

Tracking loading state

The <PayPalScriptProvider /> component is designed to be used with the usePayPalScriptReducer hook for managing global state. This usePayPalScriptReducer hook has the same API as React's useReducer hook.

The usePayPalScriptReducer hook provides an easy way to tap into the loading state of the JS SDK script. This state can be used to show a loading spinner while the script loads or an error message if it fails to load. The following derived attributes are provided for tracking this loading state:

  • isInitial - not started (only used when passing deferLoading={true})
  • isPending - loading (default)
  • isResolved - successfully loaded
  • isRejected - failed to load

For example, here's how you can use it to show a loading spinner.

const [{ isPending }] = usePayPalScriptReducer();

return (
    <>
        {isPending ? <div className="spinner" /> : null}
        <PayPalButtons />
    </>
);

To learn more, check out the loading spinner example in storybook.

Reloading when parameters change

The usePayPalScriptReducer hook can be used to reload the JS SDK script when parameters like currency change. It provides the action resetOptions for reloading with new parameters. For example, here's how you can use it to change currency.

// get the state for the sdk script and the dispatch method
const [{ options }, dispatch] = usePayPalScriptReducer();
const [currency, setCurrency] = useState(options.currency);

function onCurrencyChange({ target: { value } }) {
    setCurrency(value);
    dispatch({
        type: "resetOptions",
        value: {
            ...options,
            currency: value,
        },
    });
}

return (
    <>
        <select value={currency} onChange={onCurrencyChange}>
            <option value="USD">United States dollar</option>
            <option value="EUR">Euro</option>
        </select>
        <PayPalButtons />
    </>
);

To learn more, check out the dynamic currency example in storybook.

PayPalButtons

The <PayPalButtons /> component is documented in Storybook.

Here's an example:

// App.js
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";

export default function App() {
    function createOrder() {
        return fetch("/my-server/create-paypal-order", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            // use the "body" param to optionally pass additional order information
            // like product ids and quantities
            body: JSON.stringify({
                cart: [
                    {
                        id: "YOUR_PRODUCT_ID",
                        quantity: "YOUR_PRODUCT_QUANTITY",
                    },
                ],
            }),
        })
            .then((response) => response.json())
            .then((order) => order.id);
    }
    function onApprove(data) {
          return fetch("/my-server/capture-paypal-order", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              orderID: data.orderID
            })
          })
          .then((response) => response.json())
          .then((orderData) => {
                const name = orderData.payer.name.given_name;
                alert(`Transaction completed by ${name}`);
          });

        }
    }
    return (
        <PayPalScriptProvider options={{ clientId: "test" }}>
            <PayPalButtons
                createOrder={createOrder}
                onApprove={onApprove}
            />
        </PayPalScriptProvider>
    );
}

To learn more about other available props, see the PayPalButtons docs.

BraintreePayPalButtons

The Braintree SDK can be used with the PayPal JS SDK to render the PayPal Buttons. Read more about this integration in the Braintree PayPal client-side integration docs. The <BraintreePayPalButtons /> component is designed for Braintree merchants who want to render the PayPal button.

// App.js
import {
    PayPalScriptProvider,
    BraintreePayPalButtons,
} from "@paypal/react-paypal-js";

export default function App() {
    return (
        <PayPalScriptProvider
            options={{
                clientId: "test",
                dataClientToken:
                    "<the data-client-token value generated by your server-side code>",
            }}
        >
            <BraintreePayPalButtons
                createOrder={(data, actions) => {
                    return actions.braintree.createPayment({
                        flow: "checkout",
                        amount: "10.0",
                        currency: "USD",
                        intent: "capture",
                    });
                }}
                onApprove={(data, actions) => {
                    return actions.braintree
                        .tokenizePayment(data)
                        .then((payload) => {
                            // call server-side endpoint to finish the sale
                        });
                }}
            />
        </PayPalScriptProvider>
    );
}

Check out the docs page for the BraintreePayPalButtons to learn more about the available props.

PayPal Hosted Fields

The JS SDK hosted-fields component provides payment form functionality that you can customize. Read more about this integration in the PayPal Advanced Card Payments documentation.

There are 3 parts to the hosted-fields integration:

  1. The <PayPalHostedFieldsProvider /> provider component wraps the form field elements and accepts props like createOrder().
  2. The <PayPalHostedField> component is used for the credit card number, expiration, and cvv elements. These are customizable using props and must be children of the <PayPalHostedFieldsProvider /> component.
  3. The usePayPalHostedFields hook exposes the submit() function for submitting the payment with your own custom button.
import {
    PayPalScriptProvider,
    PayPalHostedFieldsProvider,
    PayPalHostedField,
    usePayPalHostedFields,
} from "@paypal/react-paypal-js";

const SubmitPayment = () => {
    // Here declare the variable containing the hostedField instance
    const hostedFields = usePayPalHostedFields();

    const submitHandler = () => {
        if (typeof hostedFields.submit !== "function") return; // validate that `submit()` exists before using it
        hostedFields
            .submit({
                // The full name as shown in the card and billing address
                cardholderName: "John Wick",
            })
            .then((order) => {
                fetch(
                    "/your-server-side-integration-endpoint/capture-payment-info"
                )
                    .then((response) => response.json())
                    .then((data) => {
                        // Inside the data you can find all the information related to the payment
                    })
                    .catch((err) => {
                        // Handle any error
                    });
            });
    };

    return <button onClick={submitHandler}>Pay</button>;
};

export default function App() {
    return (
        <PayPalScriptProvider
            options={{
                clientId: "your-client-id",
                dataClientToken: "your-data-client-token",
            }}
        >
            <PayPalHostedFieldsProvider
                createOrder={() => {
                    // Here define the call to create and order
                    return fetch(
                        "/your-server-side-integration-endpoint/orders"
                    )
                        .then((response) => response.json())
                        .then((order) => order.id)
                        .catch((err) => {
                            // Handle any error
                        });
                }}
            >
                <PayPalHostedField
                    id="card-number"
                    hostedFieldType="number"
                    options={{ selector: "#card-number" }}
                />
                <PayPalHostedField
                    id="cvv"
                    hostedFieldType="cvv"
                    options={{ selector: "#cvv" }}
                />
                <PayPalHostedField
                    id="expiration-date"
                    hostedFieldType="expirationDate"
                    options={{
                        selector: "#expiration-date",
                        placeholder: "MM/YY",
                    }}
                />
                <SubmitPayment />
            </PayPalHostedFieldsProvider>
        </PayPalScriptProvider>
    );
}

PayPal Card Fields

The JS SDK card-fields component provides payment form functionality that you can customize. Read more about this integration in the PayPal Advanced Card Payments documentation.

Using Card Fields Form (recommended)

There are 3 parts to the this card-fields integration:

  1. The <PayPalCardFieldsProvider /> provider component wraps the form field elements and accepts props like createOrder().
  2. The <PayPalCardFieldsForm /> component renders a form with all 4 fields included out of the box. This is an alternative for merchants who don't want to render each field individually in their react app.
  3. The usePayPalCardFields hook exposes the cardFieldsForm instance that includes methods suchs as the cardFieldsForm.submit() function for submitting the payment with your own custom button. It also exposes the references to each of the individual components for more granular control, eg: fields.CVVField.focus() to programatically manipulate the element in the DOM.
import {
    PayPalScriptProvider,
    PayPalCardFieldsProvider,
    PayPalCardFieldsForm
    usePayPalCardFields,
} from "@paypal/react-paypal-js";

const SubmitPayment = () => {
    const { cardFields, fields } = usePayPalCardFields();

    function submitHandler() {
        if (typeof cardFields.submit !== "function") return; // validate that `submit()` exists before using it

        cardFields
            .submit()
            .then(() => {
                // submit successful
            })
            .catch(() => {
                // submission error
            });
    }
    return <button onClick={submitHandler}>Pay</button>;
};

export default function App() {
    function createOrder() {
        // merchant code
    }
    function onApprove() {
        // merchant code
    }
    function onError() {
        // merchant code
    }
    return (
        <PayPalScriptProvider
            options={{
                clientId: "your-client-id",
                components: "card-fields",
            }}
        >
            <PayPalCardFieldsProvider
                createOrder={createOrder}
                onApprove={onApprove}
                onError={onError}
            >
                <PayPalCardFieldsForm />
                <SubmitPayment />
            </PayPalCardFieldsProvider>
        </PayPalScriptProvider>
    );
}

Using Card Fields Individually

There are 3 parts to the this card-fields integration:

  1. The <PayPalCardFieldsProvider /> provider component wraps the form field elements and accepts props like createOrder().
  2. The individual CardFields:
    • <PayPalNumberField> component used for the credit card number element. It is customizable using props and must be a child of the <PayPalCardFieldsProvider /> component.
    • <PayPalCVVField> component used for the credit card cvv element. It is customizable using props and must be a child of the <PayPalCardFieldsProvider /> component.
    • <PayPalExpiryField> component used for the credit card expiry element. It is customizable using props and must be a child of the <PayPalCardFieldsProvider /> component.
    • <PayPalNameField> component used for the credit cardholder's name element. It is customizable using props and must be a child of the <PayPalCardFieldsProvider /> component.
  3. The usePayPalCardFields hook exposes the cardFieldsForm instance that includes methods suchs as the cardFieldsForm.submit() function for submitting the payment with your own custom button. It also exposes the references to each of the individual components for more granular control, eg: fields.CVVField.focus() to programatically manipulate the element in the DOM.
import {
    PayPalScriptProvider,
    PayPalCardFieldsProvider,
    PayPalNameField,
    PayPalNumberField,
    PayPalExpiryField,
    PayPalCVVField,
    usePayPalCardFields,
} from "@paypal/react-paypal-js";

const SubmitPayment = () => {
    const { cardFields, fields } = usePayPalCardFields();

    function submitHandler() {
        if (typeof cardFields.submit !== "function") return; // validate that `submit()` exists before using it

        cardFields
            .submit()
            .then(() => {
                // submit successful
            })
            .catch(() => {
                // submission error
            });
    }
    return <button onClick={submitHandler}>Pay</button>;
};

// Example using individual card fields
export default function App() {
    function createOrder() {
        // merchant code
    }
    function onApprove() {
        // merchant code
    }
    function onError() {
        // merchant code
    }
    return (
        <PayPalScriptProvider
            options={{
                clientId: "your-client-id",
                components: "card-fields",
            }}
        >
            <PayPalCardFieldsProvider
                createOrder={createOrder}
                onApprove={onApprove}
                onError={onError}
            >
                <PayPalNameField />
                <PayPalNumberField />
                <PayPalExpiryField />
                <PayPalCVVField />

                <SubmitPayment />
            </PayPalCardFieldsProvider>
        </PayPalScriptProvider>
    );
}

Browser Support

This library supports all popular browsers, including IE 11. It provides the same browser support as the JS SDK. Here's the full list of supported browsers.

react-paypal-js's People

Contributors

borodovisin avatar devesh21700kumar avatar dreyks avatar dtjones404 avatar elizabethmv avatar gabrielo91 avatar gregjopa avatar hard-coder05 avatar jcyamacho avatar jshawl avatar jsierrav14 avatar leogedler avatar mchoun avatar mpcsh avatar mstuart avatar musps avatar nbierdeman avatar oscarleonnogales avatar pauliescanlon avatar pedpess avatar pedroapfilho avatar rajajaganathan avatar ronlek avatar sagarchoudhary96 avatar sebastianfdz avatar shrutikapoor08 avatar torfab avatar westeezy avatar wsbrunson avatar yocmen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-paypal-js's Issues

[Bug] Currency change not reliable

๐Ÿž Describe the Bug

It seems that if you add the dispatch on the useEffect to change the currency, and it triggers 2 times fastly, it doesn't get the correct currency reliably.

๐Ÿ”ฌ Minimal Reproduction

We have a context, and we have a defaultCurrency, and a selectedCurrency, that is loaded from the server, so it initializes as GBP (default currency), and then fastly goes to EUR (as an example of selected currency that came from the server, on an API call)

๐Ÿ˜• Actual Behavior

Creates the order as GBP

๐Ÿค” Expected Behavior

Should create the order in EUR

โž• Additional Context

What we did to fix this race condition was adding a check on the useEffect, like this:

  useEffect(() => {
    if (selectedCurrency !== defaultCurrency) {
      paypalDispatch({
        type: "resetOptions",
        value: {
          ...options,
          currency: selectedCurrency,
        },
      });
    }
  }, [selectedCurrency]);

Then it works reliably.

So either we have to add it to the documentation, or create a solution for it

[Bug] Failed to execute 'postMessage' on 'DOMWindow'

๐Ÿž Describe the Bug

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided does not match the recipient window's origin ('https://www.sandbox.paypal.com').

Same is working on localhost.

๐Ÿ”ฌ Minimal Reproduction

  1. add product to cart
  2. add to cart and go to cart page
  3. Paypal button is not loading correctly. Errors logged in console.

๐Ÿ˜• Actual Behavior

PayPal script not working on our website but works on localhost

๐Ÿค” Expected Behavior

It should load correctly

๐ŸŒ Environment

Software Version(s)
react-paypal-js 4.1.1
Browser chrome Version 89.0.4389.82
Operating System Windows 7

[Bug] Unwanted rerender from side effects with "createOrder" prop

๐Ÿž Describe the Bug

The popup windows just close without showing an error

๐Ÿ”ฌ Minimal Reproduction

On my react component I have:

const [internalOrderNumber, setinternalOrderNumber] = useState(null);

then I have a method handleCreateOrder to manage the create order on my server-side:

const handleCreateOrder = async() => {
    const body = {};
    const { error, result } = await createOrder(body);

    setinternalOrderNumber(result.internalOrderNumber); // My own order number
    return result.orderID; //paypal order number
}

๐Ÿ˜• Actual Behavior

If I use the setinternalOrderNumber(result.internalOrderNumber); the popup just banished without message

๐Ÿค” Expected Behavior

But if I remove the setinternalOrderNumber(result.internalOrderNumber); line it works like be expected.

๐ŸŒ Environment

GatsbyJS

โž• Additional Context

I have client-side and server-side code to perform the payments, in this case, is just the client-side.

Can I manage states from the Button methods? if is possible how can I handle that? thanks in advance for all the help.

[Bug] PreRender Button Clicks open many popup windows

๐Ÿž Describe the Bug

When the buttons first render, clicking on them many times will result in several popup windows opening. The issue seems to go away after second render is complete. The problem is more noticeable on a slower internet connection.

react-prereder-many-popups-bug

๐Ÿ”ฌ Minimal Reproduction

Go to https://paypal.github.io/react-paypal-js/iframe.html?id=example-paypalbuttons--default&args=&viewMode=story and repeatedly click on the buttons as soon as they render. It will open up many popup windows.

๐Ÿ˜• Actual Behavior

Repeatedly click on the buttons as soon as they render results in many popup windows opening.

๐Ÿค” Expected Behavior

Repeatedly click on the buttons as soon as they render results in only one popup window opening.

๐ŸŒ Environment

Happens in Chrome and Safari.

โž• Additional Context

It's unclear what the root cause is. I tested react-paypal-js@3 locally and see the same issue ๐Ÿค”

PayPalScript refreshing

Hi guys,

I'm trying to figure out how should I work with changing params like currency but I don't see any examples to implement it. I think it would be great to place more info about it. Right know im trying this approach:

const PaypalProvider = ({ children }) => {
  const {
    state: { paypal },
  } = useContext(AppContext);

  console.log(paypal);

  return (
    <PayPalScriptProvider
      key={paypal}
      options={{
        currency: paypal.currency,
        "client-id":
          "####",
      }}
    >
      {children}
    </PayPalScriptProvider>
  );
};

export default PaypalProvider;

So I have a global state that changes, and I see it in console.log() but how should I refresh this provider? Right now it doesn't work, and any changes doesn't affect this provider. I was trying to do something with a key but it doesn't work too. Maybe I'm working with it in a bad way?

[Feature] Create TypeScript typings

This project isn't written in TypeScript, but many TypeScript users would like to use published typings. For this issue, create TypeScript typings that cover the exported functions, and contribute it back to DefinitelyTyped!

This issue is great for someone who wants to learn more about TypeScript!

Here's an example from paypal-js: paypal/paypal-js#39

[Bug] Error: usePayPalScriptReducer must be used within a PayPalScriptProvider

๐Ÿž Describe the Bug

Unhandled Runtime Error Error: usePayPalScriptReducer must be used within a PayPalScriptProvider

๐Ÿ”ฌ Minimal Reproduction

(settings: react / nextjs)

I simply add the context in _app.js :
<PayPalScriptProvider options={{ 'client-id': 'test' }}> <Page> <Component {...pageProps} /> </Page> </PayPalScriptProvider>

I add the PayPal button into a component.

๐Ÿ˜• Actual Behavior

And then everything bugs, error message:

Unhandled Runtime Error
Error: usePayPalScriptReducer must be used within a PayPalScriptProvider

I don't even use this hook. Even by adding it somewhere, same issue. No way so far to use this service...

How to re-render the Buttons component when createOrder() function changes

I'm creating a checkout with the PayPalButtons, and I found an interesting bug, that made me realize something... We don't re-render the button if one of its functions changes, meaning that I created the createOrder function on the page, and this function depended on the shipping address coming from the context. It's undefined at first, but as soon as it gets re-hydrated, it gets the correct object.

The issue is that the PayPalButtons component doesn't care about this change, it doesn't have anything on the useEffect dependencies saying that it should, like:

}, [isResolved, props.forceReRender, props.fundingSource]);

And then what I had to do is create add:

        forceReRender={!!shippingAddress}

That makes the button re-render the function on re-hydration.

I think we should add the functions on the dependency there, and ask our users to use useCallback with some dependencies on their apps, it would solve this type of issue.

[Bug] Sometimes get 'script-src-elem' was not explicitly set error on windows

๐Ÿž Describe the Bug

Sometimes on our sandbox env when we try click on the Paypal button to open the modal, its get stuck in infinite loading spinner and we see this error in the console

Refused to load the script 'http://bpp.eset.com/en-US/Redirect-Switch?token=XXX' because it violates the following Content Security Policy directive: "script-src 'self' https://.paypal.com: https://*.paypalobjects.com 'unsafe-inline' 'unsafe-eval' https://apis.google.com". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

๐Ÿ”ฌ Minimal Reproduction

Hard to reproduce as it sometimes happens and after some refreshes, it suddenly works.

๐Ÿ˜• Actual Behavior

PayPal SDK not loaded because of the above error.

๐Ÿค” Expected Behavior

PayPal SDK should load and the Paypal modal should show the regular state.

๐ŸŒ Environment

Software Version(s)
react-paypal-js 2.0.0
Browser Chrome
Operating System Windows 10

[Feature] usePayPalScriptReducer has isReady value

When using the isPending value from the contextual hook will cause flashing. This only means the script has been loaded, it does not mean that the actual PayPal button are ready and rendered.

If you have spinners for example using isPended, this will remove the spinner and then render nothing for a second or two, then the buttons will load in, this makes for a terrible screen jogging experience.

We need a way to know the buttons are ready to properly transition from loading to ready.

[Feature] Feature request: Return value

๐Ÿš€ Feature Proposal

Hey guys, first of all thanks for creating this.
It's still active right?

Motivation

I wanted to know if it's possible to receive a return value after the button was pushed
and the order created (sucessfully or not). So I would like to get a response code back
to the react app. Do you guys thinks thats possible somehow?

Example

const return_value = 0

return (
    <PayPalScriptProvider options={initialOptions}>
        <PayPalButtons
    createSubscription={(data, actions) =>
        actions.subscription.create({
            plan_id: "P-XXXXX",
        }). onApprove( return_value = data.status.code )
    }
    style={{
        label: "subscribe",
    }}
/>
    </PayPalScriptProvider>
);

Best regards
Christian Klose

[FAQ] Where do I find the documentation for 'options'?

๐Ÿž Describe the Bug

PayPalScriptProvider supports 'options', but what are those values? Where do i find them?

In general the solution is great, but it seems that there is too much to work on, it takes a lot of documentation and examples.

<PayPalScriptProvider options={{ "client-id": "sb", "Other?" : "??" }}>
</PayPalScriptProvider>

[Feature] forceRerender as an array

Right now we have only one property on the forceRerender prop, I had to add my createOrder function to it, which is an useCallback function, and it respects the array of properties of it to force the rerender.

I think we can make it more readable/reliable if we make the forceRerender be an array, so we can pass as many properties to it as we want, and it would be very easy to implement.

[Feature] Use TypeScript for Storybook Examples

๐Ÿš€ Feature Proposal

Currently the Storybook examples are in JavaScript. Let's migrate them over to TypeScript files! It will be a good way to ensure the StoryBook examples are type safe.

Motivation

Let's go all in on TypeScript!

Can't get PayPal balance as a payment method for subscription checkout

Hey everyone,

We have customers on our site subscribing using PayPal. However, I don't think they're able to use their PayPal balance to complete the checkout and finalize their subscription. On our staging and production setups, we only get options to pay via bank account or credit card:

Screen Shot 2021-02-10 at 11 10 30 PM

However, I also want the option to pay using my balance. For example, when I click to complete an order in this storybook example, I'm able to get the balance to appear:

Screen Shot 2021-02-10 at 11 11 59 PM

We're noticing in our logs that a lot of users keep opening and closing the checkout window that appears when clicking on the PayPal button, presumably because they want the option to pay with their balance. Is there something I can do to give users the option to pay for the subscription using their balance?

Thanks!

[Feature] Add option to control when the JS SDK script loads

๐Ÿš€ Feature Proposal

Add the ability to control when the <PayPalScriptProvider> loads the JS SDK <script>. Currently it loads the JS SDK script as soon as the provider is rendered.

Motivation

In complex react apps, you may only want to load the JS SDK script under certain conditions.

Example

My initial thought is to add a new prop to the <PayPalScriptProvider> to control this behavior:

<PayPalScriptProvider options={{ "client-id": "test" }} deferLoading={true} >
    { /* pretend this component does not get shown on the initial route, and instead gets added later in the user flow */ }
    <PayPalButtons style={{ layout: "horizontal" }} />
</PayPalScriptProvider>

The shouldLoadScript deferLoading prop would accept a boolean value and default to false. Let's consider this behavior "eager loading".

When true is passed in, the script loading behavior would be considered "lazy loading". In this mode you could load the script in a few different ways:

  1. Update the value passed into the deferLoading prop and set it equal to false.
  2. Wait for the the component (ex: <PayPalButtons />) to get rendered which would dispatch an action to kick off the script loading by changing the state to pending.
  3. Dispatch the setLoadingStatus action to change from NOT_STARTED to PENDING to kick off the script loading.

Currently we have an enum for the different script loading states. I think we would need to add a new "NOT_STARTED" option to support the shouldLoadScript prop:

enum SCRIPT_LOADING_STATE {
    NOT_STARTED = "not_started",
    PENDING = "pending",
    REJECTED = "rejected",
    RESOLVED = "resolved",
}

[Bug] Authentication Error on 2nd Attempt

๐Ÿž Describe the Bug
I use Smart Button in React/NextJs for creating subscriptions. Everything works as expected, but if the user closes the Paypal popup window after having logged in already (OrderId created, onShippingChangeData called), and then attempts by clicking the Paypal button for a second time, I see the following error:

Uncaught Error: Create Subscription Api response error:
{
"name": "NOT_AUTHORIZED",
"message": "Authorization failed due to insufficient permissions.",
"debug_id": "3ba3d8fcdc9e7",
"details": [
{
"issue": "PERMISSION_DENIED",
"description": "You do not have permission to access or perform operations on this resource."
}

The popup window then opens and immediately disappears. I tried tackling this with a) calling dispatch with resetOptions in onCancel and b) reloading the react component including PayPalScriptProvider without success.

Am I missing something here? Do I somehow need to cancel the orderID at onCancel manually?

๐Ÿ”ฌ Minimal Reproduction
Create a subscription checkout with @paypal/react-paypal-js, initiate checkout, log into buyer account, close Popup window and attempt checkout for a second or third time.

๐Ÿ˜• Actual Behavior
see above

๐Ÿค” Expected Behavior
Ability to re-open the subscription checkout multiple times without loosing access rights

๐ŸŒ Environment
Browser version: Chrome Version 89.0.4389.114
OS version: OSX 11.2.3
SDK version (window.paypal.version): - @paypal/react-paypal-jss

[Bug] TS definition for dispatch "setLoadingStatus" is incorrect

๐Ÿž Describe the Bug

When following the documentation for "Defer loading" and implementing the example code in Typescript, I get a type error on the action value:
image

๐Ÿ”ฌ Minimal Reproduction

See this sandbox: https://codesandbox.io/s/react-paypal-serloadingstatus-mdkux?file=/src/Paypal.tsx

๐Ÿ˜• Actual Behavior

Type error

๐Ÿค” Expected Behavior

"pending" should be accepted

๐ŸŒ Environment

Software Version(s)
react-paypal-js 6.0.0

Accessing types from Typescript

I canโ€™t seem to import some of the types used internally in the react-paypal-js definitions and I canโ€™t seem to work out why. What I would like to do is:

    const onApprove = (data: OnApproveData, actions: OnApproveActions) => {
        return actions.order.capture().then((details: CaptureOrderResponseBody) => {
            const {payer} = details;
            [โ€ฆ]
            setSucceeded(true);
        }).catch((err) => setPaypalErrorMessage("Something went wrong."));
    };
[โ€ฆ]
        <PayPalButtons style={{layout: "vertical"}}
                       createOrder={(data, actions) => [...]}
                       onApprove={onApprove}/>

No amount of kicking and goading seems to want to let me to import type definitions for OnApproveData, OnApproveActions or CaptureOrderResponseBody even though I can see them in the .d.ts files and they seem to be exported. Is there some specific way that they need to be imported?

[Bug] Error: Window closed before response

This bug is understandable when we implement the script tag directly, but using it as a react app, it's just weird. If a user closes the tab before PayPal receives some action back, it throws this error.

It wouldn't be a problem for small projects, but for big ones, it's a nightmare and doesn't provide any benefit (in my point of view).

Wouldn't it be a good idea to remove it?

Failed to render <PayPalButtons /> component. Error: Window closed

As I have to fetch data to render the PayPalButton, it re-renders a couple times, and this error appears on the log:

Screenshot 2020-11-17 at 21 04 05

Is it something wrong with it? Should I care?

And is there a way to make it not re-render every time I change something on the createOrder or the onApprove function?

[Feature] Displaying LoadingIndicator until onApprove promise chain has finished

I am facing an issue where my server side implementation of paypal payments has to wait until the payment is captured AND then do some DB operations (writing transactions id to DB, manipulating order status, etc.). Therefore the capture method has to successfully finish first.

As I found out the capture method closes the paypal iframe on the client side whenever it finishes successfully. Leaving the client in a state of uncertainty while some backend operations are still running.

This lib provides the ability of implementing a loading indicator by accessing the script hook like
const [{ isPending }] = usePayPalScriptReducer(); [see docs here]. (By the way the docs actually do not show how to use the hook. I manually found it [here].)

Feature: It would be great to have the isPending state until the transaction is not only captured but the onApprove promise entirely finishes.

I have a workaround, but it would be much cleaner with the isPending script hook:

import React, { useState } from 'react';
import config from '@root/config';
import {  PayPalButtons,  PayPalScriptProvider,  usePayPalScriptReducer } from '@paypal/react-paypal-js';
import { Spinner } from 'appui';
import { StyledSpinnerContainer } from './styled';

const LoadingIndicator = ({ isLoading }) => {
  const [{ isPending }] = usePayPalScriptReducer();

  return (
    ((isLoading || isPending) && (
      <StyledSpinnerContainer>
        <Spinner />
      </StyledSpinnerContainer>
    )) ||
    ''
  );
};

const Paypal = ({ options, createOrder, onApprove, onError }) => {
  const [isLoading, setIsLoading] = useState(false);

  return (
    <PayPalScriptProvider
      options={{ ...options, components: 'buttons', 'client-id': config.paypalClientId }}
    >
      <LoadingIndicator {...{ isLoading }} />
      {!isLoading && (
        <PayPalButtons
          {...{
            style: { color: 'blue' },
            createOrder,
            onApprove: (...args) =>
              Promise.resolve()
                .then(() => setIsLoading(true))
                .then(() => onApprove(...args))
                .finally(() => setIsLoading(false)),
            onError,
          }}
        />
      )}
    </PayPalScriptProvider>
  );
};

export default Paypal;

[TOC] Adding TableOfContents in readme.md

๐Ÿš€ Feature Proposal

Adding table of contents in readme.md.

Motivation

The idea for Adding Table of Content to Readme.md to add additional enhancement and readability to the docs file.

Example

Down the line, if readme content increases it will be easy to hop between the docs too.

Weird implementation of `usePayPalScriptReducer`

So, if I want to just dispatch actions, without touching the state, I have to:

  const [, paypalDispatch] = usePayPalScriptReducer();

What is very ugly, and looks like a workaround, but actually it's the only way of doing it without bugging my linter.

What if we implement it as an object? So we can have the dispatch and the state inside the paypal object that we render using usePayPal? It would be much nicer to use, like:

const paypal = usePayPal

paypal.dispatch({
      type: "resetOptions",
      value: {
        currency: selectedCurrency,
      },
    })

And it would be nice to not have to pass the paypalScriptOptions every time we dispatch an action, as it would be available on the Provider already.

Improve Storybook Example with PayPal Marks radio buttons

The current radio buttons example works but could be improved to use the args and follow more React best practices.

Please update this component to use this structure:

return (
    <PayPalScriptProvider
        options={{
            "client-id": "sb",
            components: "buttons,marks,funding-eligibility",
        }}
    >
        {/* get the fundingSources list from args and dynamically create the list of radio buttons from it */}
        <FormWithRadioButtons {...args } onChange={onChange} />
        <PayPalButtons fundingSource={fundingSource} />
    </PayPalScriptProvider>
);
export const RadioButtons = RadioButtonTemplate.bind({});
RadioButtons.args = { fundingSources: [FUNDING.PAYPAL, FUNDING.CARD, FUNDING.PAYLATER] };

[Feature] Expose script error state

๐Ÿš€ Feature Proposal

The library should expose the failing state of the SDK script

Motivation

There's currently no indication in case the SDK script fails to load for any reason so there's no way for the application to figure out that the script has failed

Example

This information would be useful to have the ability to notify the app user that they should take some actions like reloading the page or checking their network connection

if the script fails to load loadScript rejects but
PayPalScriptProvider does not catch the rejected Promise

loadScript(state.options).then(() => {
if (isSubscribed) {
dispatch({ type: "setIsLoaded", value: true });
}

We can replace isLoaded state with a loading/loaded/error state. This would be a breaking change so we can first introduce a new field while keeping isLoaded for the backward compatibility

If you're interested I can take a stab at this

[Bug] unexpected token

๐Ÿž Not js syntax

"@paypal/react-paypal-js": "^4.0.0" in dist\react-paypal.node.js:286 unexpected token :
buttons?.current?.close();
_______ ^_______ ^

[Feature] Create a disabled state

So, right now, the only way to "disable" the button is to make it fail or not show it at all.

It would be good to have an isDisabled state, that would disable the button depending on what the user does. For example, when the user doesn't have anything on his cart, or when something is missing on his validation.

[Bug] Card Fields instead of Popup Checkout flow

Hi guys, didn't know if this qualified as a bug or not, but I'm getting some weird behaviour from the "Debit or Credit Card" button, or at least inconsistent with the examples shown in the Storybook.

I've implemented everything as suggested in the docs, using all the defaults and whenever I click on the "Debit or Credit Card" button I'm getting the following form:

Captura de Pantalla 2020-10-29 a la(s) 01 50 23

which animates in, instead of it taking me to the popup checkout flow as it does in the storybook example. (Shown below)

Captura de Pantalla 2020-10-29 a la(s) 01 52 19

I'm wondering if this is the intended behaviour and if so, how could I revert it to the popup flow shown in the storybook examples? I couldn't find any info about this on the documentation.

FYI, I'm running v1.0.3

Sorry if this is not the appropriate forum for this question and thanks in advance for any help.

TypeScript Issue - usually optional script options are required

usually only the client-id is required. but it seems all of the options are required in this package

TS2740: Type '{ 'client-id': string; intent: string; }' is missing the following properties from type
 'Required<InferProps<{ "buyer-country": Requireable<string>; "client-id": Validator<string>; 
commit: Requireable<string | boolean>; components: Requireable<string>; ... 12 more ...; 
vault: Requireable<...>; }>>': "buyer-country", commit, components, currency, and 11 more.
โ„น ๏ฝขwdm๏ฝฃ: Failed to compile.

how to reproduce:

  1. basic typescript setup
<PayPalScriptProvider options={{ 'client-id': 'some id', intent: 'authorize' }}></PayPalScriptProvider>

[Bug] Rerender Error - Uncaught Error: Window closed

๐Ÿž Describe the Bug

When the PayPalButtons Component is used in a Form which is again after state change the javascript console shows an error.

image

๐Ÿ”ฌ Minimal Reproduction

image

`

import React from "react";
import ToggleButton from '@material-ui/lab/ToggleButton';
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import {PayPalButtons, PayPalScriptProvider} from "@paypal/react-paypal-js";

const ShopView = () => {

const [amount, setAmount] = React.useState('0');

const handleAlignment = (event, newAlignment) => {

    if (newAlignment) {
        setAmount(newAlignment);
    }

};

return (<div>
    <PayPalScriptProvider options={{currency: "EUR", "client-id": ".... YOUR CLIENT ID GOES HEERE... "}}>


        <ToggleButtonGroup
            size="small"
            value={amount}
            exclusive
            onChange={handleAlignment}
            aria-label="text alignment"
        >
            <ToggleButton value="4" aria-label="left aligned">
                Amount 4 Euro
            </ToggleButton>
            <ToggleButton value="2" aria-label="centered">
                Amount 2 Euro
            </ToggleButton>
        </ToggleButtonGroup>


        <PayPalButtons/>


    </PayPalScriptProvider>

</div>)
}

 export default ShopView

`

๐Ÿ˜• Actual Behavior

When toggeling the the amounts an error is thrown. The error only ourres when I'm toggling fast.
Each time the paypal script gets reloaded. If I update the component state which will force a rerender before the Paypal script is loaded the error is thrown.

๐Ÿค” Expected Behavior

No Error is thrown in the JS Console

๐ŸŒ Environment

Software Version(s)
react-paypal-js 1.0.1
Browser Chrome
Operating System MacOS

โž• Additional Context

Add any other context about the problem here.

[Feature] Return custom content when the Button is not eligible

๐Ÿš€ Feature Proposal

When rendering the PayPal Button, we only want to render it when it's eligible. For the use case when it's not eligible, we currently render an empty div element. The internet has enough divs already. Let's render null instead by default for this use case.

Motivation

Avoid rendering unnecessary markup. Also, enable devs to show custom content when the buttons are unable to render due to ineligibility.

Example

<PayPalButton fundingSource={FUNDING.VENMO}>
    {/* this Alert component is just an example. It's not part of react-paypal-js */}
    <Alert message={ `You are not eligible for {FUNDING.VENMO}` } />
</PayPalButton>

We'd need to update this code to use the children prop and render that and if there are no children default to null: https://github.com/paypal/react-paypal-js/blob/main/src/components/PayPalButtons.tsx#L134-L140.

[Bug] Checkout button not appearing

๐Ÿž Describe the Bug

Hello,

I'm writing this message because I'm trying to set up a payment processing via Paypal. Unfortunately, I wanted to use your library but I can't.

When I put my button in my form, it is displayed once but the next time I reload the page, it is not displayed at all as a button in the form. And nowhere else.

However, I think I have followed the README that you propose. I have created my Sandbox account and my app on paypal. I simply copied, afterwards, the client-id of my app.

Thankyou for your help,

๐Ÿ”ฌ Minimal Reproduction

The first time I load the page on my browser, I have the button that appears
image

The second time, however, the button completely disappeared from the form.
image

However, in the "network" field of my browser, I see the following error:
image

๐Ÿ˜• Actual Behavior

Here is the code I brought to try to implement the checkout button
<PayPalScriptProvider options={{ 'client-id': 'AQul7weTY7xqBj4Yhp6hGDnRKONiHEfKe0itCC7IrSMlK3eQzqIGoUwPb8cP7uQa-2YF4B72xXIxrRx9', }} > <PayPalButtons style={{ layout: 'horizontal' }} /> </PayPalScriptProvider>

๐Ÿค” Expected Behavior

A clear and concise description of what you expected to happen.

๐ŸŒ Environment

Software
react-paypal-js
Safari, Mozilla Firefox, Google Chrome

โž• Additional Context

Add any other context about the problem here.

Impossible to style top-level container

๐Ÿž Describe the Bug

The button gets rendered with a top-level div with no attributes. There's no direct way to either place a class name or otherwise style the parent div.

๐Ÿ”ฌ Minimal Reproduction

Install the React PP buttons plugin and try setting a custom button width.

๐Ÿ˜• Actual Behavior

Impossible to specify a class name or otherwise style the top-level div.

๐Ÿค” Expected Behavior

An explicit way to set a class name or style the top-level div with tools such as styled components and the like.

๐ŸŒ Environment

Software Version(s)
react-paypal-js 3.0.3
Browser Chrome
Operating System iOS

โž• Additional Context

Add any other context about the problem here.

paypal button debit or credit card shows create paypal account, how can hide it?

Hi, I have been worked on this integration but I have a problem, how can render the paypal button for debit or credit card without show the create paypal account seccion. I just want like the example on your storybook.
Captura de Pantalla 2021-02-16 a la(s) 1 18 22 p m

But instead of that I get a form with a seccion for create a new paypal account and is required, how can disable that seccion or making not required.

Captura de Pantalla 2021-02-16 a la(s) 1 10 10 p m

I hope you can help me, thanks.

[Bug] onError() not being called in Inline card form

๐Ÿž Describe the Bug

Can't handle error when there is invalid data in the form. If the user insert invalid data, like an postal code = "111111111111111111", it just shows a message in console and "onError()" is not called, if I try to .catch() in my API calls to server-side when capturing, does not work too.

๐Ÿ”ฌ Minimal Reproduction

In the card form, insert a zip code like: "111111111111111111", click in Continue.

๐Ÿ˜• Actual Behavior

console logs returns:

ppxo_inline_guest_unhandled_error
Object { handledErrors: (1) [โ€ฆ], unhandledErrors: [], inline_guest_version: "2.12.7", timestamp: 1617132129038, windowID: "36382bffb2", pageID: "5e8b1ce512", referer: "www.sandbox.paypal.com", host: "www.sandbox.paypal.com", path: "/smart/card-fields", env: "sandbox", โ€ฆ }
beaver-logger.js:237:35
s beaver-logger.js:237
u beaver-logger.js:396
p beaver-logger.js:443
error index.js:90
H submitInlineGuestForm.js:339
value Redux
H submitInlineGuestForm.js:327
Redux 2
errorBoundaryMiddleware errorBoundaryMiddleware.js:15
ignoreNoopActionMiddleware noopActionCreator.js:25
Redux 2
r runtime.js:62
l runtime.js:296
t runtime.js:114

๐Ÿค” Expected Behavior

onError() be called, so I could manage the error and display an message showing to the user what field is wrong.

๐ŸŒ Environment

Software Version(s)
react-paypal-js 5.1.1
Firefox 87.0
Ubuntu 20.04.2 LTS

[Feature] Use TypeScript

๐Ÿš€ Feature Proposal

Update this library to use TypeScript.

Motivation

Several developers have requested TypeScript types for this library. We could solve that problem in a few different ways. One way is to convert this codebase to TypeScript and include types for all publicly exported functions.

Here's a good doc about the advantages of TypeScript.

Related PRs

#30 - @camsjams shared some really helpful types here which would be a good starting point: https://gist.github.com/camsjams/b843637df7a30c260b341daf97a96d60
#53

Additional Info

The current use of PropTypes is powering the Storybook docs: https://paypal.github.io/react-paypal-js/?path=/docs/example-paypalbuttons--default. We want to continue supporting PropTypes like this with using TypeScript. That may be one of the more challenging parts of the migration.

Any objections to migrating to TypeScript?

Any volunteers to take this work up?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.