Paypal-checkout-components: How to pass dynamic price in React to checkout button?

Created on 18 May 2017  Â·  12Comments  Â·  Source: paypal/paypal-checkout-components

Hi, I want to know how I can pass this.state.price into the payment function. I am using a create-react-app application and when I pass this.state.price to the function I get an error. I suppose it is something comming from the bindings but, when I switch syntax to es6 it does not work at all.

let payment = function () { 
        const env = this.props.env;
            const client = this.props.client;

            return paypal.rest.payment.create(env, client, {
                transactions: [
                    {
                        amount: {total: this.state.price, currency: 'EUR'}
                    }
                ]
            })
        }

Thanks!

Most helpful comment

This works! Thanks again! But when I saw the console it was full of errors: "Error: Detected close during init"

Error: Detected close during init↵ at http://localhost:3000/static/js/bundle.js:93986:89

Even though it works... I suppose it would be better not the have them...

All 12 comments

You can't use this.state inside the = function() {...} create payment call without binding it to your React component, and if you use es6 syntax of = () => {...} then you auto-bind to your React component but that breaks the needed this.props.env and this.props.client, as well as more parts of the paypal.rest.payment.create(...) call.

Do something like this

const price = this.state.price;
let payment = function () { 
        const env = this.props.env;
            const client = this.props.client;

            return paypal.rest.payment.create(env, client, {
                transactions: [
                    {
                        amount: {total: price, currency: 'EUR'}
                    }
                ]
            })
        }

I noticed the same issue and got it worked with the code example here.

Worked! But now I have an issue rendering the right price. Because the price is dynamic and changes the state depending on user input, somehow the paypal button total does not change the total. Here is the scenario:

  1. Page loads -> Button receives the right price
  2. Cancel payment popup
  3. Change the price (the state changes, I've checked it in React Dev tools)
  4. Checkout again -> Paypal Popup renders the old price

Any suggestions?
Thanks!

You didn't show the code of how you actually render the button, but if you are doing it by calling the paypal.Button.render(...) function then that is the behavior you should expect.

Look at the link I provided above, notice how it's all in a function called renderButton()? So at the top of my file I have...

import React from 'react';
import ReactDOM from 'react-dom';
import paypal from 'paypal-checkout';

and in my render function...

render() {
   <div>
     {this.renderButton()}
  </div>
}

That will ensure you are using the paypal.Button React component, and that you are triggering a re-render of the button when the state is updated.

This works! Thanks again! But when I saw the console it was full of errors: "Error: Detected close during init"

Error: Detected close during init↵ at http://localhost:3000/static/js/bundle.js:93986:89

Even though it works... I suppose it would be better not the have them...

Detected close during init occurs when the button is destroyed as it is being created. Is it possible that your React code is triggering a re-render? If you can share the full code I can help debug.

This should ideally be avoided, since creating iframes is a relatively expensive operation.

@bluepnume It is really an expensive operation since I see the button kind of reloading and that is not great of a UX. But after all that is the way the "Button" works. I am sharing the code of the function that renders the button, but as for my case I think I will be using a server side request to the API because really the iframe is not that good of a solution for my case. The price on my behalf is changing on every selected/changed input(and I have 5 of them) that would trigger for example 5 times(or more) the iframe to reload. The solution I think would be to create a route on my express backend where I can have the logic with my API credentials. Then create a button like "Pay now" and bind the button to call this route and POST some data such as price, etc.

```
renderButton() {

        let total = this.state.price;


        const opts = {
        env: 'production',
        client: {
            sandbox: 'Ac8UHFXqHvz_toL1a2wNRllVHfHMhVFQYFVTcLhMZHVtcmgeYKPk011lbWaiGZoObzyocefFaBSZG6oB',
            production: 'AYUrd_5ZUpACjK9F2Z7fPFIutyLllf6008drTABbqoO_iTAMQAYv7i3RBrbm4JzqBJvqIu-C-O-aZccc'
        },
        commit: true, // Show a 'Pay Now' button
        payment: function createPayment() {

            // eslint-disable-next-line react/prop-types
            const paymentId = paypal.rest.payment.create(this.props.env, this.props.client, {
            transactions: [{
                amount: {
                currency: 'EUR',
                total,
                },
                description: 'League of Legends Boosting services',
            }],
            });

            paymentId.then((res) => {
                console.log("Processed")
            }).catch((err) => {
                console.error("Error")
            });

            return paymentId;
        },
        onAuthorize: (data, actions) => actions.payment.execute().then((payment) => {
            console.log("Success")
            // paymentCompleted.call({
            // cartId: this.props.cart._id,
            // state: payment.state,
            // id: payment.id,
            // }, (err, res) => {
            // if (err) {
            //     alert(err)
            // } else {
            //    alert("Success")
            //    console.log(res)
            // }
            // });
        }).catch((err) => {
            console.error("You had an error", { err, items, total });
        }),
        };

        // Odd way to do this, based on https://github.com/paypal/paypal-checkout/issues/149
        const ReactButton = paypal.Button.driver('react', { React, ReactDOM });
        return (
        <ReactButton
            env={opts.env}
            client={opts.client}
            payment={opts.payment}
            commit={opts.commit}
            onAuthorize={opts.onAuthorize}
        />
        );
    }

```

Before you abandon the iframe, I think I can solve this fairly easily by implementing a correct shouldComponentUpdate() over here: https://github.com/krakenjs/xcomponent/blob/master/src/drivers/react.js

I'll give that a go tomorrow and share an updated checkout.js script. I think it should solve all of these re-rendering problems.

You may also need to change your code a little to calculate the new prices inside the payment() function rather than in render(), but I'll let you know.

@bluepnume Thank you so much! Looking forward to your solution! Btw I was thinking about an idea when one can pass the price as a prop but not sure if this would be possible.

@gopchi can you clear your cache and try with 4.0.75? In my local testing this now works without any re-renders of the iframe.

I've also updated https://github.com/paypal/paypal-checkout/blob/master/demo/react.htm with a best-practice example.

@bluepnume Works as a charm! Thanks a lot, really! :)

Great! :)

add this input in the html area of your code "`
and add that modify your payment function like so;

Was this page helpful?
0 / 5 - 0 ratings