Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sermepa Omnipay Laravel Package

jordisala1991/sermepa-omnipay

Omnipay gateway driver for Sermepa (RedSys), the payment platform used by Spanish banks. Adds payment request, response, and notification handling to PHP and Laravel apps through the Omnipay API.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm the Omnipay version it pins

composer require jordisala1991/sermepa-omnipay
composer show omnipay/common

This driver dates from early 2017, which almost certainly means it targets omnipay/common ~2.x. If your Laravel app already uses Omnipay 3 (PSR-7 requests/responses, sendData()/send() split, namespaced RequestInterface), Composer will either refuse to install or you'll end up with two Omnipay majors in the tree. Before writing any code:

composer why-not omnipay/common ^3.0

If you're on Omnipay 3 today, treat this package as a reference implementation to fork, not a dependency. Copy src/ into app/Payments/Sermepa/, fix the namespace, and keep it under your own tests.

2. Where to look first

  • src/Gateway.php — the getDefaultParameters() block tells you the exact parameter names (merchantId, terminalId, merchantKey, currency, testMode, …). This is your source of truth for config keys.
  • src/Message/PurchaseRequest.php (or equivalent) — shows how Omnipay's decimal amount is converted to Redsys integer cents, how orderId is formatted, and which URL fields (urlOk, urlKo, urlNotify) get populated from returnUrl/notifyUrl.
  • src/Message/PurchaseResponse.php — shows whether the redirect is a form POST (getRedirectResponse()) and how the Ds_SignatureVersion / Ds_MerchantParameters / Ds_Signature triple is built.
  • tests/ — the sample payloads are the fastest way to unit-test your callback controller without hitting Redsys.
  • The Redsys "Manual de integración" PDF — because the driver is old and the gateway's rules (HMAC_SHA256_V1, key derivation) are non-obvious.

3. First use case: hosted payment page redirect

Configure in config/services.php:

'sermepa' => [
    'merchant_id'  => env('SERMEPA_MERCHANT_ID'),   // FUC, e.g. 999008881
    'terminal_id'  => env('SERMEPA_TERMINAL_ID', '1'),
    'merchant_key' => env('SERMEPA_MERCHANT_KEY'),  // from the bank's admin panel
    'test_mode'    => env('SERMEPA_TEST_MODE', true),
],

Bind the gateway once in a service provider:

$this->app->singleton('sermepa', function () {
    $gateway = \Omnipay\Omnipay::create('Sermepa'); // or `new \Omnipay\Sermepa\Gateway()`
    $gateway->setMerchantId(config('services.sermepa.merchant_id'));
    $gateway->setTerminalId(config('services.sermepa.terminal_id'));
    $gateway->setMerchantKey(config('services.sermepa.merchant_key'));
    $gateway->setTestMode(config('services.sermepa.test_mode'));
    return $gateway;
});

Purchase in a controller:

public function pay(Order $order)
{
    $response = app('sermepa')->purchase([
        'amount'      => number_format($order->total, 2, '.', ''), // '49.90'
        'currency'    => 'EUR',
        'orderId'     => $order->reference,                        // ≤12 chars
        'description' => 'Order '.$order->reference,
        'returnUrl'   => route('checkout.return'),
        'notifyUrl'   => route('checkout.notify'),
    ])->send();

    if ($response->isRedirect()) {
        return $response->redirect();          // auto-submitting form to sis.redsys.es
    }

    report(new \RuntimeException($response->getMessage()));
    return back()->withErrors('Payment could not be started.');
}

The first end-to-end milestone is: sandbox credentials → test card (4548812049400004, exp 12/20, CVV 123 on the standard Redsys sandbox) → you land on urlOk and Redsys POSTs to notifyUrl.

Implementation Patterns

Treat the redirect and the notification as two independent events. Redsys will call notifyUrl server-to-server and redirect the browser to urlOk/urlKo. Either can arrive first. Your controller must be idempotent:

public function notify(Request $request)
{
    $response = app('sermepa')->completePurchase([
        // if the driver reads $_POST internally, no args are needed;
        // if it accepts request params, pass them explicitly
        'request_params' => $request->all(),
    ])->send();

    DB::transaction(function () use ($response, $request) {
        $order = Order::where('reference', $request->input('Ds_Merchant_Order'))
            ->lockForUpdate()
            ->firstOrFail();

        if ($order->status !== 'paid' && $response->isSuccessful()) {
            $order->markPaid($response->getTransactionReference());
        }
    });

    return response('OK', 200); // Redsys wants a 200
}

