Search code examples
reactjspaypalreact-propsreact-statereact-lifecycle

How do I implement PayPal in React with different payment options?


I'm trying to grasp how to setup a PayPal method within React properly.

I've got three cards with each a different payment amount and cannot figure out how to pass props from my Class to the PayPal Function.

All i need is the Cost and Description to change the purchase_items[] array within the PayPal Function right?

FYI: I followed the tutorial from PayPal mainly and can make a payment, just not with the Different Costs and Descriptions i want.

PayPal.js

import React, {useRef, useEffect} from 'react';
export default function PayPal(){
const [paid, setPaid] = React.useState(false);
const paypal = useRef()

useEffect(()=> {
    window.paypal.Buttons({
        createOrder: (data, actions, err) => {
            return actions.order.create({
                intent: "CAPTURE",
                purchase_units: [
                    {
                        description: "Dummy",
                        amount: {
                            currency_code: "AUD",
                            value: 15.49
                        }
                    }
                ]
            })
        },
        onApprove: async(data, actions) => {
            const order = await actions.order.capture()
            setPaid(true);
            console.log(order);
        },
        onError: (err) => {
            console.error(err);
        }
    }).render(paypal.current)
}, [])

return(
    <div className="Processing">
        {paid ?(
            // If the payment is made
            <div>Payment successful!</div>
        ):(
            // If any error occurs
            <div>Error Occurred in processing payment! Please try again.</div>
        )}
    </div>
);
}

Purchases.js

import React, {Component} from 'react';
import PayPal from '../../Components/PayPal.js';
import {Card, CardActions} from '@material-ui/core';

class Purchases extends Component(){

constructor(props){
    super(props);
    this.state ={
        cost: 5.00,
        checkout: false,
        desc: "Test"
    };
}

setCheckout = (bool) =>{
    this.setState({checkout: bool});
};

handlePayment = (price, info) =>{
    this.setState(state => ({cost: price}, {desc: info}));
};

render(){
    return (
        <div className="Purchase">
            {this.state.checkout ?(
                <PayPal cost={this.state.cost} desc={this.state.desc}/>
            ) : (      
                <div>
                <Card>
                    Payment 1 = $1 AUD
                    <CardActions
                    onClick={() =>{
                        setCheckout(true);
                        this.handlePayment(1.00, "Purchase Plan 1");
                    }}
                />
                </Card>
                <Card>
                    Payment 2 = $2 AUD
                    <CardActions
                    onClick={() =>{
                        setCheckout(true);
                        this.handlePayment(2.00, "Purchase Plan 2");
                    }}
                    />
                </Card>
                <Card>
                    Payment 3 = $3 AUD
                    <CardActions
                        onClick={() =>{
                            setCheckout(true);
                            this.handlePayment(3.00, "Purchase Plan 3");
                        }}
                    />
                </Card>
            </div>
            )}
        </div>
    );
}
}
export default Purchases;

Solution

  • I'm not familiar with the PayPal API, but I can help you with the React side of things.

    <PayPal cost={this.state.cost} desc={this.state.desc}/>
    

    You are calling the PayPal component with props cost and desc, so we need to make the PayPal component accept these props and use them.

    The ref const paypal = useRef() just creates a ref which you can attach to a component. It doesn't do anything on its own as paypal.current is never anything but undefined. However it seems like the argument of render() should be a selector rather than an element.

    Right now you just have "paid" and "error", but I think you'll want a third render situation for "pending".

    export default function PayPal({ cost, desc }) {
      const [completed, setCompleted] = React.useState(false);
      const [paid, setPaid] = React.useState(false);
    
      useEffect(() => {
        window.paypal?.Buttons({
            createOrder: (data, actions, err) => {
              return actions.order.create({
                intent: "CAPTURE",
                purchase_units: [
                  {
                    description: desc, // from props
                    amount: {
                      currency_code: "AUD",
                      value: cost // from props
                    }
                  }
                ]
              });
            },
            onApprove: async (data, actions) => {
              const order = await actions.order.capture();
              setPaid(true);
              setCompleted(true);
              console.log(order);
            },
            onError: (err) => {
              setCompleted(true);
              console.error(err);
            }
          })
          .render("#paypal-button-container");
      }, [cost, desc]);
    
      return (
        <div className="Processing">
          <div id="paypal-button-container" />
          {completed &&
            (paid ? (
              // If the payment is made
              <div>Payment successful!</div>
            ) : (
              // If any error occurs
              <div>Error Occurred in processing payment! Please try again.</div>
            ))}
        </div>
      );
    }
    

    There are some syntax errors in your Purchases component. I would recommend using a function component since they are generally easier but that doesn't matter. I actually had fun playing with this so I made a bunch of changes!

    import React from "react";
    import {Card, CardActions, CardContent, Typography, Button} from "@material-ui/core";
    import PayPal from "./PayPal";
    
    const Purchases = () => {
      const [state, setState] = React.useState({
        cost: 5.0,
        checkout: false,
        desc: "Test"
      });
    
      // helper to avoid repetition between the cards
      const renderCard = (title, desc, cost) => {
        return (
          <Card className="purchase-card">
            <CardContent>
              <Typography variant="h5" component="h2">
                {title}
              </Typography>
            </CardContent>
            <CardActions>
              <Button
                onClick={() =>
                  setState({
                    cost,
                    desc,
                    checkout: true
                  })
                }
                color="primary"
              >
                ${cost} AUD
              </Button>
            </CardActions>
          </Card>
        );
      };
    
      console.log(state);
      return (
        <div className="Purchase">
          {state.checkout ? (
            <div>
              <Typography variant="h2">Checkout</Typography>
              <Typography variant="h4">
                {state.desc}: ${state.cost} AUD
              </Typography>
              <Button
                onClick={() => setState((prev) => ({ ...prev, checkout: false }))}
              >
                Change Plan
              </Button>
              <PayPal cost={state.cost} desc={state.desc} />
            </div>
          ) : (
            <div className="card-group">
              {renderCard("Payment 1", "Purchase Plan 1", 1)}
              {renderCard("Payment 2", "Purchase Plan 2", 2)}
              {renderCard("Payment 3", "Purchase Plan 3", 3)}
            </div>
          )}
        </div>
      );
    };
    export default Purchases;
    

    Code Sandbox Link