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

Redsys Php Laravel Package

creagia/redsys-php

PHP package for Redsys payment gateway integration. Generate signed payment requests, redirect customers to the Redsys TPV, and validate notifications with a simple API. Ideal for Spanish online stores needing secure card payments through Redsys, with helpers for signing and response handling.

View on GitHub
Deep Wiki
Context7

Getting Started

creagia/redsys-php is a framework-agnostic PHP SDK for Redsys (the old Sermepa / TPV Virtual), the gateway behind most Spanish acquirers — Santander, BBVA, Sabadell, CaixaBank, Bankinter. If a Spanish client hands you a "FUC", a "terminal" and a base64 "clave secreta", this is the package you want. It is not a Laravel package: there is no service provider, no config file to publish, no facade. You get value objects and signature handling; the Laravel wiring is yours.

composer require creagia/redsys-php

Read in this order:

  1. README.md — the redirect + notification happy path, which is 90% of real integrations.
  2. src/RedsysClient.php — the only object that holds credentials and environment.
  3. src/Enums/ — Currency, ResponseCode, and (as of 3.0.3) the Apple Pay / Google Pay pay-method case.
  4. src/RedsysRequest.php / src/RedsysNotification.php — outbound request build and inbound callback verification.

First use case — a redirect checkout. Bind the client as a singleton so credentials come from config/services.php rather than being inlined:

// config/services.php
'redsys' => [
    'merchant_code' => env('REDSYS_MERCHANT_CODE'),
    'terminal'      => env('REDSYS_TERMINAL', 1),
    'secret_key'    => base64_decode(env('REDSYS_SECRET_KEY')),
    'environment'   => env('REDSYS_ENVIRONMENT', 'test'),
],
$this->app->singleton(RedsysClient::class, fn () => new RedsysClient(
    merchantCode: config('services.redsys.merchant_code'),
    terminal: (int) config('services.redsys.terminal'),
    secretKey: config('services.redsys.secret_key'),
    environment: config('services.redsys.environment'),
));

Then: create a local orders row (status = pending, your own 12-char reference, amount in cents), build the request with the order reference and both URL callbacks, and render the auto-submitting form to sis-t.redsys.es:25443 (test) / sis.redsys.es (live) from the package's generated POST data. Test end-to-end with the classic sandbox card 4548 8120 4940 0004, any future expiry, CVV 123 before you go anywhere near production keys.

Implementation Patterns

Treat the notification (server-to-server POST) as the only source of truth. The user's browser returning to your ok URL proves nothing — it is a GET the user can fake. Redirect them to a "processing" page, and let the notification handler flip the order to paid. This is the single most important architectural decision in a Redsys integration.

Route::post('/redsys/notify', RedsysNotificationController::class)
    ->withoutMiddleware([VerifyCsrfToken::class]); // or exclude in bootstrap/app.php

Verification order inside the handler (do it in this sequence, every time):

  1. Parse the payload into RedsysNotification and verify the signature with the client — reject on failure, log loudly.
  2. Look up the order by Ds_Order; 404 if unknown (someone is probing you).
  3. Compare Ds_Amount against the stored amount in cents — a valid signature on the wrong amount still means fraud or a bug.
  4. Check Ds_Response is in the 0000–0099 authorised range.
  5. Guard idempotency: if the order is already paid, return HTTP 200 and do nothing. Redsys retries.
  6. Return HTTP 200 with an empty body. Anything else and Redsys keeps retrying.

Keep the SDK out of your domain. Wrap it in a thin RedsysGateway service that takes your Order model and returns a DTO (redirectUrl, formData) or a PaymentResult. Controllers never touch RedsysClient, and you can swap to a different acquirer without rewriting checkout.

Refunds and cancellations reuse the same client. Build a request with the original order reference and the refund transaction type (3 for refund, 9 for cancellation), signed with the same secret. Store the original Ds_AuthorizationCode alongside the order so support can reconcile in the bank's panel.

Feature-test the callback, don't stub the SDK. Record one real notification payload from the sandbox (the full Ds_SignatureVersion / Ds_MerchantParameters / Ds_Signature trio), commit it as a fixture, and assert your controller marks the order paid. Add a second fixture with a tampered signature to assert rejection.

Wallets (new in 3.0.3). The Apple Pay / Google Pay case added in this release lets you carry a wallet-originated payment through the same request/notification path instead of hand-rolling Ds_Merchant_PayMethods. If you have a mobile app and were previously branching outside the SDK, this is the moment to collapse that custom code into the package's case. For everyone else it changes nothing.

Gotchas and Tips

  • Amounts are integers in cents. 1000 = €10.00. Sending 10.00 silently produces a €0.10 charge or a signature error depending on the acquirer.
  • Ds_Merchant_Order is constrained: 12 characters max, first four numeric, and it must be unique forever. A common pattern is date('md') + an incrementing ID padded. Refunds require the exact original string, so persist it verbatim — never derive it at read time.
  • "Invalid signature" is almost always environment or key. Test keys and live keys are different, and the secret must be the raw base64-decoded bytes. The second most common cause is whitespace in the .env value.
  • The URLs registered in the bank's TPV panel must match byte-for-byte, including trailing slash and www. A mismatch surfaces as a generic gateway error page, not an exception in your logs.
  • Exhaustive match on the package's enums will break on patch releases. 3.0.3 adds a wallet case; if you wrote match ($response->payMethod) { ... } with no default arm, you get an UnhandledMatchError at runtime for that traffic. Always include a default when matching enums you don't own — this is the one real upgrade risk in this release.
  • Notifications can arrive before the user's browser redirect and can arrive twice. Idempotent handlers + a "processing" interstitial page solve both.
  • Log the raw merchant parameters, and decode them into readable JSON when triaging. Redsys' error codes (0101 expired card, 0102 temporary bank refusal, 0190 denied by issuer) are far more legible than your own abstraction — surface them to support staff.
  • Don't store card data. With the redirect flow you stay in SAQ-A scope; the moment you post card fields to your own server you're in SAQ-D territory.
  • 3DS challenge flows are handled by Redsys' hosted page in the redirect model — but the Ds_Merchant_EMV3DS parameter set matters if you're doing in-app or MIT. Read the acquirer's manual for your specific merchant configuration before improvising; the package just passes the payload through.

On the 3.0.3 upgrade itself: additive, no breaking changes, no config or migration steps. composer update creagia/redsys-php and ship — provided you check for exhaustive enum match statements (see above).

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle