PayPal Checkout

In February 2019, the PayPal Checkout API received a much needed facelift that brought massive improvements to the developer experience. The new v2 API has detailed documentation on par with Stripe and offers one of the smoothest paths to start accepting payments and/or subscriptions in a progressive web app. The following lesson will show you how to start accepting payments entirely from your frontend JavaScript code with PayPal Checkout with your choice of Angular, React, and Vue.

If you’re looking to implement a fullstack payment solution, check out the Stripe Payments Master Course. Ironically, you can pay for it with PayPal if you prefer 🤷. Both PayPal and Stripe share similar APIs, so concepts from the course overlap both APIs.

Live Demo

The button below is the actual live implementation used for Fireship (Angular Elements). Try it 👇

Initial Setup

PayPal Credentials

Create an new REST API app from the PayPal developer applications screen. PayPal provides two sets of API keys. The sandbox keys are used for testing, while the live keys are used to process real payments.

Each set of keys contains a client ID and secret. For basic checkout integrations, you only need the Client ID and can capture charges entirely from the frontend. Optionally, you can use the secret key to capture charges on a backend server (never expose the secret key in the frontend code or a public git repo).

PayPal API credentials

PayPal API credentials

Testing

PayPal provides testing accounts that you can use to make mock transactions. Change the password to something you’ll remember and use it to test out your first payment.

Mock PayPal accounts for payment testing

Mock PayPal accounts for payment testing

Angular

Generate the App

Create a new Angular project with the CLI.

command line
ng new myApp

Add the PayPal Script Tag

In Angular, we can simply include the script in the head of the HTML. Replace the value of the Client ID.

file_type_html index.html
<head>

  <!-- ... other stuff -->
  <script
    src="https://www.paypal.com/sdk/js?client-id=YOUR-CLIENT-ID">
  </script>
</head>

Payment Component

In the HTML, we need a template reference of a DOM element that PayPal will replace with the button.

file_type_html some.component.html
<div *ngIf="!paidFor">
  <h1>Buy this Couch - ${{ product.price }} OBO</h1>
</div>

<div *ngIf=paidFor>
  <h1>Yay, you bought a sweet couch!</h1>
</div>


<div #paypal></div>
file_type_ng_component_ts some.component.ts
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';

declare var paypal;

@Component({
  selector: 'app-payme',
  templateUrl: './payme.component.html',
  styleUrls: ['./payme.component.css']
})
export class PaymeComponent implements OnInit {
  @ViewChild('paypal', { static: true }) paypalElement: ElementRef;

  product = {
    price: 777.77,
    description: 'used couch, decent condition',
    img: 'assets/couch.jpg'
  };

  paidFor = false;

  ngOnInit() {
    paypal
      .Buttons({
        createOrder: (data, actions) => {
          return actions.order.create({
            purchase_units: [
              {
                description: this.product.description,
                amount: {
                  currency_code: 'USD',
                  value: this.product.price
                }
              }
            ]
          });
        },
        onApprove: async (data, actions) => {
          const order = await actions.order.capture();
          this.paidFor = true;
          console.log(order);
        },
        onError: err => {
          console.log(err);
        }
      })
      .render(this.paypalElement.nativeElement);
  }
}

React

The react implementation takes advantage of Hooks to manage state and initialize the PayPal script tag.

Generate the App

Create a new React project with create-react-app.

command line

npx create-react-app my-app

Payment Component

file_type_js App.js
import './App.css';
import React, { useState, useRef, useEffect } from 'react';

function Product({ product }) {
  const [paidFor, setPaidFor] = useState(false);
  const [error, setError] = useState(null);
  const paypalRef = useRef();

  useEffect(() => {
    window.paypal
      .Buttons({
        createOrder: (data, actions) => {
          return actions.order.create({
            purchase_units: [
              {
                description: product.description,
                amount: {
                  currency_code: 'USD',
                  value: product.price,
                },
              },
            ],
          });
        },
        onApprove: async (data, actions) => {
          const order = await actions.order.capture();
          setPaidFor(true);
          console.log(order);
        },
        onError: err => {
          setError(err);
          console.error(err);
        },
      })
      .render(paypalRef.current);
  }, [product.description, product.price]);

  if (paidFor) {
    return (
      <div>
        <h1>Congrats, you just bought {product.name}!</h1>
        <img alt={product.description} src={gif} />
      </div>
    );
  }

  return (
    <div>
      {error && <div>Uh oh, an error occurred! {error.message}</div>}
      <h1>
        {product.description} for ${product.price}
      </h1>
      <div ref={paypalRef} />
    </div>
  );
}

function App() {
  const product = {
    price: 777.77,
    name: 'comfy chair',
    description: 'fancy chair, like new',
    image: chair,
  };

  return (
    <div className="App">
      <Product product={product} />
    </div>
  );
}

export default App;

Vue

Generate the App

Create a new Vue app with the Vue CLI

command line

vue create my-app

Payment Component

Payments.vue
<template>
  <div>
    <div v-if="!paidFor">
      <h1>Buy this Lamp - ${{ product.price }} OBO</h1>

      <p>{{ product.description }}</p>

    </div>

    <div v-if="paidFor">
      <h1>Noice, you bought a beautiful lamp!</h1>
    </div>

    <div ref="paypal"></div>
  </div>
</template>

<script>
// import image from "../assets/lamp.png"
export default {
  name: "HelloWorld",

  data: function() {
    return {
      loaded: false,
      paidFor: false,
      product: {
        price: 777.77,
        description: "leg lamp from that one movie",
        img: "./assets/lamp.jpg"
      }
    };
  },
  mounted: function() {
    const script = document.createElement("script");
    script.src =
      "https://www.paypal.com/sdk/js?client-id=YOUR-CLIENT-ID";
    script.addEventListener("load", this.setLoaded);
    document.body.appendChild(script);
  },
  methods: {
    setLoaded: function() {
      this.loaded = true;
      window.paypal
        .Buttons({
          createOrder: (data, actions) => {
            return actions.order.create({
              purchase_units: [
                {
                  description: this.product.description,
                  amount: {
                    currency_code: "USD",
                    value: this.product.price
                  }
                }
              ]
            });
          },
          onApprove: async (data, actions) => {
            const order = await actions.order.capture();
            this.paidFor = true;
            console.log(order);
          },
          onError: err => {
            console.log(err);
          }
        })
        .render(this.$refs.paypal);
    }
  }
};
</script>

Questions? Let's chat

Open Discord