Never mark an order paid from the returnUrl handler. The browser redirect is user-controllable. returnUrl should only display status; the authoritative transition happens in notifyUrl after signature verification. If the notification is delayed, the return page shows "pending" and refreshes — that's correct behavior.

Wrap the gateway behind your own small interface. The driver is abandoned; a PaymentGateway contract with start() and confirm() methods means swapping to the Redsys REST API or a maintained driver later is a one-file change.

Format orderId deliberately. Redsys requires the order number to be ≤ 12 characters and to start with 4 digits. A common convention is sprintf('%04d', $order->id % 10000) . substr($uuid, 0, 8). Store it on the order; you need it to look up the row on notification, and it's the seed for the HMAC key derivation.

Amounts. Omnipay's amount is a decimal string; the driver multiplies by 100 and casts to int. Use number_format($total, 2, '.', '') rather than (string) $total to avoid 49.9 → 4989 rounding bugs. Redsys is EUR-only with currency code 978.

Queue side effects. The notify endpoint should persist the payment row and dispatch a job (send receipt, provision access). Redsys times out on slow notifications and retries, amplifying any latency.

Testing without calling Redsys. Use the driver's own test fixtures: build a fake Request::create('/notify', 'POST', $fixture) and assert your controller transitions the order. Also add a test that a payload with a tampered Ds_Signature is rejected.

Gotchas and Tips

The merchantKey is base64 in the admin panel — and the driver may or may not decode it. Redsys hands you a key like sq7HjrUOBfKmC576ILgskD5srU870gJ7 which is base64 for a binary key used in HMAC-SHA256 over a 3DES-derived per-order key. Some drivers expect the raw panel string and base64_decode() internally; others expect the already-decoded bytes. If you get SIS0042 (signature error) in sandbox, try both. This is the single most common integration failure.

Signature version. Since 2017 Redsys requires HMAC_SHA256_V1. If this driver predates the cut-over or defaults to SHA-1, every real transaction fails while the sandbox "kind of" works. Check Ds_SignatureVersion in PurchaseRequest.

completePurchase() may read $_POST directly. Omnipay 2 drivers often do. That bypasses Laravel's request lifecycle and makes testing hard. Grep the source for $_POST; if found, patch the driver to accept request_params from the constructor — it's a five-line change and worth doing before you build on top of it.

Endpoints. The sandbox lives at https://sis-t.redsys.es:25443/sis/realizarPago (note the non-443 port) and production at https://sis.redsys.es/sis/realizarPago. Set testMode explicitly; don't rely on defaults. Corporate proxies frequently block port 25443 — if the sandbox form POSTs and nothing happens, check egress rules.

Description and name length limits. Ds_Merchant_ProductDescription is 125 chars, Ds_Merchant_MerchantName is 25, and the URL fields are capped too. Truncate in PHP with mb_substr; overlong values cause opaque SIS errors rather than validation failures.

Duplicate order numbers are rejected. Retries after a failed signature or a user double-submit with the same orderId may come back as a duplicate-order error. Generate a fresh order reference per payment attempt and link attempts to the order.

Error codes are your debugging lever. Log the full decoded Ds_MerchantParameters JSON alongside Ds_Response and the Redsys error code. SIS0042 = signature mismatch, SIS0051 = duplicate order, SIS0023 = merchant/terminal config problem. The gateway returns these in getMessage() / the response payload, but you have to log it — the browser only sees urlKo.

Callback body. Return HTTP 200. Some integrations expect a literal OK body; either way, a 500 from Laravel puts Redsys into a retry loop that will keep hammering your endpoint.

Do not trust Ds_Response = 0000 alone. Success is 0000 in Ds_Response and a valid signature and a matching amount/currency/order. The driver's isSuccessful() should cover this, but verify the amount against your stored order inside the transaction — it's cheap insurance.

Extension points if you fork. Gateway::getDefaultParameters(), PurchaseRequest::getData() (form fields), and PurchaseResponse::getRedirectResponse() are the three seams you'll touch. Add authorize()/capture() only if your merchant contract supports pre-auth; most Sermepa/Redsys accounts are sale-only, so don't over-model.

Maintenance reality check. Zero stars, zero dependents, last release March 2017. There is no one to send a security patch. Budget an afternoon to vendor the source, pin it in app/Payments/, and write signature tests against real Redsys fixtures. If the fork grows beyond a couple hundred lines, evaluate replacing it with the official Redsys REST API (JSON over TLS, notifyUrl + https://apis-prelive.redsys.es/apis.redsys.es endpoints) — it's a cleaner long-term bet than maintaining a 2017 Omnipay 2 driver.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core