Skip to main content
Lunar’s Opayo addon integrates with Opayo’s (formerly SagePay) payment API to handle card payments, including 3D Secure authentication and saved card tokens.
This addon is currently in alpha. While every step is taken to ensure it works as intended, it will not be considered out of alpha until additional tests have been added.

Installation

Require the Composer package

composer require lunarphp/opayo

Configure the service

Add the Opayo credentials to config/services.php.
'opayo' => [
    'vendor' => env('OPAYO_VENDOR'),
    'env' => env('OPAYO_ENV', 'test'),
    'key' => env('OPAYO_KEY'),
    'password' => env('OPAYO_PASSWORD'),
],

Publish the configuration

This will publish the configuration under config/lunar/opayo.php.
php artisan vendor:publish --tag=lunar.opayo.config

Publish the views (optional)

The Opayo addon includes Blade and Livewire components for checkout. To customize these views, publish them.
php artisan vendor:publish --tag=lunar.opayo.components

Enable the driver

Set the driver in config/lunar/payments.php.
<?php

return [
    // ...
    'types' => [
        'card' => [
            // ...
            'driver' => 'opayo',
        ],
    ],
];

Configuration

The following options are available in config/lunar/opayo.php.
KeyDefaultDescription
policyautomaticDetermines the capture policy. automatic captures payment immediately; deferred authorizes first and captures later
The Opayo API endpoint is determined by the services.opayo.env value.
EnvironmentAPI Base URL
testhttps://sandbox.opayo.eu.elavon.com/api/v1/
livehttps://live.opayo.eu.elavon.com/api/v1/

Backend Usage

Get a merchant session key

A merchant session key is required before tokenizing card details on the client side.
use Lunar\Opayo\Facades\Opayo;

$merchantKey = Opayo::getMerchantKey();

Authorize a payment

use Lunar\Facades\Payments;
use Lunar\Facades\CartSession;

$response = Payments::driver('opayo')->cart(
    CartSession::current()->calculate()
)->withData([
    'merchant_key' => $request->get('merchantSessionKey'),
    'card_identifier' => $request->get('cardToken'),
    'browserLanguage' => $request->get('browserLanguage'),
    'challengeWindowSize' => $request->get('challengeWindowSize'),
    'browserIP' => $request->ip(),
    'browserAcceptHeader' => $request->header('accept'),
    'browserUserAgent' => $request->get('browserUserAgent'),
    'browserJavaEnabled' => $request->get('browserJavaEnabled', false),
    'browserColorDepth' => $request->get('browserColorDepth'),
    'browserScreenHeight' => $request->get('browserScreenHeight'),
    'browserScreenWidth' => $request->get('browserScreenWidth'),
    'browserTZ' => $request->get('browserTZ'),
    'status' => 'payment-received',
])->authorize();

Capture a deferred payment

When using the deferred policy, payments must be captured separately after authorization.
use Lunar\Facades\Payments;

Payments::driver('opayo')->capture($transaction, $amount);

Refund a payment

use Lunar\Facades\Payments;

Payments::driver('opayo')->refund($transaction, $amount, $notes);

Retrieve a transaction

Fetch transaction details from the Opayo API. This method includes retry logic with up to 4 attempts.
use Lunar\Opayo\Facades\Opayo;

$transaction = Opayo::getTransaction($transactionId);

3D Secure Authentication

When authorizing a payment, Opayo may require 3D Secure (3DS) authentication. The authorization response indicates whether a challenge is needed.

Handling the 3DS response

use Lunar\Opayo\Responses\ThreeDSecureResponse;

$response = Payments::driver('opayo')->cart($cart)->withData([
    // ...
])->authorize();

if ($response instanceof ThreeDSecureResponse) {
    return response()->json([
        'requires_auth' => true,
        'data' => $response,
    ]);
}
The ThreeDSecureResponse contains the following properties:
PropertyTypeDescription
successboolWhether the initial authorization was successful
statusstring nullableThe 3DS status
acsUrlstring nullableThe Access Control Server URL for the challenge
acsTransIdstring nullableThe ACS transaction ID
dsTransIdstring nullableThe Directory Server transaction ID
cReqstring nullableThe challenge request (3DS 2.x)
paReqstring nullableThe payer authentication request (3DS 1.x)
transactionIdstring nullableThe Opayo transaction ID
messagestring nullableAn optional message with additional details

Completing the 3DS challenge

After the customer completes the 3DS challenge on the storefront, authorize the payment again with the challenge response data. For 3DS 2.x:
use Lunar\Facades\Payments;

$response = Payments::driver('opayo')->cart($cart)->withData([
    'cres' => $request->get('cres'),
    'transaction_id' => $request->get('transaction_id'),
])->threedsecure();

if (! $response->success) {
    // Handle authentication failure
}
For 3DS 1.x:
$response = Payments::driver('opayo')->cart($cart)->withData([
    'pares' => $request->get('pares'),
    'transaction_id' => $request->get('transaction_id'),
])->threedsecure();
For more information on 3D Secure with Opayo, see:

Saved Cards

Authenticated users can save their card details for future purchases. The addon stores card tokens (not actual card details) in the opayo_tokens table.
Saved payments must be enabled on the Opayo account before this feature can be used.

Saving a card

Pass the saveCard data key when authorizing a payment.
use Lunar\Facades\Payments;

$response = Payments::driver('opayo')->cart($cart)->withData([
    // ...
    'saveCard' => true,
])->authorize();
After a successful authorization, a new entry is created in the opayo_tokens table associated with the authenticated user. If a token already exists with the same last four digits for that user, it is replaced.

Using a saved card

To pay with a previously saved card, pass the token as the card_identifier and set reusable to true.
use Lunar\Facades\Payments;

$response = Payments::driver('opayo')->cart($cart)->withData([
    // ...
    'card_identifier' => $savedToken->token,
    'reusable' => true,
])->authorize();
Responses are then handled the same as any other transaction, including 3D Secure challenges if required.

HasOpayoTokens trait

Add the HasOpayoTokens trait to the User model to access saved card tokens.
use Lunar\Opayo\Concerns\HasOpayoTokens;

class User extends Authenticatable
{
    use HasOpayoTokens;
}
This provides an opayoTokens relationship.
$tokens = $user->opayoTokens;

Token fields

FieldTypeDescription
ididPrimary key
user_idforeignIdThe associated user
card_typestringThe card type (e.g., visa, mastercard)
last_fourstringThe last four digits of the card number
tokenstringThe Opayo card identifier token
auth_codestring nullableThe authentication code for recurring transactions
expires_attimestampThe card expiration date
created_attimestamp
updated_attimestamp

Livewire Component

The addon includes a Livewire payment form component that handles merchant key retrieval, card tokenization, and 3D Secure challenges.
<livewire:opayo.payment :cart="$cart" />
The component emits the following events:
EventDescription
opayoAuthorizationSuccessfulEmitted when a payment is successfully authorized
opayoAuthorizationFailedEmitted when authorization fails

Including the scripts

Use the Blade directive to include both the compiled Opayo JavaScript and the Sagepay vendor script in the page layout.
@opayoScripts
To include only the Sagepay vendor script (if the compiled JavaScript is already loaded separately):
@opayoScripts(false)

AVS/CVC Checks

The addon captures Address Verification System (AVS) and Card Verification Code (CVC) check results from Opayo. These are stored in the transaction metadata and can be accessed through the payment checks method.
use Lunar\Facades\Payments;

$checks = Payments::driver('opayo')->getPaymentChecks($transaction);