PayPal Express Checkout with React JS and PHP RESTful
Wall Script
MailxEngine
Follow Me:
Monday, April 16, 2018

PayPal Express Checkout with React JS and PHP RESTful

There are several payment options available in the market today. PayPal checkout option is one among them. In my previous articles, I have already discussed few payment options - Braintree PayPal using PHP, Payment system and PayPal Express Checkout with PHP and MySQL. These are the most viewed and trending articles till this day. Today, let’s see how the PayPal Express Checkout works with React JS.

PayPal Express Checkout with React JS and PHP RESTful


Live demo sandbox PayPal credentials for testing username: [email protected] password: 9lessons
Live Demo


Part One: Login with Facebook and Google using ReactJS and RESTful APIs

Database Design
To build the PayPal checkout ordering system, you have to create the following tables. Social login user table has been present in the previous article.

Products
Product table contains all the product details. You can extend this table based on your needs.
CREATE TABLE products(
pid int AUTO_INCREMENT PRIMARY KEY,
product varchar(300),
product_image varchar(200),
price float,
currency varchar(10)
);

Orders
Order table contains all the user order details with PayPal payment ids.
CREATE TABLE orders(
oid int AUTO_INCREMENT PRIMARY KEY,
pid_fk int,
uid_fk int,
payerID varchar(500),
paymentID varchar(500),
token varchar(500),
created int(11)
);


Home.js
Let continue the provious article. Here the user session storage data is present, storing the data with setState. Using user id and token you can access other informaiton like products and order details.
import React, {Component} from 'react';
import './Home.css';
import {Redirect} from 'react-router-dom';
class Home extends Component {

constructor(props){
    super(props);
    this.state = {
       name:'',
       redirect: false,
   };
}

componentDidMount() {
     let data = JSON.parse(sessionStorage.getItem('userData'));
     console.log(data);
     this.setState({name: data.userData.name})
}

render() {

if(!sessionStorage.getItem('userData') || this.state.redirect){
    return (<Redirect to={'/'}/>)
}

return (
<div >
Welcome {this.state.name}
</div>
);
}
}
export default Home;

Lets start
You have to create common components for multiple uses. In this project, we are commonly using project title and log out.

Title.js
This component contains project Title and log out functionality. Using the props you can disturbute the data. You can import this component in multiple pages.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import "./Title.css";

class Title extends Component {
constructor(props) {
   super(props);
   this.logout = this.logout.bind(this);
   this.state = {
     redirect: false
   };
}

logout() {
   sessionStorage.setItem("userData", "");
   sessionStorage.clear();
   this.setState({ redirect: true });
}

render() {
if (this.state.redirect) {
   return <Redirect to={"/"} />;
}
return (
   <div className="row">
   <div className="medium-12 columns">
   <ul className="right">
   <li>
      <a href="/orders">Orders</a>
   </li>
   <li>
   <a href="#" onClick={this.logout}>
      Logout
   </a>
   </li>
   </ul>
   <h2>Welcome {this.props.name}</h2>
   </div>
   </div>
);
}
}

export default Title;


ProductsList.js
Component for displaying all of the product details. Here the product data is calling from React props constuctor. onClick button this.props.checkOut points to parent function, you will find this in next step.
import React, { Component } from "react";
import "./ProductsList.css";
class ProductsList extends Component {
constructor(props) {
   super(props);
}

render() {
let productList = this.props.productsData.map(function(productData, index) {
return (
   <div className="row list" key={index}>
   <div className="medium-3 columns">
   <img
src={
"https://demos.9lessons.info/PHP-PayPal-ExpressCheckout/img/" +
productData.product_img
}
/>
</div>
   <div className="medium-3 columns">{productData.product}</div>
   <div className="medium-3 columns">$ {productData.price}</div>
   <div className="medium-3 columns">
<button className="button" value={productData.pid}
onClick={this.props.checkout}>
   Order
</button>
</div>
</div>
);
}, this);

return <div>{productList}</div>;
}
}

export default ProductsList;

