• Сообщество
  • Связаться с нами
Документация
Плагины и интеграцииВсе расширения для Medusa от сообществаЭкспертыПодберите специалиста для разработки и развития вашего проекта на Medusa
КейсыПосмотрите примеры Medusa в продакшене и успешные внедрения
Меч Moscow
Комплексная e-commerce платформа на Medusa для московского fashion-бренда

Меч Moscow · Fashion

Нужна помощь в разработке плагина интеграции для Medusa?

Написать намНайти плагины

Gorgo снижает затраты на адаптацию Medusa к локальным рынкам.

Мы разрабатываем плагины интеграции, осуществляем поддержку и развиваем сообщество разработчиков на Medusa в Telegram.

  • Ресурсы Medusa
  • Плагины и интеграции
  • Эксперты
  • Кейсы
  • Medusa Чат в Telegram
  • Medusa Новости в Telegram
  • Документация Gorgo
  • Связаться с нами
  • head@gorgojs.com
  • TelegramGitHub
MedusaПлагиныRedsys
R

Redsys

Redsys / Sermepa TPV Virtual payment provider plugin for MedusaJS v2

Нужна доработка этого плагина?

Связаться с нами
npm install medusa-payment-redsys
Категория
Платежи
Создано
Juansoler
Версия
1.0.2
Последнее обновление
3 дня назад
Ежемесячные загрузки
429
Звезды на Github
0
npmNPMGitHubGithub
MedusaПлагиныRedsys

medusa-payment-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
  • 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
  • Spanish error messages for Redsys response codes
  • Zero PCI scope — card data is handled by Redsys' secure page

Prerequisites

  • MedusaJS v2.13.0 or later
  • Node.js v20 or later
  • A Redsys merchant account (or sandbox test credentials)
  • v5.3.0+ (installed automatically as a dependency)

Installation

npm install medusa-payment-redsys
# or
yarn add medusa-payment-redsys
# or
pnpm add medusa-payment-redsys

Configuration

Environment Variables

Add the following to your file:

REDSYS_SECRET_KEY=sq7Hj....
REDSYS_MERCHANT_CODE=999008881
REDSYS_TERMINAL=001
REDSYS_ENVIRONMENT=sandbox
REDSYS_NOTIFICATION_URL=https://your-api.com/hooks/payment/redsys_redsys
REDSYS_SUCCESS_URL=https://your-store.com/checkout/redsys-callback
REDSYS_ERROR_URL=https://your-store.com/checkout/redsys-callback?error=1

For sandbox testing, use the following test credentials from Redsys:

Medusa Configuration

In your :

import { defineConfig } from "@medusajs/framework/config"
export default defineConfig({
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "medusa-payment-redsys/providers/redsys",
id: "redsys",
options: {
secretKey: process.env.REDSYS_SECRET_KEY,
merchantCode: process.env.REDSYS_MERCHANT_CODE,
terminal: process.env.REDSYS_TERMINAL || "001",
environment:
process.env.REDSYS_ENVIRONMENT || "sandbox",
notificationUrl:
process.env.REDSYS_NOTIFICATION_URL,
successUrl: process.env.REDSYS_SUCCESS_URL,
errorUrl: process.env.REDSYS_ERROR_URL,
transactionType: "0", // "0" = immediate capture, "1" = pre-authorization
},
},
],
},
},
],
})

Enable in Region

Enable the Redsys provider in your Medusa admin panel under Settings > Regions and select Redsys as a payment provider.

The provider ID will be:

Options

OptionTypeRequiredDefaultDescription
stringYes—Redsys HMAC-SHA256 secret key
stringYes—Redsys merchant code (FUC)
stringNoTerminal number
stringNo or
stringNo—Webhook URL for Redsys to POST transaction results
stringNo—URL to redirect after successful payment (URLOK)
stringNo—URL to redirect after failed payment (URLKO)
stringNo = immediate capture, = pre-authorization

Payment Flow

  1. Customer selects Redsys as payment method
  2. creates a signed redirect form with Redsys merchant parameters
  3. Customer clicks "Place Order" → storefront calls to create the order, then auto-submits the redirect form to Redsys TPV
  4. Customer completes payment on the Redsys hosted payment page
  5. Redsys sends a webhook notification to
  6. validates the HMAC-SHA256 signature and updates the payment status
  7. Redsys redirects the customer's browser to or

