PayPal Checkout with Payment Buttons in PHP

PayPal is a globally recognized electronic payment gateway. It enables people to shop online and send and receive money using their PayPal account. Due to its awesome features and services it has become a "must be included" payment gateway for web applications.

PayPal Checkout with Payment Buttons in PHP

PayPal offers different APIs to web applications to receive payments for placed orders and/or donations. Most commonly used are the PayPal Payouts API and Orders API v2 with the JavaScript SDK. This post demonstrates how to add PayPal payment buttons to integrate the PayPal checkout experience in a PHP application with a working demo and code.

 

What are Payment Buttons, and How Do They Work?

PayPal payment buttons are customizable buttons that PayPal offers to allow web applications to accept payments for orders, subscriptions and donations with ease. The workflow of these buttons is as follows:

  1. We place the PayPal payment buttons on the checkout page on our website.
  2. The user clicks the buttons to pay for purchased items.
  3. Then the PayPal checkout experience is launched in a popup window with configured options.
  4. The user makes the payment and the orders API is called to finalize the transaction.
  5. The user is shown an order confirmation page upon successful transaction.
 

How to Integrate PayPal Checkout with Payment Buttons in PHP?

PayPal has become one of the must-be included payment methods for online applications for receiving payments. In this post, we are going to integrate PayPal checkout in a PHP application.We will create an HTML page with payment buttons. We will create and capture a PayPal order with the PayPal order API in a PHP script on the server side. You can refer to PayPal's Call Orders API from Your Server for more details. Steps to implement PayPal checkout with payment buttons:

  • First we must have a PayPal sandbox account.
  • Get the OAuth 2.0 client ID and client secret of this sandbox account.
  • Create a PHP class to handle PayPal API requests for creating and capturing orders.
  • Create an HTML page with cart items and add PayPal checkout buttons.
  • Render PayPal payment buttons with JavaScript and send a request to the server-side script.
  • Create and save the order to PayPal by sending a request from JavaScript.
  • Show a success page when an order is successfully created.

Before we start, I have created a static session array to store cart products for demonstration. I am not saving the order to database and I assume you already have a shopping cart set up. Let's start by creating the following files:

  • constants.php: This file contains PayPal credential constants.
  • paypal-sdk/paypal_client.php: A PHP class responsible to make API requests.
  • index.php: An HTML page displaying the cart items and PayPal checkout buttons.
  • javascript.js: Our client-side JavaScript to render the payment buttons.
  • paypal-request.php: The file that processes the order requests and uses the paypal_client class to create and capture orders.
  • style.css: CSS styles for our user interface.
 

Step 1: Create a PayPal Sandbox App and Get API Credentials

  • Log in to your PayPal developer account and navigate to PayPal Developer.
  • Click "Sandbox Accounts" under Testing Tools top navigation menu item.
  • Click "Create Account" button. We need two accounts: one for the buyer and another for the merchant.
  • Now click "App & Credentials" in the top navigation menu.
  • Click "Create App" and enter the App name and assign a merchant account to the App.

    Paypal App & Credentials
  • After you have created your app, you will see a screen like this: copy Client ID and Client Secret.

    Paypal App & Credentials

Now that we have our Sandbox Client ID & Secret, we are ready to integrate payment buttons.

 

Step 2: Add PayPal Credential Constants

In this step we create a PHP file to store PayPal credentials. We will define PAYPAL_ENVIRONMENT, PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET as credential constants.

constants.php

<?php
define('PAYPAL_ENVIRONMENT', 'sandbox');
define('PAYPAL_CLIENT_ID', 'PAYPAL_CLIENT_ID');
define('PAYPAL_CLIENT_SECRET', 'PAYPAL_CLIENT_SECRET');
 

Step 3: Create a PayPal Client Class to Send API Requests

We need a PHP class to manage API requests. This class will be responsible for making API requests to PayPal API endpoints for generating and refreshing tokens as well as creating and capturing orders. The class definition is as follows:

  • $base_url: Contains the sandbox or production endpoint URL.
  • $client_id: Client id created in previous steps.
  • $client_secret: Client secret created in previous steps.
  • $access_token: Access token for PayPal requests authorization.
  • $token_type: The type of token i.e. "Bearer".
  • $expires_in: The expiration time of the current access token. Used to refresh/recreate the token when it expires.
  • $created_at: The creation time of the current access token. Used to refresh/recreate the token when it expires.
