Using PayPal's REST API with PHP and MySQL

9 August, 2018

PayPal is pretty much ubiquitous these days in the world of payment services. It is easily one of the most recognisable payment brands on the web today. If you work a lot with ecommerce sites then at some point you're more than likely going to have to develop an integration with PayPal.

PayPal's REST API is a simple way of integrating the payment gateway into your site to take payments, but sadly the official documentation can be a little on the confusing side. In this article we're going to look at a simple example that will use PayPal's SDK to take payments using PHP and MySQL.

Overview

The process of taking a payment via PayPal's REST API can be broken down into the following steps:-

  1. The customer confirms they want to pay for goods
  2. A request is issued from your server to PayPal for a payment to be made
  3. PayPal creates a payment record after the customer has logged into their PayPal account and confirmed they are happy to proceed
  4. You receive a notification from PayPal that a payment record has been initiated, the response is processed and we complete the payment
  5. The customer is redirected to a thank you page

In order to keep things simple for our example we're going to split the request and response into two separate files and load all our common functionality in via a bootstrap file:-

  • src/request.php
  • src/response.php
  • src/bootstrap.php

Then all the front-end will be handled by some HTML files:-

  • index.html - will post to our request script to initiate a payment
  • payment-successful.html - after successfully taking a payment our response script will redirect here
  • payment-cancelled.html - if the payment is cancelled part way through PayPal will redirect to this page

You'll find the complete example code over on GitHub if you want to look at it in full.

Installing the SDK

PayPal provides a really useful PHP SDK for working with the REST API. Using this helpful library of functions will do a lot of the leg work for us. The easiest way of installing this is via Composer.

composer require paypal/rest-api-sdk-php:^1.13

So that we can benefit from using the namespaces of the SDK classes to import them into our scripts we want to make sure we include Composer's autoload in our codebase. If you're already using Composer in your project you'll hopefully already be doing this somewhere. For our example code we'll include it at the top of a bootstrap.php file that will be required by our request and response scripts.

require '../vendor/autoload.php';

Set Up

To keep things tidy we set up our PayPal API credentials and database config in bootstrap.php. If you want to have a play with the example code make sure you edit both $paypalConfig and $dbConfig so that they are set up for your details.

For the PayPal settings we need to define a client ID and client secret which we can get from our PayPal account.

// PayPal settings. Change these to your account details and the relevant URLs
// for your site.
$paypalConfig = [
    'client_id' => 'your-paypal-api-client-id',
    'client_secret' => 'your-paypal-api-client-secret',
    'return_url' => 'http://example.com/response.php',
    'cancel_url' => 'http://example.com/payment-cancelled.html'
];

We also want to define a return URL for the PayPal response, this will be where we process the payment, and a cancel URL. The cancel URL will be where customer's are sent if they decide to cancel the transaction and return to our site.

In order to interact with PayPal's REST API we need to authenticate against it and create an API context object that we can use to get and make payments.

$apiContext = getApiContext(
    $paypalConfig['client_id'],
    $paypalConfig['client_secret'],
    $enableSandbox
);

For testing $enableSandbox is set to true in our example code. If you want to take real payments this would want toggling to false. Our getApiContext function looks like this:-

function getApiContext($clientId, $clientSecret, $enableSandbox = false)
{
    $apiContext = new ApiContext(
        new OAuthTokenCredential($clientId, $clientSecret)
    );
    $apiContext->setConfig([
        'mode' => $enableSandbox ? 'sandbox' : 'live'
    ]);
    return $apiContext;
}

In this function we toggle between the sandbox and live by setting the mode using setConfig(). We can also enable logging here which can be useful if you're trying to debug things.

$apiContext->setConfig([
    'mode' => $enableSandbox ? 'sandbox' : 'live',
    'log.FileName' => '../some-paypal-log-file.log',
    'log.LogLevel' => $enableSandbox ? 'DEBUG' : 'INFO'
]);

