Testing PayPal in Laravel (PHPUnit & Dusk)

Boštjan Oblak
dotdev
Published in
5 min readNov 12, 2017

--

Recently I’ve been working on a central payment system (aka paywall). This paywall will be used in many of our company’s projects. This allows us to add new payment methods, handle invoices, etc. all in one place.

We currently support CreditCards via NestPay and PayPal. When dealing with payments you must be very careful not to have bugs. This means that code needs to be well tested. But how do you test payments?

In this article, I’ll show you how you can go about testing PayPal payments. First we will look at unit and feature testing, and later take a look at browser testing (using Laravel Dusk) which will simulate complete payments.

TL;DR You can view sample code on GitHub: https://github.com/BostjanOb/PayPalTesting

Unit testing the PayPal payments

Preconditions

This is not a tutorial on how to make payments over PayPal. I assume you already have a PayPal sandbox account with both a client_id and a client_secret. If not … check here: https://developer.paypal.com/docs/classic/lifecycle/sb_create-accounts/

I assume you have a Laravel 5.5 project. If not, I think the examples provided can be easily ported to any other framework you might be using (except maybe Laravel Dusk browser tests).

Creating a simple PayPal payment app

First, create a simple app to process payments via PayPal. We will be paying for a single item.

Start by making a new empty Laravel project:

> laravel new paypaltesting
> composer require paypal/rest-api-sdk-php

Add some code (filenames in comments)- for full code check GitHub (link at the bottom):

Then create the PayPalController:

To summarise the code:

  1. A user visits the /paypal endpoint. We create a random ‘purchase’ entry (line 21), make a request to PayPal and redirect the user to the provided redirect.
  2. The user successfully completes a payment on PayPal and is redirected back to /paypal/success. Here we check the payment and execute the transaction. We redirect the user to /paypal/view/{id} so we can view the purchase status.

So how do we test this? We have some additional business logic when a payment is completed and we need to be sure it works.

Testing PayPal payments using PHPUnit

To be able to successfully unit test our code (we will be touching the database so…..go watch https://www.youtube.com/watch?v=LdUKfbG713M), we need to do some refactoring. And let’s be honest … that huge controller is not what you want :)

The problem is we don’t want to make API calls to PayPal so we need to somehow mock them.

To make that possible we move our PayPal code to a new class which will handle all the PayPal code. So let’s create a PayPalPayment.php class:

An important thing to notice about this class is that we create 3 methods that make API calls to PayPal: paymentCreate, paymentGet and paymentExecute. This allows us to mock these methods when making tests.

Now that we have moved all that logic to a new class, let’s refactor the PayPalController and make it MUCH smaller and more readable.

We injected our new PayPalPayment class inside our constructor (__construct) via the service container. This will enable us to switch instances with our mock object.

Now how do we test all this?

First we need to see what kind of JSON response PayPal returns to us. To get this info we will use the laravel dd() helper inside paypal/api/payment.php (in the PayPal-PHP-SDK — and don’t forget to remove it later). Let’s use the create() method as an example:

public function create($apiContext = null, $restCall = null)
{
$payLoad = $this->toJSON();
$json = self::executeCall(
"/v1/payments/payment",
"POST",
$payLoad,
null,
$apiContext,
$restCall
);
$this->fromJson($json);
return $this;
}

put dd($json) right before $this->from($json). The JSON strings we get will help us when we write our test class:

For every test we bind a new instance to the service container and overwrite the functions we need. We also use anonymous classes which were introduced in PHP7.

Browser test (Laravel Dusk)

To simulate a complete payment process we have to do a browser test. And Laravel Dusk makes this extremely simple.

Install Laravel Dusk:

> composer require --dev laravel/dusk
> php artisan dusk:install

Now create the .env.dusk.local file inside your applications’ base directory with the environment settings for Laravel Dusk. I use SQLite when testing. Be careful though, since in-memory storage won’t work with Laravel Dusk. So if you are working with SQLite, you will need to create a persistent SQLite file.

Some things to set inside .env.dusk.local:

  • set the correct APP_URL which will point to your app url
  • additional to PAYPAL_ID and PAYPAL_SECRET add 2 new settings: PAYPAL_USER_EMAIL = email of the PayPal test user
    PAYPAL_USER_PASSWORD = password of the PayPal test user

Now let’s write the test. Inside /tests/Browser create a new file named PayPalTest.php:

It’s important to set timeouts high enough because the PayPal sandbox can be slow sometimes.

Steps we make inside our test:

  1. Open the /paypal page
  2. Wait for the PayPal iframe to show
  3. Switch the browser to this iframe so we can log in
  4. Enter our credentials and click on the login button
  5. Wait until we are logged in and we see both the ‘Ship to’ and ‘Pay with’ texts
  6. Click the confirm button
  7. Wait until payment is complete
  8. Check that the payment is marked as approved in the database

If any of the steps fail, Laravel Dusk will make a screenshot and save it inside /tests/Browser/screenshots so you can check what went wrong.

Conclusion

Unit testing PayPal won’t guarantee that the payment process will work, but it gives you an option to test the business logic of when the payment is approved, fails, when an error occurs, etc.
We simulate JSON responses from PayPal API, so in case PayPal changes those responses your tests would be invalid.

We also provide browser testing which simulates the complete payment process, just like if the user would be clicking the payment interface, step by step. We must take care of timeouts because PayPal can be slow sometimes.
We must be aware that we are using HTML ids and tags that PayPal uses. If PayPal changes their interface, Laravel Dusk tests would fail.

And as mentioned at the beginning of the article, all code is available on GitHub at https://github.com/BostjanOb/PayPalTesting. Commits have been added step by step as this article has been written.

--

--