Importing Components and Data Binding with RESTful APIs

Home.js
Imported Title and ProductList components with PostData URL fetch provider. Function getProducts() fetch the data from products API based on the login user id and token.
import React, { Component } from "react";
import "./Home.css";
import { Redirect } from "react-router-dom";
import { PostData } from "../../services/PostData";
import Title from "../Title/Title";
import ProductsList from "../ProductsList/ProductsList";
class Home extends Component {
constructor(props) {
   super(props);
   this.state = {
      name: "",
      redirect: false,
      products: [],
      pid: ""
   };

this.getProducts = this.getProducts.bind(this);
this.checkout = this.checkout.bind(this);
}

componentDidMount() {
   let data = JSON.parse(sessionStorage.getItem("userData"));
   this.getProducts(data.userData);
   this.setState({ name: data.userData.name });
}

getProducts(userData) {
let postData = { uid: userData.uid, token: userData.token };
   console.log(postData);
   PostData("products", postData).then(result => {
      let responseJson = result;
      this.setState({ products: responseJson.products });
   });
}

checkout(e) {
   let pid = e.target.getAttribute("value");
   sessionStorage.setItem("pid", pid);
   this.setState({ pid: pid });
}

render() {
if (!sessionStorage.getItem("userData") || this.state.redirect) {
   return <Redirect to={"/"} />;
}

if (this.state.pid > 0) {
   return <Redirect to={"/checkout"} />;
}

return (
<div className="row body">
<Title name={this.state.name} />
<ProductsList productsData={this.state.products}
checkout={this.checkout}/>
</div>
);
}
}
export default Home;

Function checkOut() changes the this.state.pid value base on the product value attribute. In the render method, if the pid value is present, it is redirecting to /checkout page.

Working with PayPal and Checkout Page
You have to install PayPal checkout component for connecting PayPal API.

Install PayPal Express Checkout plugin for React.
$ npm install react-paypal-express-checkout --save

Paypal.js
Create a custom component with Paypal Express Button plugin. You have to configure your PayPal sandbox and production merchent IDs for more inform check PayPal Express Checkout with PHP and MySQL. On payment success, it will call onSuccess with createOther method. This PHP API will validate with payment data like payerID, paymentToken etc.. Valid call changes the redirect state value to true and it redirects to orders page.
import React, { Component } from "react";
import "./PayPal.css";
import PaypalExpressBtn from "react-paypal-express-checkout";
import { PostData } from "../../services/PostData";
import { Redirect } from "react-router-dom";
class PayPal extends Component {
constructor(props) {
   super(props);
   this.state = {
      redirect: false
   };
   this.createOrder = this.createOrder.bind(this);
}

createOrder(payment) {
let postData = {
   uid: this.props.userData.uid,
   token: this.props.userData.token,
   payerID: payment.payerID,
   paymentID: payment.paymentID,
   paymentToken: payment.paymentToken,
   pid: this.props.pid
};

PostData("createOrder", postData).then(result => {
   let responseJson = result;
   if (responseJson.status === "true") {
      this.setState({ redirect: true });
   }
});
}

render() {
if (this.state.redirect) {
   return <Redirect to={"/orders"} />;
}

const onSuccess = payment => {
   this.createOrder(payment);
};

const onCancel = data => {
   console.log("The payment was cancelled!", data);
};

const onError = err => {
console.log("Error!", err);
};

let env = "sandbox"; // Change to "production" for live
let currency = "USD";
let total = this.props.value;

const client = {
sandbox"AQwoZAAHsmA5vBLj_mZffS3NWJjNJODewuV2WakPm-BQilgsawTtnbLvWHNC73idcfiaHBOjaeTDkAS8",
production: "<insert production client id>"
};

return (
<PaypalExpressBtn
env={env}
client={client}
currency={currency}
total={total}
onError={onError}
onSuccess={onSuccess}
onCancel={onCancel}
/>
);
}
}

export default PayPal;
For production you can change the let evn value to "production". Use my sandbox token for testing.

