PayPal Checkout with Payment Buttons in PHP

PayPal is globally used electronic payment processor. People can shop online, transfer money online and receive money online using a single PayPal account. Because of its features and global reach PayPal has become a "must be included" payment method for online shopping stores.

PayPal Checkout with Payment Buttons in PHP

PayPal provides API integration for web applications to receive payment for placed orders. Mostly used PayPal APIs are PayPal Payouts API and PayPal Orders API v2 with JavaScript SDK. In this post will learn how to add PayPal payments buttons to integrate PayPal checkout experience in PHP application with working demo and code.

 

What are Payment Buttons and How Do They work?

PayPal payment buttons are customizable buttons PayPal offers which allows web applications to accept payments for orders, subscriptions and donation with ease. These payment buttons work in following way: 

  1. We place the PayPal payment buttons on checkout page on our website.
  2. Buyer clicks the buttons to pay for order.
  3. Buttons launch the PayPal checkout experience with payment options.
  4. Buyer makes the payment which calls Orders API to finalize the transaction.
  5. We show order confirmation page to buyer on successful transaction.
 

How to Integrate PayPal Checkout with Payment Buttons in PHP

PayPal has become one of the must be included payment method for online applications that allow to receive payments. In this post we are going to integrate PayPal checkout experience in PHP application. We will create an HTML page with payment buttons. We will create and capture PayPal order with PayPal order API in PHP script on 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:

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

Before we get started, For demonstration I have created a session to hold cart products and amount statically. I shall not be saving order to database after successful transaction and I am under opinion you already have a cart setup. So lets get started and implement PayPal checkout with payment buttons. We are going to create following files:

  • constants.php: Contains PayPal configuration constants.
  • paypal-sdk/paypal_client.php: A PHP class containing code to connect to PayPal API endpoints and handle requests.
  • index.php: HTML page containing the cart items and PayPal checkout button.
  • javascript.js: Javascript code to render payment buttons.
  • paypal-request.php: Processes order requests received from our client side code.
  • style.css: CSS styles for our page.
 

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

  • Login to your PayPal developer account and navigate to PayPal Developer.
  • Click on Sandbox Accounts under Testing Tools top navigation menu item.
  • Click on Create Account button, We need two accounts one for buyer and other for merchant. You don't need to create these accounts if you already have them created.
  • Now click on App & Credentials in top navigation menu.
  • Click on Create App and enter App name and assign a merchant account to 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 setup payment buttons.

 

Step 2: Add Configuration Constants (PHP)

Next we create a PHP file to define our configuration constants for use in our API requests. Add PayPal sandbox constants for PAYPAL_ENVIRONMENT, PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET in the constants file.

constants.php

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

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

Create custom PHP class to handle requests to PayPal endpoints. This class will be responsible for creating and refreshing access token and sending requests to PayPal API endpoints. The class definition is as below:

  • $base_url: Holds the endpoint URL for sandbox or production.
  • $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 expire time of current access token. Used to refresh/recreate the token when expired.
  • $created_at: The creation time of current access token. Used to refresh/recreated the token when expired.
The methods used in paypal_client class are:
  • __construct(): Constructor of class when and instance of class is created. Sets $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 access token for future requests. Sets $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 to display PayPal payments buttons on page. I have created a static array of products for demonstration. Next loop through products array to calculate total tax amount and total items amount (without tax). Then create a cart session that can be accessed in our server side script.

index.php

<?php
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 href="css/style.css" rel="stylesheet" />
<script defer="defer" src="https://www.paypal.com/sdk/js?client-id=<?=PAYPAL_CLIENT_ID?>"></script>
<script defer="defer" src="js/javascript.js"></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: Render PayPal Payment Buttons on Checkout Page with JavaScript

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

  • Listen to window load event and render PayPal buttons. 
  • Define endpoint for createOrder to create an order in our server side script. 
  • Define onApprove to capture order and redirect to a thank you/success page after 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: Handle Order Creation and Capture on Server Side

Next we need to handle the requests sent from our client side JavaScript SDK. We will use paypal_client class to create an order in PayPal and then capture it after successful payment.

paypal-request.php

<?php
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 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;
}

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

Paypal Transaction Details

Now we have a working PayPal Checkout Integration in PHP which uses JavaScript SDK and Orders API v2.