Logging is most verbose in the 'DEBUG' level. For a production site this should be switched to 'INFO' which logs less detail. In PayPal's live environment 'DEBUG' is not an option and will throw a warning.

The Request

With PayPal's SDK we need to build up a payment object that we can then generate an approval link with; we then use this link to redirect the customer to the PayPal website. This will be our payment request.

The payment object is made up of a payer, a transaction and some redirect URLs. For the payer part we need to set up the payment method to be 'paypal'.

$payer = new Payer();
$payer->setPaymentMethod('paypal');

Next let's generate the transaction itself. For this we need to set the amount payable by defining an 'amount' object with the total payable and currency. This is then added to a 'transaction' object along with a description of the transaction and an invoice number. The invoice number is whatever we want to be to tie it in to the purchase being made within our checkout process; we'll use it again when handling the response to add the payment to our database. The description can be anything you want and will show up in PayPal against the order details of the payment to help you understand what the payment was for.

// Set some example data for the payment.
$currency = 'GBP';
$amountPayable = 10.00;
$invoiceNumber = uniqid();
$amount = new Amount();
$amount->setCurrency($currency)
    ->setTotal($amountPayable);
$transaction = new Transaction();
$transaction->setAmount($amount)
    ->setDescription('Some description about the payment being made')
    ->setInvoiceNumber($invoiceNumber);

We now need to set the redirect URLs. These will determine where PayPal sends the customer back to after they have confirmed the payment on PayPal's website. Hopefully these are pretty self explanatory. The return URL is where PayPal will send the customer after they agree to the payment, which is where we'll deal with the response to complete payment. The cancel URL is where the customer returns to our site if they decide to terminate the purchase.

$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl($paypalConfig['return_url'])
    ->setCancelUrl($paypalConfig['cancel_url']);

With the payer, transaction and redirect URLs set up we can now build our payment object.

$payment = new Payment();
$payment->setIntent('sale')
    ->setPayer($payer)
    ->setTransactions([$transaction])
    ->setRedirectUrls($redirectUrls);
try {
    $payment->create($apiContext);
} catch (Exception $e) {
    throw new Exception('Unable to create link for payment');
}

We're telling PayPal here our intention is to make a 'sale' by using setIntent(). It's important we remember to set this before generating the approval link. Other options include 'authorize' and 'order'; 'authorize' is used when you want to capture a payment at a later date (e.g. for a delayed shipment); 'order' is used when you don't have all items in stock and want to take part payment for items you do have now and complete payment later for the rest of the items as they become available. You can read about the differences between PayPal's checkout payment actions on their website.

Finally we want to redirect the customer to PayPal.

header('location:' . $payment->getApprovalLink());
exit(1);

You can see the complete request.php script on GitHub.

The Response

Once the customer clicks 'Continue' on the PayPal website to proceed with the transaction they will be redirected to the return URL we defined in our request action.

Firstly we check that the query string contains the parameters we expect to be returned by PayPal in order to complete the transaction.

if (empty($_GET['paymentId']) || empty($_GET['PayerID'])) {
    throw new Exception('The response is missing the paymentId and PayerID');
}