CheckOut.js
Checkout page for displying more details about the selected product. Imported PayPal component and set attributes values like product value, pid and userData.
import React, { Component } from "react";
import "./Checkout.css";
import { Redirect } from "react-router-dom";
import { PostData } from "../../services/PostData";
import Title from "../Title/Title";
import PayPal from "../PayPal/PayPal";
class Checkout extends Component {
constructor(props) {
   super(props);
   this.state = {
      name: "",
      redirect: false,
      pid: "",
      product: [],
      userData: []
};
}

componentDidMount() {
let data = JSON.parse(sessionStorage.getItem("userData"));
this.getProductData(data.userData);
this.setState({ name: data.userData.name });
this.setState({ userData: data.userData });
}

getProductData(userData) {
let pid = sessionStorage.getItem("pid");
let postData = { uid: userData.uid, token: userData.token, pid: pid };

PostData("getProduct", postData).then(result => {
   let responseJson = result;
   this.setState({ product: responseJson.product });
});
}

render() {
if (!sessionStorage.getItem("userData") || this.state.redirect) {
   return <Redirect to={"/"} />;
}

return (
<div className="row body">
<Title name={this.state.name} />
<h4>Checkout</h4>
<div className="row">
<div className="medium-4 columns">Product</div>
<div className="medium-4 columns">Name</div>
<div className="medium-4 columns">Price</div>
</div>
<div className="row">
<div className="medium-4 columns">
<img
src={
"https://demos.9lessons.info/PHP-PayPal-ExpressCheckout/img/" +
this.state.product.product_img
}
/>
</div>
<div className="medium-4 columns">{this.state.product.product}</div>
<div className="medium-4 columns">
{this.state.product.price}
<PayPal
value={this.state.product.price}
pid={this.state.product.pid}
userData={this.state.userData}
/>
</div>
</div>
</div>
);
}
}
export default Checkout;#

Orders.js
Orders page this contains all of your successful orders. Function getOrders fetch orders form RESTful apis, component OrdersList renders based on the state.orders array.
import React, { Component } from "react";
import "./Orders.css";
import { Redirect } from "react-router-dom";
import { PostData } from "../../services/PostData";
import Title from "../Title/Title";
import OrdersList from "../OrdersList/OrdersList";

class Orders extends Component {
constructor(props) {
   super(props);
   this.state = {
      name: "",
      redirect: false,
      orders: []
};

this.getOrders = this.getOrders.bind(this);
}

componentDidMount() {
   let data = JSON.parse(sessionStorage.getItem("userData"));
   this.getOrders(data.userData);
   this.setState({ name: data.userData.name });
}

getOrders(userData) {
let postData = { uid: userData.uid, token: userData.token };
PostData("orders", postData).then(result => {
   let responseJson = result;
   this.setState({ orders: responseJson.orders });
});
}

render() {
if (!sessionStorage.getItem("userData") || this.state.redirect) {
   return <Redirect to={"/"} />;
}

return (
<div className="row body">
<Title name={this.state.name} />
<OrdersList ordersData={this.state.orders} />
</div>
);
}
}

export default Orders;

OrderList.js
Component that display the user payment orders.
import React, { Component } from "react";
import "./OrdersList.css";
class OrdersList extends Component {
constructor(props) {
   super(props);
}

render() {
let ordersList = this.props.ordersData.map(function(orderData, index) {
return (
<div clasName="row orderList" key={index}>
<div className="medium-3 columns">{orderData.oid}</div>
<div className="medium-3 columns">{orderData.product}</div>
<div className="medium-3 columns">${orderData.price}</div>
<div className="medium-3 columns">{orderData.created}</div>
</div>
);
}, this);

return <div>{ordersList}</div>;
}
}
export default OrdersList;

Working with PHP RESTful
This project is the combination of Login with Facebook and Google using ReactJS and PayPal Express Checkout with PHP and MySQL


