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
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.
| Key | Default | Description |
|---|
policy | automatic | Determines 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.
| Environment | API Base URL |
|---|
test | https://sandbox.opayo.eu.elavon.com/api/v1/ |
live | https://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:
| Property | Type | Description |
|---|
success | bool | Whether the initial authorization was successful |
status | string nullable | The 3DS status |
acsUrl | string nullable | The Access Control Server URL for the challenge |
acsTransId | string nullable | The ACS transaction ID |
dsTransId | string nullable | The Directory Server transaction ID |
cReq | string nullable | The challenge request (3DS 2.x) |
paReq | string nullable | The payer authentication request (3DS 1.x) |
transactionId | string nullable | The Opayo transaction ID |
message | string nullable | An 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
| Field | Type | Description |
|---|
id | id | Primary key |
user_id | foreignId | The associated user |
card_type | string | The card type (e.g., visa, mastercard) |
last_four | string | The last four digits of the card number |
token | string | The Opayo card identifier token |
auth_code | string nullable | The authentication code for recurring transactions |
expires_at | timestamp | The card expiration date |
created_at | timestamp | |
updated_at | timestamp | |
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:
| Event | Description |
|---|
opayoAuthorizationSuccessful | Emitted when a payment is successfully authorized |
opayoAuthorizationFailed | Emitted 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.
To include only the Sagepay vendor script (if the compiled JavaScript is already loaded separately):
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);