XUtils

OmniPay

A framework agnostic multi-gateway payment processing library.


TL;DR

Just want to see some code?

use Omnipay\Omnipay;

$gateway = Omnipay::create('Stripe');
$gateway->setApiKey('abc123');

$formData = array('number' => '4242424242424242', 'expiryMonth' => '6', 'expiryYear' => '2030', 'cvv' => '123');
$response = $gateway->purchase(array('amount' => '10.00', 'currency' => 'USD', 'card' => $formData))->send();

if ($response->isRedirect()) {
    // redirect to offsite payment gateway
    $response->redirect();
} elseif ($response->isSuccessful()) {
    // payment was successful: update database
    print_r($response);
} else {
    // payment failed: display message to customer
    echo $response->getMessage();
}

As you can see, Omnipay has a consistent, well thought out API. We try to abstract as much as possible the differences between the various payments gateways.

Credit Card / Payment Form Input

User form input is directed to an CreditCard object. This provides a safe way to accept user input.

The CreditCard object has the following fields:

  • firstName
  • lastName
  • number
  • expiryMonth
  • expiryYear
  • startMonth
  • startYear
  • cvv
  • issueNumber
  • type
  • billingAddress1
  • billingAddress2
  • billingCity
  • billingPostcode
  • billingState
  • billingCountry
  • billingPhone
  • shippingAddress1
  • shippingAddress2
  • shippingCity
  • shippingPostcode
  • shippingState
  • shippingCountry
  • shippingPhone
  • company
  • email

Even off-site gateways make use of the CreditCard object, because often you need to pass customer billing or shipping details through to the gateway.

The CreditCard object can be initialized with untrusted user input via the constructor. Any fields passed to the constructor which are not recognized will be ignored.

$formInputData = array(
    'firstName' => 'Bobby',
    'lastName' => 'Tables',
    'number' => '4111111111111111',
);
$card = new CreditCard($formInputData);

You can also just pass the form data array directly to the gateway, and a CreditCard object will be created for you.

CreditCard fields can be accessed using getters and setters:

$number = $card->getNumber();
$card->setFirstName('Adrian');

If you submit credit card details which are obviously invalid (missing required fields, or a number which fails the Luhn check), InvalidCreditCardException will be thrown. You should validate the card details using your framework’s validation library before submitting the details to your gateway, to avoid unnecessary API calls.

For on-site payment gateways, the following card fields are generally required:

  • firstName
  • lastName
  • number
  • expiryMonth
  • expiryYear
  • cvv

You can also verify the card number using the Luhn algorithm by calling Helper::validateLuhn($number).

The Payment Response

The payment response must implement ResponseInterface. There are two main types of response:

  • Payment was successful (standard response)
  • Website requires redirect to off-site payment form (redirect response)

Successful Response

For a successful responses, a reference will normally be generated, which can be used to capture or refund the transaction at a later date. The following methods are always available:

$response = $gateway->purchase(array('amount' => '10.00', 'card' => $card))->send();

$response->isSuccessful(); // is the response successful?
$response->isRedirect(); // is the response a redirect?
$response->getTransactionReference(); // a reference generated by the payment gateway
$response->getTransactionId(); // the reference set by the originating website if available.
$response->getMessage(); // a message generated by the payment gateway

In addition, most gateways will override the response object, and provide access to any extra fields returned by the gateway. If the payment authorization is re-usable the gateway will implement $response->getCardReference();. This method is always available (but may return NULL) from 3.1.1

Redirect Response

The redirect response is further broken down by whether the customer’s browser must redirect using GET (RedirectResponse object), or POST (FormRedirectResponse). These could potentially be combined into a single response class, with a getRedirectMethod().

After processing a payment, the cart should check whether the response requires a redirect, and if so, redirect accordingly:

$response = $gateway->purchase(array('amount' => '10.00', 'card' => $card))->send();
if ($response->isSuccessful()) {
    // payment is complete
} elseif ($response->isRedirect()) {
    $response->redirect(); // this will automatically forward the customer
} else {
    // not successful
}

The customer isn’t automatically forwarded on, because often the cart or developer will want to customize the redirect method (or if payment processing is happening inside an AJAX call they will want to return JS to the browser instead).

To display your own redirect page, simply call getRedirectUrl() on the response, then display it accordingly:

$url = $response->getRedirectUrl();
// for a form redirect, you can also call the following method:
$data = $response->getRedirectData(); // associative array of fields which must be posted to the redirectUrl

Error Handling

You can test for a successful response by calling isSuccessful() on the response object. If there was an error communicating with the gateway, or your request was obviously invalid, an exception will be thrown. In general, if the gateway does not throw an exception, but returns an unsuccessful response, it is a message you should display to the customer. If an exception is thrown, it is either a bug in your code (missing required fields), or a communication error with the gateway.

You can handle both scenarios by wrapping the entire request in a try-catch block:

try {
    $response = $gateway->purchase(array('amount' => '10.00', 'card' => $card))->send();
    if ($response->isSuccessful()) {
        // mark order as complete
    } elseif ($response->isRedirect()) {
        $response->redirect();
    } else {
        // display error to customer
        exit($response->getMessage());
    }
} catch (\Exception $e) {
    // internal error, log exception and display a generic message to the customer
    exit('Sorry, there was an error processing your payment. Please try again later.');
}

Test mode and developer mode

Most gateways allow you to set up a sandbox or developer account which uses a different url and credentials. Some also allow you to do test transactions against the live site, which does not result in a live transaction.

Gateways that implement only the developer account (most of them) call it testMode. Authorize.net, however, implements both and refers to this mode as developerMode.

When implementing with multiple gateways you should use a construct along the lines of the following:

if ($is_developer_mode) {
    if (method_exists($gateway, 'setDeveloperMode')) {
        $gateway->setDeveloperMode(TRUE);
    } else {
        $gateway->setTestMode(TRUE);
    }
}

Recurring Billing

At this stage, automatic recurring payments functionality is out of scope for this library. This is because there is likely far too many differences between how each gateway handles recurring billing profiles. Also in most cases token billing will cover your needs, as you can store a credit card then charge it on whatever schedule you like. Feel free to get in touch if you really think this should be a core feature and worth the effort.

Incoming Notifications

Some gateways (e.g. Cybersource, GoPay) offer HTTP notifications to inform the merchant about the completion (or, in general, status) of the payment. To assist with handling such notifications, the acceptNotification() method will extract the transaction reference and payment status from the HTTP request and return a generic NotificationInterface.

$notification = $gateway->acceptNotification();

$notification->getTransactionReference(); // A reference provided by the gateway to represent this transaction
$notification->getTransactionStatus(); // Current status of the transaction, one of NotificationInterface::STATUS_*
$notification->getMessage(); // Additional message, if any, provided by the gateway

// update the status of the corresponding transaction in your database

Note: some earlier gateways used the completeAuthorize and completePurchase messages to handle the incoming notifications. These are being converted and the complete* messages deprecated. They won’t be removed in OmniPay 2.x, but it is advisable to switch to the acceptNotification message when convenient. An example is Sage Pay Server completeAuthorize which is now handled by acceptNotification.

Example Application

An example application is provided in the omnipay/example repo. You can run it using PHP’s built in web server (PHP 5.4+):

$ php composer.phar update --dev
$ php -S localhost:8000

For more information, see the Omnipay example application.

Security

If you discover any security related issues, please email barryvdh@gmail.com instead of using the issue tracker.

Feedback

Please provide feedback! We want to make this library useful in as many projects as possible. Please head on over to the mailing list and point out what you do and don’t like, or fork the project and make suggestions. No issue is too small.


Articles

  • coming soon...