SecurePay Integration with PHP

SecurePay Integration Guide for Omnipay v3 PHP Library with Laravel

I've already covered few Omnipay integration guides with my previous blog posts. In this post, I'm going to explain in detail about SecurePay, a leading payment solution for businesses of different sizes over Australia. I've recently contributed to the v3 release of the omnipay-securepay open source library for PHP on github.

By using this package (omnipay/securepay) we can implement different gateways listed below.

  • SecurePay_DirectPost (DirectPost v2 Integration)
  • SecurePay_SecureXML (Secure XML API Integration)

We're going to integrate SecurePay DirectPost v2 in this article below.

  • The customer submits the payment information directly to the Direct Post payment service from the website.
  • The payment gateway validates and redirects back to the website to fulfill the order.

Before starting the coding let's install the package via composer.

We're going to integrate this package with Laravel v5.6 for this article, use the command below to install the package.

composer require league/omnipay omnipay/securepay

Let's add some new routes for this integration under app/routes/web.php

Route::get('/checkout/payment', [
    'name' => 'Payment',
    'as' => 'checkout.payment',
    'uses' => 'PaymentController@checkout',
]);

Route::post('/checkout/payment/{order}/process', [
    'name' => 'Payment',
    'as' => 'checkout.payment.process',
    'uses' => 'PaymentController@payment',
]);

Route::get('/checkout/payment/{order}/completed', [
    'name' => 'Payment Completed',
    'as' => 'checkout.payment.completed',
    'uses' => 'PaymentController@completed',
]);

Route::get('/checkout/payment/{order}/failed', [
    'name' => 'Payment Failed',
    'as' => 'checkout.payment.failed',
    'uses' => 'PaymentController@failed',
]);

Adding new model toapp/Order.php handle database operations to store orders coming through the website.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    const PAYMENT_COMPLETED = 1;
    const PAYMENT_PENDING = 0;
    /**
     * @var string
     */
    protected $table = 'orders';

    /**
     * @var array
     */
    protected $dates = ['deleted_at'];

    /**
     * @var array
     */
    protected $fillable = ['transaction_id', 'amount', 'payment_status'];
}

This model has really basic stuff to show the process while building this integration guide, you are free to customize as per the requirement of your application.

A new database table is needed to store the orders. Let's create a table orderswith a console command.

php artisan make:migration create_table_orders --create=orders
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateOrdersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('transaction_id')->nullable();
            $table->float('amount')->unsigned()->nullable();
            $table->integer('payment_status')->unsigned()->default(0);
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('orders');
    }
}

Let's add a new classapp/SecurePay.php which interacts with library classes and framework classes to interact with API services.

<?php

namespace App;

use Exception;
use Illuminate\Http\Request;
use Omnipay\Common\CreditCard;
use Omnipay\Common\Exception\InvalidCreditCardException;
use Omnipay\Omnipay;

/**
 * Class SecurePay
 * @package App
 */
class SecurePay
{
    /**
     * @return mixed
     */
    public function gateway()
    {
        $gateway = Omnipay::create('SecurePay_DirectPost');

        $gateway->setMerchantId(config('services.securepay.merchant_id'));
        $gateway->setTransactionPassword(config('services.securepay.password'));
        $gateway->setTestMode(config('services.securepay.sandbox'));

        return $gateway;
    }

    /**
     * @param array $parameters
     * @param Request $request
     */
    public function card(array $parameters, Request $request)
    {
        return [
            'card' => new CreditCard([
                'firstName' => $request->get('first_name'),
                'lastName' => $request->get('last_name'),
                'number' => $request->get('card_number'),
                'expiryMonth' => $request->get('expiry_month'),
                'expiryYear' => $request->get('expiry_year'),
                'cvv' => $request->get('cvc'),
            ]),
        ];
    }

    /**
     * @param array $parameters
     * @param Request $request
     * @return mixed
     */
    public function purchase(array $parameters, Request $request)
    {
        $parameters = array_merge($parameters, $this->card($parameters, $request));

        try {
            $response = $this->gateway()
                ->purchase($parameters)
                ->send();

        } catch (InvalidCreditCardException $e) {
            throw new Exception($e->getMessage());
        }

        return $response;
    }

    /**
     * @param array $parameters
     * @param Request $request
     * @return mixed
     */
    public function complete(array $parameters, Request $request)
    {
        $parameters = array_merge($parameters, $this->card($parameters, $request));

        $response = $this->gateway()
            ->completePurchase($parameters)
            ->send();

        return $response;
    }

    /**
     * @param $amount
     */
    public function formatAmount($amount)
    {
        return number_format($amount, 2, '.', '');
    }

    /**
     * @param $order
     */
    public function getCancelUrl($order)
    {
        return route('checkout.payment.failed', $order->id);
    }

    /**
     * @param $order
     */
    public function getReturnUrl($order)
    {
        return route('checkout.payment.completed', $order->id);
    }
}