index.php
Included following POST method calls. Please check Login with Facebook and Google using ReactJS
<?php
/* ### Srinivas Tamada ### */
/* ### https://www.9lessons.info ### */
require 'config.php';
require 'Slim/Slim.php';

\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();

$app->post('/signup','signup'); /* User Signup */
$app->post('/products','products'); /* User Products */
$app->post('/orders','orders'); /* User Orders */
$app->post('/getProduct','getProduct'); /* get proudct */
$app->post('/createOrder','createOrder'); /* Create Order */
$app->run();
......
......
......
......
?>

products
Getting product details for products table. If you want you can extend to add pagination.
function products() {
$request = \Slim\Slim::getInstance()->request();
$data = json_decode($request->getBody());
$uid=$data->uid;
$token=$data->token;
$system_token = apiToken($uid);
if($token == $system_token){
   $db = getDB();
   $sql = "SELECT * FROM products";
   $stmt = $db->prepare($sql);
   $stmt->execute();
   $products = $stmt->fetchALL(PDO::FETCH_OBJ);
   if($products){
      $products = json_encode($products);
      echo '{"products": ' .$products . '}';
   } else {
      echo '{"error":{"text":"No data available"}}';
   }
}
else{
echo '{"error":{"text":"No access"}}';
}
}


orders
User order details based on the post input uid.
function orders() {
$request = \Slim\Slim::getInstance()->request();
$data = json_decode($request->getBody());
$uid=$data->uid;
$token=$data->token;
$system_token = apiToken($uid);
if($token == $system_token){
   $db = getDB();
   $sql = "SELECT * FROM orders O, products P WHERE O.pid_fk = P.pid AND uid_fk=:uid order by O.oid DESC; ";
   $stmt = $db->prepare($sql);
   $stmt->bindParam("uid", $uid,PDO::PARAM_INT);
   $stmt->execute();
   $orders = $stmt->fetchALL(PDO::FETCH_OBJ);
   if($orders){
      $orders = json_encode($orders);
      echo '{"orders": ' .$orders . '}';
   } else {
      echo '{"error":{"text":"No data available"}}';
   }
}
else{
   echo '{"error":{"text":"No access"}}';
}
}


getProduct
Getting individual product details based on product id.
function getProduct() {
$request = \Slim\Slim::getInstance()->request();
$data = json_decode($request->getBody());
$uid=$data->uid;
$token=$data->token;
$pid=$data->pid;
$system_token = apiToken($uid);
if($token == $system_token){
   $db = getDB();
   $sql = "SELECT * FROM products WHERE pid=:pid";
   $stmt = $db->prepare($sql);
   $stmt->bindParam("pid", $pid,PDO::PARAM_STR);
   $stmt->execute();
   $product = $stmt->fetch(PDO::FETCH_OBJ);
   if($product){
      $product = json_encode($product);
      echo '{"product": ' .$product . '}';
   } else {
      echo '{"error":{"text":"No data available"}}';
   }
}
else{
echo '{"error":{"text":"No access"}}';
}
}


createOrder
Creating order for successful payments. This will call internal CURL actions function paypalCheck.
function createOrder() {
$request = \Slim\Slim::getInstance()->request();
$data = json_decode($request->getBody());
$uid=$data->uid;
$token=$data->token;
$pid=$data->pid;
$payerID=$data->payerID;
$paymentToken=$data->paymentToken;
$paymentID=$data->paymentID;
$system_token = apiToken($uid);
if($token == $system_token){
if(paypalCheck($paymentID, $pid, $payerID, $paymentToken, $uid)){
   echo '{"status": "true" }';
} else {
   echo '{"error":{"text":"No data available"}}';
}
}
else{
   echo '{"error":{"text":"No access"}}';
}
}