The methods used in the paypal_client class are:
  • __construct(): Constructor of class when an instance of the class is created. Sets the $client_id, $client_secret, $base_url and $access_token properties of the class.
  • set_access_token(): Sends the request to PayPal's authorization endpoint to retrieve the access token for future requests. Sets the $access_token, $token_type, $expires_in and $created_at properties of class.
  • curl_request(): A generic function used to send curl requests to PayPal endpoints.

paypal_client.php

<?php

class paypal_client
{
public string $base_url;

private string $client_id;

private string $client_secret;

private ?string $access_token;

private string $token_type;

private int $expires_in;

private int $created_at;

public function __construct($client_id, $client_secret, $sandbox = false)
{
$this->client_id = $client_id;

$this->client_secret = $client_secret;

$this->base_url = $sandbox
? 'https://api-m.sandbox.paypal.com'
: 'https://api-m.paypal.com';

$this->access_token = null;
}


/**
* @throws Exception
*/
public function set_access_token(): stdClass|array
{
$headers = [
'content-type' => 'application/x-www-form-urlencoded'
];

$curl_options = [
CURLOPT_USERPWD => $this->client_id . ':' . $this->client_secret
];

$body = [
'grant_type' => 'client_credentials'
];

$response = $this->curl_request('/v1/oauth2/token', 'POST', $headers, http_build_query($body), $curl_options);

$response = json_decode($response);

$this->access_token = $response->access_token;

$this->token_type = $response->token_type;

$this->expires_in = $response->expires_in;

$this->created_at = time();

return $response;
}


/**
* @param string $path
* @param string $method
* @param array $headers
* @param array|string $body
* @param array $curl_options
* @return bool|string
* @throws Exception
*/
public function curl_request(string $path, string $method, array $headers = [], array|string $body = [], array $curl_options = []): bool|string
{
$curl = curl_init();

array_change_key_case($headers);

$headers = array_merge(['accept' => 'application/json'], $headers);

curl_setopt($curl, CURLOPT_URL, $this->base_url . $path);
curl_setopt($curl, CURLOPT_TIMEOUT, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

if (str_starts_with($this->base_url, 'https://')) {
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
}

if (!array_key_exists('authorization', $headers) && !str_ends_with($path, 'v1/oauth2/token')) {
if (is_null($this->access_token) || time() > ($this->created_at + $this->expires_in)) {
$this->set_access_token();
}

$headers['authorization'] = sprintf('%s %s', $this->token_type, $this->access_token);
}

// If any headers set add them to curl request
if (!empty($headers)) {
curl_setopt($curl, CURLOPT_HTTPHEADER, array_map(function ($key, $value) {
return $key . ': ' . $value;
}, array_keys($headers), array_values($headers)));
}

// Set the request type , GET, POST, PUT or DELETE
switch (strtoupper($method)) {
case 'POST':
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
break;
case 'PUT':
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
break;
case 'DELETE':
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
default:
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
break;
}

// If any data is supposed to be sent along with request add it to curl request
if (!empty($body)) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
}

// Any extra curl options to add in curl object
if (!empty($curl_options)) {
foreach ($curl_options as $option_key => $option_value) {
curl_setopt($curl, $option_key, $option_value);
}
}

$response = curl_exec($curl);

$error = curl_error($curl);

$error_code = curl_errno($curl);

$status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);

if ($error_code > 0) {
throw new Exception($error, $error_code);
}

if ($status_code < 200 || $status_code >= 300) {
throw new Exception($response, $status_code);
}

curl_close($curl);

return $response;
}
}

Step 4: Create the Checkout Page with PayPal Buttons

Create an HTML page as a user interface to display cart items in tabular format. Add a container element where the PayPal payment buttons will be displayed. Loop through the products array and calculate the total tax amount and the total items amount (without tax). Then create a cart session that can be accessed in our server-side script.

index.php

<?php
if (!session_id()) {
session_start();
}

include __DIR__ . '/constants.php';

$products = [];

// Static array of products
$products[] = ['product_title' => 'Javascript Book - PDF', 'product_price' => 10.00, 'product_tax' => 2.00, 'product_quantity' => 3];
$products[] = ['product_title' => 'PHP Book - PDF', 'product_price' => 15.00, 'product_tax' => 3.00, 'product_quantity' => 2];