To store the API credentials required to connect with API service, you can use config/services.phpwhich ships with the framework out of the box.

    'securepay' => [
        'merchant_id' => env('SECUREPAY_MERCHANTID'),
        'password' => env('SECUREPAY_PASSWORD'),
        'sandbox' => env('SECUREPAY_SANDBOX', true)
    ],

Now, we need a controller class to handle and process all incoming requests into the application. Let's add a class PaymentController.php

<?php

namespace App\Http\Controllers;

use App\Order;
use App\SecurePay;
use Exception;
use Illuminate\Http\Request;

/**
 * Class PaymentController
 * @package App\Http\Controllers
 */
class PaymentController extends Controller
{
    /**
     * @param Request $request
     */
    public function checkout(Request $request)
    {
        $order = Order::findOrFail(mt_rand(1, 140));

        // you application logic goes here
        // the above order is just for example.

        return view('checkout.payment', compact('order'));
    }

    /**
     * @param $order_id
     * @param Request $request
     */
    public function payment($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = new SecurePay;

        try {
            $response = $gateway->purchase([
                'amount' => $gateway->formatAmount($order->amount),
                'transactionId' => $order->id,
                'currency' => 'USD',
                'cancelUrl' => $gateway->getCancelUrl($order),
                'returnUrl' => $gateway->getReturnUrl($order),
            ], $request);
        } catch (Exception $e) {
            $order->update(['payment_status' => Order::PAYMENT_PENDING]);

            return redirect()
                ->route('checkout.payment.failed', [$order->id])
                ->with('message', sprintf("Your payment failed with error: %s", $e->getMessage()));
        }

        if ($response->isRedirect()) {
            $response->redirect();
        }

        return redirect()->back()->with([
            'message' => "We're unable to process your payment at the moment, please try again !",
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function completed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        $gateway = new SecurePay;

        $response = $gateway->complete([
            'amount' => $gateway->formatAmount($order->amount),
            'transactionId' => $order->id,
            'currency' => 'USD',
            'cancelUrl' => $gateway->getCancelUrl($order),
            'returnUrl' => $gateway->getReturnUrl($order),
        ], $request);

        if ($response->isSuccessful()) {
            $order->update([
                'transaction_id' => $response->getTransactionReference(),
                'payment_status' => Order::PAYMENT_COMPLETED,
            ]);

            return redirect()->route('checkout.payment')->with([
                'message' => 'Payment Successful, Thank you for your order !',
            ]);
        }

        return redirect()->back()->with([
            'message' => $response->getMessage(),
        ]);
    }

    /**
     * @param $order_id
     * @param Request $request
     * @return mixed
     */
    public function failed($order_id, Request $request)
    {
        $order = Order::findOrFail($order_id);

        return view('checkout.payment', compact('order'));
    }
}

To submit the payment information by the customer we need a checkout page, for this example, I have implemented a nice view with the code snippet below.

<div class="card-details">
  <h3 class="title">Credit Card Details</h3>
  <span>@csrf</span>
  <div class="row">
    <div class="form-group col-sm-8">
      <label for="card-holder">Card Holder</label>
      <div class="input-group expiration-date">
          <input type="text" class="form-control" placeholder="Fist Name" name="first_name" aria-label="Fist Name" aria-describedby="basic-addon1">
          <span class="date-separator">/</span>
          <input type="text" class="form-control" placeholder="Last Name" name="last_name" aria-label="Last Name" aria-describedby="basic-addon1">
      </div>
    </div>
    <div class="form-group col-sm-4">
      <label for="">Expiration Date</label>
      <div class="input-group expiration-date">
        <input type="text" class="form-control" placeholder="MM" aria-label="MM" name="expiry_month" aria-describedby="basic-addon1">
        <span class="date-separator">/</span>
        <input type="text" class="form-control" placeholder="YY" aria-label="YY" name="expiry_year" aria-describedby="basic-addon1">
      </div>
    </div>
    <div class="form-group col-sm-8">
      <label for="card-number">Card Number</label>
      <input id="card-number" type="text" class="form-control" placeholder="Card Number" name="card_number" aria-label="Card Holder" aria-describedby="basic-addon1">
    </div>
    <div class="form-group col-sm-4">
      <label for="cvc">CVC</label>
      <input id="cvc" type="text" class="form-control" placeholder="CVC" aria-label="CVC" name="cvc" aria-describedby="basic-addon1">
    </div>
    <div class="form-group col-sm-12">
      <button type="submit" class="btn btn-primary btn-block">
          <i class="fa fa-credit-card" aria-hidden="true"></i>
          Pay with Credit Card
      </button>
    </div>
  </div>
</div>

To view the full HTML code click here.

The preview looks like below.

SecurePay Integration, Checkout

To view the full source code written while crafting this blog post, visit the github repository and grab the code.

Thanks for reading up to the end, I hope you enjoyed reading this article. If you have any feedback on this article feel free to leave your comment below.

Happy Coding!