Important: authorizePayment Behavior

This plugin's returns for sessions with status and . This is intentional for the redirect flow: the real authorization happens on Redsys TPV and is confirmed via webhook. Without this, would fail with a 400 error because Medusa requires the payment session to be authorized before completing the cart.

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.

1. — Register the payment method

Add Redsys to the payment info map and add a helper function:

// Inside paymentInfoMap, add:
pp_redsys_redsys: {
title: "Credit / Debit Card",
icon: <CreditCard />,
},
// Add helper function:
export const isRedsys = (providerId?: string) => {
return providerId?.startsWith("pp_redsys_")
}

2. — Add order completion without redirect

Add a function. The standard does a (server-side), but Redsys needs to redirect the browser to the TPV instead. This function completes the cart, creates the order, but returns the result so the client can handle the TPV redirect:

export async function completeCartWithoutRedirect(cartId?: string) {
const id = cartId || (await getCartId())
if (!id) {
throw new Error("No existing cart found when completing cart")
}
const headers = {
...(await getAuthHeaders()),
}
const cartRes = await sdk.store.cart
.complete(id, {}, headers)
.then(async (cartRes) => {
const cartCacheTag = await getCacheTag("carts")
revalidateTag(cartCacheTag)
return cartRes
})
.catch(medusaError)
if (cartRes?.type === "order") {
const orderCacheTag = await getCacheTag("orders")
revalidateTag(orderCacheTag)
removeCartId()
}
return cartRes
}

3. — Redsys payment button

Add a component that:

  1. Reads the payment session data (formUrl, merchantParams, signature) from the cart
  2. Calls to create the order
  3. Dynamically builds and auto-submits a to Redsys' TPV
// Add import:
import { isManual, isRedsys, isStripeLike } from "@lib/constants"
import { completeCartWithoutRedirect, placeOrder } from "@lib/data/cart"
// Add case in PaymentButton's switch:
case isRedsys(paymentSession?.provider_id):
return (
<RedsysPaymentButton
notReady={notReady}
cart={cart}
data-testid={dataTestId}
/>
)
// Add the component:
const RedsysPaymentButton = ({
cart,
notReady,
"data-testid": dataTestId,
}: {
cart: HttpTypes.StoreCart
notReady: boolean
"data-testid"?: string
}) => {
const [submitting, setSubmitting] = useState(false)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const handlePayment = async () => {
setSubmitting(true)
const paymentSession = cart.payment_collection?.payment_sessions?.find(
(s) => s.status === "pending" && isRedsys(s.provider_id)
)
const redsysData = paymentSession?.data as Record<string, string> | undefined
if (!redsysData?.formUrl || !redsysData?.merchantParams || !redsysData?.signature) {
setErrorMessage("No se pudieron obtener los datos de pago de Redsys")
setSubmitting(false)
return
}
const cartRes = await completeCartWithoutRedirect()
.catch((err) => {
setErrorMessage(err.message)
setSubmitting(false)
return null
})
if (!cartRes || cartRes.type !== "order") {
setErrorMessage(cartRes ? "Error al crear el pedido" : "")
setSubmitting(false)
return
}
const form = document.createElement("form")
form.method = "POST"
form.action = redsysData.formUrl
const fields: Record<string, string> = {
Ds_SignatureVersion: redsysData.signatureVersion,
Ds_MerchantParameters: redsysData.merchantParams,
Ds_Signature: redsysData.signature,
}
Object.entries(fields).forEach(([name, value]) => {
const input = document.createElement("input")
input.type = "hidden"
input.name = name
input.value = value
form.appendChild(input)
})
document.body.appendChild(form)
form.submit()
}
return (
<>
<Button
disabled={notReady || submitting}
isLoading={submitting}
onClick={handlePayment}
size="large"
data-testid={dataTestId}
>
Place order
</Button>
<ErrorMessage
error={errorMessage}
data-testid="redsys-payment-error-message"
/>
</>
)
}

4. — Callback page (new file)

Create the page Redsys redirects to after payment. It reads the query param and redirects to the order confirmation page:

import { retrieveOrder } from "@lib/data/orders"
import { Metadata } from "next"
import { redirect } from "next/navigation"
export const metadata: Metadata = {
title: "Resultado del pago",
description: "Resultado de la operación con Redsys",
}
type Props = {
searchParams: Promise<{ [key: string]: string | undefined }>
}
export default async function RedsysCallbackPage(props: Props) {
const searchParams = await props.searchParams
const isError = searchParams.error === "1"
const orderId = searchParams.orderId
if (isError) {
return (
<div className="flex flex-col items-center justify-center min-h-[50vh] gap-4 p-8">
<h1 className="text-2xl font-bold text-red-600">Pago no completado</h1>
<p className="text-gray-600">
La operación no se ha completado correctamente.
</p>
<a href="/" className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
Volver a la tienda
</a>
</div>
)
}
if (orderId) {
try {
const order = await retrieveOrder(orderId)
if (order) {
const countryCode = order.shipping_address?.country_code?.toLowerCase() || "dk"
return redirect(`/${countryCode}/order/${orderId}/confirmed`)
}
} catch {
// Order not found, show success anyway
}
}
return (
<div className="flex flex-col items-center justify-center min-h-[50vh] gap-4 p-8">
<h1 className="text-2xl font-bold text-green-600">Pago procesado</h1>
<p className="text-gray-600">Tu pago ha sido procesado correctamente.</p>
<a href="/" className="mt-4 px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
Volver a la tienda
</a>
</div>
)
}

5. — 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:

// Redsys callback URL — bypass region redirect
if (request.nextUrl.pathname.startsWith("/checkout/redsys-callback")) {
return NextResponse.next()
}

6. — CORS

Ensure your storefront domain is allowed in CORS:

projectConfig: {
http: {
storeCors: "http://localhost:8000,https://your-store.com",
},
}

Session Data Reference

The payment session field returned by :

{
orderId: "1234ABCD5678",
amount: "2550",
currency: "978",
status: "pending",
transactionType: "0",
merchantParams: "base64...", // Base64-encoded merchant parameters
signature: "hmac...", // HMAC-SHA256 signature
signatureVersion: "HMAC_SHA256_V1",
formUrl: "https://sis-t.redsys.es:25443/sis/realizarPago"
}

These fields are used in step 3 to build the auto-submitting redirect form.

Webhook

Medusa automatically exposes a webhook endpoint for the Redsys provider 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.

Test Cards (Sandbox)

Card NumberBrandBehavior
4548810000000003VISA3DS v2 approved
5576441563045037Mastercard3DS v2 approved
4548814479727229VISA3DS frictionless
4548817212493017VISA3DS challenge
Any + CVV 999AnyPayment declined

Transaction Types

CodeTypeDescription
PaymentAuthorization + immediate capture (default)
Pre-authorizationReserve funds only
ConfirmationCapture pre-authorized funds
RefundFull or partial refund
CancellationCancel/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 for HMAC-SHA256 verification.
  • Use HTTPS for all communication with Redsys.
  • Do not trust client-side payment data. The webhook with signature verification is the source of truth.
  • The redirect flow keeps you out of PCI scope — card data is handled by Redsys' secure page.

Currency Support

The plugin includes built-in numeric currency codes for all major currencies. If your currency is not listed, it defaults to EUR (). See for the full list.

Development

# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Watch mode (for local plugin development)
npm run dev

Local Testing with a Medusa Project

# From your plugin directory
npm run dev
# In your Medusa project directory:
npx medusa plugin:add ../path-to/medusa-payment-redsys

License

MIT — see LICENSE file for details.

Support

For issues and questions, please open an issue on GitHub.

Еще в этой категории

Посмотреть все
Платежи
Braintree logo

Braintree

От Lambda Curry

Поддержка платежей и 3D Secure через Braintree

GitHubnpm
Платежи
Pay. logo

Pay.

От Webbers

Принимайте кредитные карты, цифровые платежи и купи сейчас — плати потом

GitHubnpm
Платежи
Mollie logo

Mollie

От Variable Vic

Легко принимайте мультивалютные платежи через Mollie

GitHubnpm

Еще от этого автора

Посмотреть все
Другое
P

Product description

От Juansoler

Medusa v2 plugin to generate AI-powered product descriptions via OpenRouter.

npm