paypalCheck internal function
Using CURL methods authenticating PayPal transcation for more understanding check PayPal Express Checkout with PHP and MySQL.
function paypalCheck($paymentID, $pid, $payerID, $paymentToken, $uid){
$ch = curl_init();
$clientId = PayPal_CLIENT_ID;
$secret = PayPal_SECRET;
curl_setopt($ch, CURLOPT_URL, PayPal_BASE_URL.'oauth2/token');
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, $clientId . ":" . $secret);
curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials");
$result = curl_exec($ch);
$accessToken = null;
if (empty($result)){
   return false;
}
else {
   $json = json_decode($result);
   $accessToken = $json->access_token;
   $curl = curl_init(PayPal_BASE_URL.'payments/payment/' . $paymentID);
   curl_setopt($curl, CURLOPT_POST, false);
   curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
   curl_setopt($curl, CURLOPT_HEADER, false);
   curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
   curl_setopt($curl, CURLOPT_HTTPHEADER, array(
      'Authorization: Bearer ' . $accessToken,
      'Accept: application/json',
      'Content-Type: application/xml'
   ));
$response = curl_exec($curl);
$result = json_decode($response);
$state = $result->state;
$total = $result->transactions[0]->amount->total;
$currency = $result->transactions[0]->amount->currency;
$subtotal = $result->transactions[0]->amount->details->subtotal;
$recipient_name = $result->transactions[0]->item_list->shipping_address->recipient_name;
curl_close($ch);
curl_close($curl);
$product = getProductData($pid);
if($state == 'approved' && $currency == $product->currency && $product->price == $subtotal){
   updateOrder($pid, $payerID, $paymentID, $paymentToken, $uid);
   return true;
}
else{
   return false;
}
}
}

updateOrder internal function
Insert order details with payment details.
function updateOrder($pid, $payerID, $paymentID, $token,$uid)
{
if(paymentCheck($paymentID) < 1 && $uid > 0){
   $db = getDB();
   $stmt = $db->prepare("INSERT INTO orders(uid_fk, pid_fk, payerID, paymentID, token, created ) VALUES (:uid, :pid,:payerID, :paymentID, :token, :created)");
   $stmt->bindParam("paymentID", $paymentID, PDO::PARAM_STR) ;
   $stmt->bindParam("payerID", $payerID, PDO::PARAM_STR) ;
   $stmt->bindParam("token", $token, PDO::PARAM_STR) ;
   $stmt->bindParam("pid", $pid, PDO::PARAM_INT) ;
   $stmt->bindParam("uid", $uid, PDO::PARAM_INT) ;
   $created = time();
   $stmt->bindParam("created", $created, PDO::PARAM_INT) ;
   $stmt->execute();
   $db=null;
   return true;
}
else{
   return false;
}
}

paymentCheck internal function
Using thie method cross checking the payment is preset are not.
function paymentCheck($paymentID)
{
   $db = getDB();
   $stmt = $db->prepare("SELECT * FROM orders WHERE paymentID=:paymentID");
   $stmt->bindParam("paymentID", $paymentID, PDO::PARAM_STR) ;
   $stmt->execute();
   $count = $stmt->rowcount();
   $db=null;
   return $count;
}

Run this project React
$ git clone https://github.com/srinivastamada/react-login-paypal.git
$ cd react-login-paypal
$ yarn start

For project deployment on Google firebase read Create an Angular 2 CLI App with Firebase Social Authentication Facebook, Google, Github and Twitter
Was this article helpful?
Thanks! Your feedback helps us to improve 9lessons.info


19 comments:

  1. Are you using webpack for reactjs ?

    ReplyDelete
  2. Amazing tips which improve my knowledge

    ReplyDelete
  3. Great Blog Thanks for sharing and keep rocking

    ReplyDelete
  4. Good post sir it's very useful

    ReplyDelete
  5. Thanks for sharing.
    Nice article got more information about this thanks a lot!

    ReplyDelete
  6. Really great and useful Post with working code.

    ReplyDelete
  7. amazing post, thanks for sharing this article. i am truly motivated by you for blogging
    Thanks Again...

    ReplyDelete
  8. I’m really happy to say it was an interesting post to read. I learned new information from your article, you are doing a great job. Continue

    ReplyDelete
  9. Amazing blog,superb description i always get something new here

    ReplyDelete

Make in India