If the query string looks good we proceed to complete the transaction (so far money hasn't changed hands). We start by getting the payment from PayPal that we initiated in our request using the 'paymentId'. Finally we execute the payment using PaymentExecution which enables us to take a payment from a PayPal account.

$paymentId = $_GET['paymentId'];
$payment = Payment::get($paymentId, $apiContext);
$execution = new PaymentExecution();
$execution->setPayerId($_GET['PayerID']);
try {
    // Take the payment
    $payment->execute($execution, $apiContext);
    ...
} catch (Exception $e) {
    // Failed to take payment
}

If this all goes well we've successfully taken payment. We attempt to retrieve the payment from PayPal to check it went through okay and write it to our database.

try {
    $db = new mysqli($dbConfig['host'], $dbConfig['username'], $dbConfig['password'], $dbConfig['name']);
    $payment = Payment::get($paymentId, $apiContext);
    $data = [
        'transaction_id' => $payment->getId(),
        'payment_amount' => $payment->transactions[0]->amount->total,
        'payment_status' => $payment->getState(),
        'invoice_id' => $payment->transactions[0]->invoice_number
    ];
    if (addPayment($data) !== false && $data['payment_status'] === 'approved') {
        // Payment successfully added, redirect to the payment complete page.
        header('location:payment-successful.html');
        exit(1);
    } else {
        // Payment failed
    }
} catch (Exception $e) {
    // Failed to retrieve payment from PayPal
}

If the payment is successful it will have the state 'approved'. Before we executed the payment it would have had the state 'created'. If the request failed after we executed the payment the state would be 'failed'.

In our example code we're calling a simple method called addPayment which will add some data from PayPal to our database. We then redirect to a successful payment page. The addPayment method looks like this:-

function addPayment($data)
{
    global $db;
    if (is_array($data)) {
        $stmt = $db->prepare('INSERT INTO `payments` (transaction_id, payment_amount, payment_status, invoice_id, createdtime) VALUES(?, ?, ?, ?, ?)');
        $stmt->bind_param(
            'sdsss',
            $data['transaction_id'],
            $data['payment_amount'],
            $data['payment_status'],
            $data['invoice_id'],
            date('Y-m-d H:i:s')
        );
        $stmt->execute();
        $stmt->close();
        return $db->insert_id;
    }
    return false;
}

The full code for the response.php script can be found on GitHub. We've also included an SQL script for creating the payments table if you want to try out the code and test out payments.

Testing with the Sandbox

It goes without question that testing is crucial to integrating with a payment gateway. PayPal provides a sandbox for doing this which allows us to create fake accounts for testing both the buyer and seller experiences. In order to test our script we will want to set up both. PayPal sandbox accounts are free.

To create or log in to a sandbox account go over to the PayPal Developer site and follow the links to login to the dashboard. Once you're signed up and logged in you want to create a couple of Sandbox accounts.

  • Create a business account for the seller/merchant. This represents the account for your website that will be taking payment.
  • Create a personal account for the buyer. This will be used for making payments to the website during testing. You'll want to give this account a PayPal balance that money can be subtracted from.

Next we need to create an app and some API credentials for our integration. At the time of writing you can do this from the dashboard by creating a new app under the 'REST API apps' heading. You'll need to give your app a name and then associate it with the business account we just set up. PayPal will then give us a Client ID and Secret that we can use to authenticate against the API with, these want adding to our bootstrap.php file.

When you come to test the code you'll want to access the index.html page. This is just a super simple HTML file that renders a form to the page that will post to our request.php script. When you click the 'Submit payment' button you'll be asked to log in to PayPal, use your fake personal account you just set up here. If you continue with the payment you'll get redirected back to the response.php script and finally the payment-successful.html page.

To test the payment has successfully gone through on PayPal you will want to log in to the fake business account on the Sandbox site. You should see a list of recent activity and if everything has worked according to plan the most recent item should be for the payment you just tested.

Complete Code

The complete example code for the integration described in this post can be found on GitHub. Check it out, download and play with it. Hopefully this really simple example will get you started with integrating PayPal into your site.

If you find yourself needing to implement more than one payment gateway into your site I'd recommend checking out Omnipay which has an implementation for PayPal. Omnipay adds a consistent API for taking payments from different payment services.

Written by Andy

With more than two decades’ coding experience under his belt, Senior Developer Andy continues to prove an integral part of the Evoluted development team. Having been with Evoluted since 2012, he has worked on countless projects for our clients. An analytical thinker, his background includes time spent working as a theoretical physicist; which involved research into Quantum Optics.

Up next…
An Introduction to Deployer
30 July, 2018

0 Comments

No comments just yet!

Leave a comment

Replying to: - Cancel