Redsys
Redsys / Sermepa TPV Virtual payment provider plugin for MedusaJS v2
@jsm406/medusa-plugin-redsys
Redsys / Sermepa TPV Virtual payment provider plugin for MedusaJS v2.
This plugin enables payment processing through Redsys' hosted payment page (TPV Virtual) via redirect flow. Customers are redirected to the Redsys secure payment page to complete their transaction.
Production-proven: This plugin is derived from a live production Medusa store processing real Redsys payments.
Features
- Redsys hosted payment page / TPV Virtual redirect flow
- Bizum mobile payment support via Redsys TPV
- Sandbox and production environments
- One-step payment (immediate capture) and two-step payment (pre-authorization + capture)
- Full and partial refunds via Redsys API
- Payment cancellation
- Webhook handling with HMAC-SHA256 signature verification
- Secure webhook correlation: a payment is only authorized after a valid, fully matching webhook (order, session, amount, currency, provider, merchant, terminal and transaction type)
- Spanish error messages for Redsys response codes
- Zero PCI scope — card data is handled by Redsys' secure page
Prerequisites
- MedusaJS v2.22.3 or later
- Node.js v22.22.0 or later
- A Redsys merchant account (or sandbox test credentials)
- v5.3.2+ (installed automatically as a dependency)
Installation
Configuration
Environment Variables
Add the following to your file:
For sandbox testing, use the following test credentials from Redsys:
Medusa Configuration
In your :
Enable in Region
Enable the Redsys provider(s) in your Medusa admin panel under Settings > Regions:
- Credit/Debit Card: Select Redsys as a payment provider
- Provider ID:
- Bizum: Select Redsys Bizum as a payment provider
- Provider ID:
You can enable one or both providers depending on which payment methods you want to offer.
Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| string | Yes | — | Redsys HMAC-SHA256 secret key | |
| string | Yes | — | Redsys merchant code (FUC) | |
| string | No | Terminal number | ||
| string | No | or | ||
| string | No | — | Webhook URL for Redsys to POST transaction results | |
| string | No | — | URL to redirect after successful payment (URLOK) | |
| string | No | — | URL to redirect after failed payment (URLKO) | |
| string | No | = immediate capture, = pre-authorization |
Payment Flow
- Customer selects Redsys as payment method → Medusa creates a Payment Session ()
- generates a secure Redsys , stores a payment reference ( ↔ payment session, amount, currency, merchant, terminal, transaction type) in the payment session , and creates a signed redirect form
- Customer clicks "Place Order" → the storefront saves in and auto-submits the redirect form to Redsys TPV. is NOT called here — no order is created before the payment exists
- Customer completes payment on the Redsys hosted payment page
- Redsys sends a webhook notification to
- verifies the HMAC signature and validates the order, session, amount, currency, provider, merchant, terminal and transaction type against the stored reference, then marks it as confirmed
- Medusa authorizes/captures the payment and completes the cart server-side, creating the order
- Redsys redirects the customer's browser to or with the Redsys order ID as a query parameter
- Storefront callback page reads the saved , retries if needed, and redirects to the order confirmation page
Security: authorizePayment Behavior
A payment session is only authorized after a valid HMAC-confirmed webhook that fully matches the stored payment reference. returns for any session whose reference is missing, not confirmed, or mismatched (amount, currency, session, provider, merchant or transaction type). It never trusts the stored field, so a — or even a forged — status can never authorize a payment on its own.
ID Mapping (Redsys → Medusa)
The plugin generates a 12-character alphanumeric (e.g. ) used as Redsys' merchant order reference. When the cart is completed after payment, Medusa generates its own order ID (e.g. ). These are different IDs.
The callback URL from Redsys only contains the Redsys order ID, not the Medusa order ID. To bridge this gap, the storefront stores the mapping → in before redirecting to the TPV. The callback page uses the to retrieve/complete the order and redirect to the correct confirmation page.
Storefront Integration
Redsys is a redirect-based payment method (no card input in your storefront — the customer enters card data on Redsys' secure TPV). You must adapt your Medusa Next.js storefront with the changes below.
Security: starting with v1.1.1 the storefront must not call before redirecting to Redsys. Doing so would fail anyway (the payment is not authorized yet) and previously created unpaid orders. The Redsys webhook completes the cart after the payment is confirmed.
1. — Register the payment methods
Add Redsys and Bizum to the payment info map and add helper functions:
2. Payment buttons and callback page
The full copyable implementation lives in . The two critical changes vs. older versions:
- The payment buttons no longer call before redirecting to Redsys. They only store in under and submit the redirect form. The HMAC-confirmed webhook completes the cart server-side.
- The callback page reads from the URL, recovers the , and retries with exponential backoff to bridge the race between the browser redirect and the webhook arrival.
3. — Bypass region redirect
If your storefront uses middleware to enforce region/country code prefixes in URLs (as the default Medusa Next.js storefront does), add a bypass so is not redirected. Add this early in the function:
4. — CORS
Ensure your storefront domain is allowed in CORS:
Session Data Reference
The payment session field returned by :
Note: The identifier in the callback URL is the value returned by the library and is normal. This does not indicate a problem — the actual signature computation follows the Redsys v4.1 specification.
Webhook
Medusa automatically exposes webhook endpoints for the Redsys providers at:
For local development with sandbox, you must expose your backend to the internet (e.g., via ngrok) so Redsys can reach the webhook. Set to the ngrok URL.
Important: Redsys sends the notification to but the signature verification and payment status update happens through the Medusa webhook handler — make sure points to the same endpoint or forward notifications accordingly.
Persistence: payment session data
Since v1.1.3 the plugin stores everything it needs in the Medusa Payment Session field, so it needs no direct database access and no extra table or migration:
| Field | Purpose |
|---|---|
| Redsys order ID generated by the plugin | |
| Real Medusa payment session () | |
| Medusa cart, if available | |
| , , | Expected amount/currency |
| (provider option), , | Expected merchant/terminal/type |
| Only set to by a validated webhook | |
| , | Set by the validated webhook |
A webhook can only confirm a payment that the plugin itself created in /, and only if every field matches.
Test Cards (Sandbox)
Card Payments
| Card Number | Brand | Behavior |
|---|---|---|
| 4548810000000003 | VISA | 3DS v2 approved |
| 5576441563045037 | Mastercard | 3DS v2 approved |
| 4548814479727229 | VISA | 3DS frictionless |
| 4548817212493017 | VISA | 3DS challenge |
| Any + CVV 999 | Any | Payment declined |
Bizum (Sandbox)
Important: In sandbox, Bizum transactions cannot exceed 10€. Use a discount coupon or low-price test product.
| Field | Value |
|---|---|
| Phone number | |
| PIN | |
| SMS code |
Test scenarios by amount:
| Amount | Result |
|---|---|
| < 5€ | Payment approved |
| 5€ - 10€ | Payment approved |
| 10€ - 15€ | Payment declined (exceeds sandbox limit) |
| > 15€ | Payment declined (no Bizum user) |
Transaction Types
| Code | Type | Description |
|---|---|---|
| Payment | Authorization + immediate capture (default) | |
| Pre-authorization | Reserve funds only | |
| Confirmation | Capture pre-authorized funds | |
| Refund | Full or partial refund | |
| Cancellation | Cancel/void a transaction |
Security
- Never log PAN, CVV, or the secret key. The provider strips sensitive fields from log output.
- Always validate signatures server-side. uses 's , which verifies the HMAC signature before anything else.
- The webhook is the only source of truth. A payment is authorized only when a valid webhook confirms a payment the plugin previously recorded, and the amount, currency, provider, merchant, terminal and transaction type all match. Reusing an old is impossible because each payment session stores its own order data.
- Use HTTPS for all communication with Redsys.
- Do not trust client-side payment data. The webhook with signature verification is the source of truth.
- Amounts are normalized. is received in the smallest unit (e.g. ) and converted back to the main unit () before being passed to Medusa.
- Unsupported currencies fail closed. An unknown currency throws instead of silently charging in EUR.
- The redirect flow keeps you out of PCI scope — card data is handled by Redsys' secure page.
Upgrading from < 1.1.1
Sessions created before v1.1.1 do not carry a and have no payment reference, so they will not be authorized (fail-closed). Customers in the middle of a checkout will need to refresh / recreate their payment session. This is intentional: it is safer to reject than to authorize an unverified payment.
Currency Support
The plugin includes built-in numeric currency codes for all major currencies (see for the full list). Unsupported currencies are rejected with an error rather than silently falling back to EUR.
Development
Local Testing with a Medusa Project
Acknowledgements
- TomatitoCodes — for discovering and responsibly reporting the vulnerability in the webhook/HMAC authorization flow. His finding drove the security hardening released in v1.1.1–v1.1.4: a payment session is no longer authorized from client-controlled session state and only an HMAC-verified, fully matching Redsys webhook can confirm it.
License
MIT — see LICENSE file for details.
Version History
v1.2.2 (2026-11-04)
- Updated: MedusaJS dependencies to v2.22.3 (, , , )
- Verified: No source changes required — the plugin builds, typechecks and passes its test suite against Medusa v2.22.3
v1.2.1 (2026-11-02)
- Updated: MedusaJS dependencies to v2.22.2 (, , , )
- Verified: No source changes required — the plugin builds, typechecks and passes its test suite against Medusa v2.22.2
v1.2.0 (2026-10-25)
- Updated: MedusaJS dependencies to v2.22.1 (, , , )
- Updated: Node.js engine requirement to (required by Medusa v2.22)
- Updated: to v5.3.2 and dev tooling (, , , )
- Docs: Added acknowledgement for TomatitoCodes and documented the current Payment Session persistence model
v1.1.2 (2026-08-31)
- Fixed: Install-time peer dependency conflict — removed from (aligned with the official convention; only is required).
v1.1.1 (2026-08-31) — Security release
- SECURITY: Never authorize a Redsys payment before an HMAC-confirmed webhook
- SECURITY: Correlate webhooks with the real Medusa payment session ID () instead of an artificial ID
- SECURITY: Validate order, amount, currency, provider, merchant, terminal and transaction type before confirming a webhook
- SECURITY: Persist a payment reference () for every initiated payment, preventing reuse of previously confirmed s
- SECURITY: Generate Redsys order IDs using cryptographic randomness instead of
- SECURITY: Reject unsupported currencies instead of silently falling back to EUR
- FIX: Normalize webhook amounts ( in cents → main unit) before passing them to Medusa
- FIX: Return for preauthorizations and for immediate captures in webhook actions
- FIX: Tighten Redsys response-code validation (4-digit payment codes 0000-0099, confirmation/refund 900, cancellation 400)
- FIX: Make generate a fresh and keep MerchantData at fixed 3-position layout
- FIX: Storefront flow no longer creates the order before the payment (see )
v1.1.0 (2026-06-16)
- Added: Bizum payment method support via Redsys TPV
- New provider with parameter
- Full lifecycle support: initiate, authorize, capture, cancel, refund, webhook
- Same configuration as card provider (can share credentials)
- Separate webhook endpoint at
v1.0.12 (2026-05-13)
- Fixed: Response code validation for payment authorization (codes 0-99), refunds/confirmations (code 900), cancellations (code 400)
- Note: The in the callback URL is normal - it's the identifier returned by the redsys-es library. The actual HMAC computation follows Redsys v4.1 specification.
v1.0.0 (2025-05-05)
- Initial release
Support
For issues and questions, please open an issue on GitHub.