$items_total = $tax_total = 0.00;

// Loop through products and calculate tax total & items total
foreach ($products as $product) {
$tax_total += floatval($product['product_tax'] * $product['product_quantity']);
$items_total += floatval($product['product_price'] * $product['product_quantity']);
}

// Function to show amount in price format
function to_price($amount = 0.00, $locale = 'en_US', $currency_code = 'USD'): bool|string
{
return numfmt_format_currency(numfmt_create($locale, NumberFormatter::CURRENCY), $amount, $currency_code);
}

// Create a cart session
$_SESSION['cart']['products'] = $products;
$_SESSION['cart']['items_total'] = $items_total;
$_SESSION['cart']['tax_total'] = $tax_total;
$_SESSION['cart']['cart_total'] = $tax_total + $items_total;
?>
<!DOCTYPE html>
<html>
<head>
<title>Paypal Checkout with Payment Buttons - Demo</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport"/>
<link rel="stylesheet" href="css/style.css"/>
<script src="https://www.paypal.com/sdk/js?client-id=<?= PAYPAL_CLIENT_ID ?>" defer="defer"></script>
<script src="js/javascript.js" defer="defer"></script>

</head>
<body>
<section class="section py-4">
<div class="container">
<table class="table table-bordered mb-4">
<thead>
<tr>
<th colspan="2">Product</th>
<th style="width: 120px">Price</th>
<th style="width:120px;">Tax</th>
<th style="width:100px;">Quantity</th>
<th style="width: 120px;">Item Total</th>
</tr>
</thead>
<tbody>
<?php foreach ($_SESSION['cart']['products'] as $product) {
$item_total = $product['product_price'] * $product['product_quantity'];
$item_tax = $product['product_tax'] * $product['product_quantity'];
?>
<tr>
<td width="31">
<img src="images/pdf-book.png"/>
</td>
<td>
<?= $product['product_title']; ?>
</td>
<td>
<?= to_price($product['product_price']); ?>
</td>
<td>
<?= to_price($product['product_tax']); ?>
</td>
<td>
<?= $product['product_quantity'] ?>
</td>
<td>
<?= to_price($item_total + $item_tax); ?>
</td>
</tr>
<?php } ?>
<tr>
<th colspan="5" class="text-right">
Tax Total
</th>
<th class="text-right">
<?= to_price($_SESSION['cart']['tax_total']); ?>
</th>
</tr>
<tr>
<th colspan="5" class="text-right">
Items Total
</th>
<th class="text-right">
<?= to_price($_SESSION['cart']['items_total']); ?>
</th>
</tr>
<tr>
<th colspan="5" class="text-right">
Cart Total
</th>
<th class="text-right">
<?= to_price($_SESSION['cart']['cart_total']); ?>
</th>
</tr>
</tbody>
</table>
<div id="paypal-buttons"></div>
</div>
</section>
</body>
</html>

Step 5: Initialize PayPal Payment Buttons on Checkout Page (JavaScript)

We need to render PayPal payment buttons using the JavaScript SDK PayPal provides. We need to do the following:

  • Listen to the window load event and render PayPal buttons.
  • Define an endpoint for createOrder to create an order in our server-side script.
  • Define onApprove to capture the order and redirect the user to a success page after a successful transaction.

javascript.js

window.addEventListener("load", function () {
// Render paypal Buttons
paypal.Buttons({
style: {
layout: "horizontal"
},
// Call your server to create an order
createOrder: function (data, actions) {
return fetch("paypal-request.php", {
mode: "no-cors",
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
action: "create-order",
})
}).then(function (res) {
return res.json();
}).then(function (data) {
if (!data.success && data.message) {
console.error(data.message);
}
return data.id;
});
},
// Call your server to save the transaction
onApprove: function (data, actions) {
return fetch("paypal-request.php", {
mode: "no-cors",
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
action: "save-order",
id: data.orderID
})
}).then(function (res) {
return res.json();
}).then(function (data) {
// Redirect to thank you/success page after saving transaction
if (data.success) {
window.location.assign("payment-success.php");
}
});
}
}).render("#paypal-buttons");
});

Step 6: Create and Capture an Order on Server Side

In this file, we process the requests sent from our client-side JavaScript SDK. We will use the paypal_client class to create an order in PayPal and then capture it after successful payment.

paypal-request.php

<?php
if (!session_id()) {
session_start();
}

include __DIR__ . '/constants.php';

// Import the PayPal SDK client that was created in `Set up Server-Side SDK`.
include __DIR__ . '/paypal-sdk/paypal_client.php';

// Capture the input received from fetch() request
$json_data = file_get_contents('php://input');
$request = json_decode($json_data);

$paypal_client = new paypal_client(PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_ENVIRONMENT === 'sandbox');

switch ($request->action) {
case 'create-order':
$headers = [
'prefer' => 'return=representation',
'content-type' => 'application/json'
];

// Assign cart from session to a variable
$cart = $_SESSION['cart'];

// Set currency code used in transaction
$currency_code = 'USD';

$request_body = [];
$application_context = [];
$purchase_units = [];
$pu_amount = [];

// Prepare context of our request
$application_context['locale'] = 'en-US';
$application_context['user_action'] = 'PAY_NOW';

// Prepare amount & break down of amount for order
$pu_amount['currency_code'] = $currency_code;
$pu_amount['value'] = floatval($cart['cart_total']);

// Items total break down without tax
$pu_amount['breakdown']['item_total']['currency_code'] = $currency_code;
$pu_amount['breakdown']['item_total']['value'] = number_format($cart['items_total'], 2);

// Total tax of all products
$pu_amount['breakdown']['tax_total']['currency_code'] = $currency_code;
$pu_amount['breakdown']['tax_total']['value'] = number_format($cart['tax_total'], 2);

$purchase_units['amount'] = $pu_amount;

$items = [];

// Loop through all products in session and prepare items array for order request
if (!empty($cart['products'])) {
foreach ($cart['products'] as $product) {
$item['name'] = $product['product_title'];
$item['quantity'] = $product['product_quantity'];
$item['category'] = 'DIGITAL_GOODS';

// Unit amount of product (without tax)
$item['unit_amount']['currency_code'] = $currency_code;
$item['unit_amount']['value'] = number_format($product['product_price'], 2);

$items[] = $item;
}
}

$purchase_units['items'] = $items;

// Finally create request body array assigning context & purchase units created above
$request_body['intent'] = 'CAPTURE';
$request_body['application_context'] = $application_context;
$request_body['purchase_units'][] = $purchase_units;

try {
$response = $paypal_client->curl_request('/v2/checkout/orders', 'POST', $headers, json_encode($request_body));
die($response);
} catch (Exception $e) {
echo $e->getMessage();
}

break;
case 'save-order':
$headers = [
'prefer' => 'return=minimal',
'content-type' => 'application/json'
];

try {
$response = $paypal_client->curl_request(sprintf('/v2/checkout/orders/%s/capture', $request->id), 'POST', $headers);

// Save order to database here

// unset cart session
unset($_SESSION['cart']);

echo json_encode(['success' => 1], JSON_PRETTY_PRINT);
} catch (Exception $e) {
echo $e->getMessage();
}

break;
}

Step 7: Add CSS Styles for Checkout Page

Add CSS styles for the entire user interface, including the payment buttons.

style.css

* {
box-sizing: border-box;
}
html,body {
margin: 0;
padding: 0;
}
body {
background-color: #f6f6f6;
font-family: "Segoe UI", "Roboto", "Helvetica", sans-serif;
font-size: 15px;
font-weight: normal;
font-style: normal;
line-height: 1.5;
}
.container {
width: 100%;
max-width: 1140px;
margin-right: auto;
margin-left: auto;
padding-right: 15px;
padding-left: 15px;
}
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.text-right {
text-align: right;
}
.table {
width: 100%;
border-collapse: collapse;
background-color: #fff;
margin-bottom: 1rem;
}
.table tr th,
.table tr td {
border: 1px solid #dddddd;
padding: 10px;
}
#paypal-buttons {
max-width: 750px;
margin-right: auto;
margin-left: auto;
}

Log in to your sandbox buyer or merchant account and verify the transaction was placed. You should see the transaction details like in the screenshot below:

Paypal Transaction Details

Using these code snippets, we now have a working PayPal Checkout Integration in PHP that uses the JavaScript SDK and Orders API v2.