El desarrollo de esta versión ha costado 2.800 euros. El coste acumulado para este año es de 13.350 euros. El coste acumulado desde la primera versión es de 212.080 euros, pero el coste para ti es solo la licencia de 79€.
Nueva versión 31.0.0 del plugin Redsys para WooCommerce de WooCommerce.com.
31.0.0
Nuevo:
- Nueva superficie del protocolo A2A (Agent2Agent). Opcional y desactivada por defecto. Publica una Agent Card en /.well-known/agent-card.json y un endpoint JSON-RPC 2.0 en /wp-json/wc-redsys-a2a/v1/rpc con siete skills: query_payment_status, create_payment, capture_payment, refund_payment, list_payments, tokenize_card y recurrent_charge. La autenticación reutiliza el servidor de autorización OAuth 2.1 + PKCE de UCP con scopes a2a:payments:* (y un bearer de sandbox opcional para pruebas bajo WP_DEBUG).
- Stream de Server-Sent Events reanudable en /wp-json/wc-redsys-a2a/v1/tasks/{taskId}/stream para actualizaciones de tareas en vivo, con reanudación mediante Last-Event-ID y latidos (heartbeats) cada 15s.
- Notificaciones push salientes firmadas a una push_url registrada por el cliente. Cabecera X-A2A-Signature: sha256=<hmac> sobre <timestamp>.<body>, con reintentos a 1m/5m/30m/2h (máximo 4), entregadas mediante Action Scheduler.
- Nueva pestaña de administración “A2A (Agent2Agent)” dentro de Redsys Avanzado -> Agentic Commerce, con Estado / Clientes (crear / revocar / rotar bearers de sandbox) / Límites y modos (max_amount, sensitive_threshold y modos de pago expuestos) / Confirmaciones (aprobar reembolsos o cargos recurrentes detenidos en input-required).
- Topes por skill: max_amount es un límite máximo estricto que falla la tarea con limit_exceeded; sensitive_threshold detiene la tarea en input-required hasta que un administrador la confirma. Ambos sin límite por defecto (opcional).
- Registro de auditoría de solo anexado (append-only) en wp_redsys_a2a_audit_log. Nunca se almacenan los payloads en bruto: solo un hash SHA-256 más un resumen saneado de una línea que enmascara PAN, tokens, secretos y la parte local de los emails.
- Almacenamiento auto-reparable. Las cuatro tablas A2A se recrean bajo demanda mediante el ensure_table() de cada store, de modo que una tabla eliminada nunca provoque un error fatal.
- Soporte para agentes de compra con IA (ChatGPT, Claude, Gemini, Perplexity y otros). Los agentes de IA ahora pueden descubrir tus productos, montar carritos, completar pagos y recibir actualizaciones de pedidos desde tu tienda. Se soportan tanto el estándar ACP de OpenAI/Stripe como el estándar abierto UCP (respaldado por Google, Shopify y otros), y cada uno puede activarse o desactivarse de forma independiente.
- Nueva subsección “Agentic Commerce” dentro de los ajustes de Redsys Avanzado, con interruptores por función (carritos, descuentos, fulfillment, consentimiento del comprador, registro dinámico de clientes, etc.) y botones de un clic para probar los flujos de descubrimiento de agentes, OAuth y webhook.
- Firma de webhooks con un botón “Rotar clave de firma”. Las claves rotadas siguen siendo válidas durante 7 días para que las integraciones existentes sigan funcionando durante el cambio.
- El modal de campos personalizados de Express (Apple Pay / Google Pay) ahora también recoge los campos registrados mediante la API de Blocks en checkouts clásicos, de modo que NIF/DNI y campos similares siempre aparecen independientemente del checkout que uses.
- Acción masiva opcional “Aprobar preautorización” para la lista de pedidos (Redsys Redirección). Desactivada por defecto por seguridad; actívala en los ajustes de la pasarela solo si necesitas confirmar pedidos preautorizados en masa.
Nota:
- Activar este soporte NO significa que los asistentes de IA vayan a empezar a completar compras en tu tienda desde el primer día. El flujo completo de checkout dirigido por agentes todavía se está desplegando, especialmente en Europa, donde los requisitos de SCA/3DS, PSD2 y consentimiento RGPD hacen que la mayoría de agentes hoy se detengan en el descubrimiento de productos o el pre-carrito y devuelvan el pago final al cliente. Tu tienda está lista para el día en que cada agente active el flujo completo en tu mercado.
Actualizado:
- El modal de campos personalizados de Express ahora precarga su configuración al cargar la página e inicia Apple Pay de forma síncrona al hacer clic, corrigiendo los casos en que iOS Safari abortaba la hoja de pago.
- Los campos personalizados de Express en la página de producto vuelven a usar el modal por defecto. El formulario en línea introducido en 30.4.1 ahora es opcional mediante un filtro.
Arreglado:
- [Crítico] Las renovaciones de suscripciones podían fallar con el error Redsys SIS0502 (“Id Oper no coincide”) en hostings con Redis, Memcached u otras cachés de objetos persistentes. Dos workers de renovación en paralelo podían enviar dos referencias de pedido distintas a Redsys para la misma suscripción, provocando que la renovación fallara y se reintentara muchas veces. El bloqueo de pago duplicado y la referencia de pedido ahora se almacenan de forma fiable independientemente del backend de caché. Afecta a las pasarelas de Redirección e InSite de Redsys.
- Las renovaciones de suscripción de InSite no tenían ninguna protección de concurrencia (el bloqueo se eliminaba en los errores pero nunca se creaba). Ahora se aplica a las renovaciones de InSite la misma protección que usa la pasarela de redirección.
- El estado interno cacheado usado durante el checkout (datos 3DS, identificador de tarjeta guardada, caché de firma, estado de OAuth, estado temporal de IMAP, etc.) ahora se almacena de forma que funciona correctamente en hostings con cachés de objetos persistentes. Antes, en cachés que se comportaban mal, este estado podía desaparecer silenciosamente a mitad del checkout y provocar errores SIS0502.
- Los botones Express de Apple Pay y Google Pay (checkout de bloques y página de producto) fallaban en iOS Safari con “Must create a new ApplePaySession from a user gesture handler” cuando el modal de campos personalizados de Express estaba activado. El modal ya no consume el clic del usuario antes de lanzar Apple Pay.
- El IRPF de Autónomos Premium se calculaba mal en el Pago Express (Apple Pay / Google Pay) en la página de producto cuando el cliente elegía el tipo de usuario en el modal de Express, porque el guardado del modal competía con la primera llamada del servidor de Apple Pay. El tipo de usuario ahora se envía junto con cada petición de Express Pay, de modo que los totales son siempre correctos.
- En páginas de producto con productos virtuales, la hoja de Apple Pay mostraba brevemente el total correcto (subtotal + impuestos) y luego revertía al precio del producto sin impuestos. El callback de envío ahora mantiene el último total devuelto por el servidor.
- Al guardar los ajustes de Agentic Commerce se descartaba silenciosamente el envío “Guardar cambios” de WooCommerce por culpa de formularios HTML anidados. La página de ajustes se ha reestructurado y todos los botones de acción única (probar descubrimiento, probar OAuth, acciones de webhook, etc.) ahora funcionan correctamente junto al botón principal de Guardar.
- Los botones Express de Apple Pay y Google Pay en la página de producto asignaban la zona de envío incorrecta cuando la región del cliente estaba restringida por provincia. Apple/Google Pay envían el nombre localizado del estado (p. ej. “Barcelona”) en administrativeArea, pero las zonas de envío de WooCommerce coinciden por código de estado (p. ej. “B”); la zona país-por-estado no coincidía y el carrito caía en una zona más cara (p. ej. “Resto de Europa”). Los nombres de estado ahora se normalizan a códigos de estado de WooCommerce antes de la búsqueda de zona de envío y la creación del pedido.
- El checkout de InSite con Bloques podía crear pedidos duplicados cuando Redsys emitía el mismo postMessage de token de pago más de una vez (o cuando el guardado se ejecutaba de forma concurrente). Ahora una protección evita la creación de pedidos duplicados/concurrentes y se reinicia si el formulario de pago se refresca tras un error.
- El registro de depuración REST de InSite hacía referencia al objeto equivocado para leer el flag de depuración, por lo que el payload “REST trataPeticion” podía registrarse (o saltarse) independientemente del ajuste de depuración de la pasarela. Ahora usa el propio flag de depuración de la pasarela.
31.0.1
Actualizado:
- La cuenta atrás del modal de Bizum en la página de pago mostraba 7 minutos. Ahora muestra correctamente 5 minutos, y el control de tiempo del lado del servidor que redirige al checkout se ha ajustado en consecuencia (60 iteraciones * 5 segundos = 5 minutos).
Arreglado:
- Bizum en la página de pago (Bizum InSite) no enviaba la descripción a Redsys. El cargo real desde el modal se realiza a través de la API REST (trataPeticionREST), y esa petición no incluía el parámetro DS_MERCHANT_PRODUCTDESCRIPTION, por lo que la “Descripción de Redsys” configurada (por ejemplo “Order ID”) llegaba vacía al panel de Redsys. La descripción ahora se calcula a partir del ajuste seleccionado y se envía en la petición REST.
31.0.2
Seguridad:
- La clave secreta SHA-256 del comercio ya no se escribe en texto plano en los registros de depuración de WooCommerce. Todas las entradas de depuración ahora enmascaran la clave, mostrando solo sus últimos 4 caracteres, mediante el nuevo helper WCRed()->mask_secret(). Aplicado en todas las pasarelas (Redirección, InSite, Bizum, Google Pay, Apple Pay, PayGold, Domiciliación Bancaria y las clases de soporte de Blocks).
- Las firmas de las notificaciones (IPN) de Redsys ahora se verifican con hash_equals() (comparación en tiempo constante) en todos los manejadores de notificaciones, unificándolos con la verificación que ya usaban InSite y el cliente REST. Endurecimiento frente a ataques de temporización (timing attacks).
- El manejador AJAX de borrado de un único token en la pantalla de gestión de tokens ahora requiere la capacidad manage_woocommerce además del nonce, igualándolo al manejador de borrado masivo.
Actualizado:
- El filtro redsys_modify_data_to_send ahora recibe ‘context’ => ‘add_payment_method’ y ‘user_id’ en el array de datos para el flujo de “Añadir método de pago”, de modo que los snippets personalizados y las reglas condicionales puedan enrutar la tokenización al terminal correcto (terminal + SHA256) sin depender de un pedido que no existe en este flujo.
- Actualizadas todas las traducciones.
Arreglado:
- Añadir una tarjeta desde Mi Cuenta (“Añadir método de pago”) en tiendas multidivisa podía ser rechazado por Redsys con un error de moneda. La petición se enviaba con la moneda de la sesión del cliente (por ejemplo ARS, 32) pero con el terminal por defecto, que está configurado para otra moneda (por ejemplo EUR, 978). La tokenización de tarjeta es una operación de importe cero sin un pedido detrás, así que si ningún filtro o regla condicional enruta la petición a otro terminal, ahora se envía la moneda base de la tienda, coincidiendo siempre con la configuración del terminal por defecto.
- Añadir una tarjeta desde Mi Cuenta (o mediante el enlace de email del perfil) registraba siempre la tarjeta en Redsys como one-click (DS_MERCHANT_COF_TYPE ‘C’), incluso cuando el cliente seleccionaba la opción de suscripciones. El tipo de token se leía de la clave interna equivocada, por lo que nunca se enviaba ‘R’. El token se guardaba correctamente como R en WooCommerce, pero el acuerdo COF se creaba como C en Redsys, lo que podía provocar que algunos emisores rechazaran posteriores renovaciones de suscripción (MIT) hechas con esos tokens. Las tarjetas añadidas con la opción de suscripciones ahora envían correctamente DS_MERCHANT_COF_TYPE ‘R’. Los tokens creados mientras el error estaba presente no se pueden reclasificar; si una renovación es rechazada, pide al cliente que vuelva a añadir la tarjeta.
- Los pagos realizados con una tarjeta guardada (token R) y las renovaciones de suscripción no respetaban los ajustes de preautorización. Con “Preautorizar todos los pedidos” activado (o un producto marcado para preautorización), el cargo se ejecutaba como una venta normal (tipo 0) pero el pedido se marcaba igualmente como “Preautorizado”, de modo que confirmar la preautorización más tarde fallaba en Redsys con SIS0059 (“No existe operación sobre la que realizar la confirmación”). Ambos flujos ahora envían una preautorización real de tipo 1 que puede confirmarse después, y un pedido solo se marca como “Preautorizado” cuando se ha ejecutado una preautorización real. Esto también habilita las renovaciones de suscripción preautorizadas (por ejemplo bienes vendidos al peso, donde cada renovación se ajusta antes del cargo final).
31.0.3
Nuevo:
- Sección “Apps y Plugins” dentro de Redsys Avanzado -> Ajustes Avanzados de Redsys. Una página de aterrizaje de solo lectura que muestra la app nativa de gestión para macOS (con descarga, requisitos y plataformas “próximamente”) y lista el resto de plugins gratuitos y premium, webs y portales, skills de Claude y perfiles de desarrollador de José Conti, cada uno agrupado por tipo con un enlace. Cada lista es filtrable (redsys_apps_plugins_mac_app, redsys_apps_plugins_free, redsys_apps_plugins_premium, redsys_apps_plugins_webs, redsys_apps_plugins_skills, redsys_apps_plugins_profiles).
Seguridad:
- Eliminado un bypass de verificación de firma de notificación que podía permitir que una petición anónima marcara un pedido como pagado cuando el secreto SHA-256 se dejaba vacío o mal configurado. El listener de IPN de Redsys (endpoint público wc-api) tenía un fallback heredado que, cuando no había secreto configurado, aceptaba una notificación únicamente porque el Ds_MerchantCode enviado coincidía con el FUC del comercio, un valor público y no secreto que un atacante puede suministrar. Esa aceptación basada solo en el código de comercio se ha eliminado de todas las pasarelas (Redirección, Bizum, Bizum InSite, Google Pay checkout y redirección, Apple Pay, PayGold, Domiciliación Bancaria, MasterPass y Transferencia Bancaria): cuando no hay secreto para verificar el HMAC, la petición ahora falla de forma segura (rechazo HTTP) en lugar de ser confiada. Además, la rutina de verificación compartida (WooRedsysAPI::verify_signature_notif) ahora devuelve false siempre que la clave del comercio o la firma recibida estén vacías, de modo que ni check_ipn_request_is_valid() ni successful_request() puedan completar un pago con un HMAC de clave vacía calculable por el atacante. InSite ya requería una firma válida (sin fallback por código de comercio) e Inespay no se ve afectado (relaciona los pedidos por su propio id de payin). Las tiendas reales no se ven afectadas, porque tener un secreto configurado es obligatorio para poder cobrar.
Arreglado:
- Las notificaciones de Bizum se rechazaban con un error de verificación de firma, dejando el pedido sin pagar aunque el pago se hubiera autorizado en Redsys. La clave del comercio y el número de pedido eran correctos (la firma de la petición coincidía perfectamente), pero la firma de la notificación es un HMAC calculado sobre la cadena Ds_MerchantParameters exactamente como se recibe, y las notificaciones de Bizum llegan con el relleno (padding) final “=” de base64 eliminado, mientras que Redsys firma sobre el valor con el padding canónico, así que el HMAC calculado localmente nunca coincidía. La rutina de notificación compartida (create_merchant_signature_notif en WooRedsysAPI) ahora vuelve a rellenar el payload a un múltiplo de cuatro antes de hashear, y la verificación (verify_signature_notif) ahora compara ambas firmas ignorando el padding final “=” (que no aporta entropía, así que la comparación sigue siendo en tiempo constante). Los payloads de tarjeta ya son canónicos y se dejan intactos, de modo que el resto de pasarelas siguen funcionando sin cambios. Esta rutina la comparten todos los manejadores de notificación (Redirección, Bizum, InSite, PayGold, Google Pay, Apple Pay, Domiciliación Bancaria, Transferencia Bancaria y los clientes REST/A2A).
- Las notificaciones de tarjeta y wallet podían rechazarse con un “signature mismatch”, de modo que el cliente que volvía a la página de gracias (y el IPN servidor a servidor) no conseguía marcar el pedido como pagado hasta que un reintento posterior de IPN tenía éxito por casualidad; los reembolsos y los enlaces de PayGold pagados más tarde podían quedar sin confirmar. Hay dos problemas de padding implicados: además del padding del payload de Bizum anterior, Redsys entrega la firma de la notificación (Ds_Signature) en base64 URL-safe con el “=” final eliminado, mientras que la firma calculada localmente lo mantiene, así que una comparación estricta también falla para los pagos de tarjeta normales. La comparación tolerante al padding vive en verify_signature_notif(), pero la mayoría de los manejadores seguían comparando la firma en línea con un hash_equals()/!== estricto que reintroducía la sensibilidad. Ahora todos los manejadores delegan en verify_signature_notif(), de modo que el arreglo llega a todos: Redirección de tarjeta (ambos validadores de IPN y las comprobaciones de respuesta REST-SOAP de captura/reembolso), Bizum, Bizum InSite, InSite (IPN, successful_request y los dos validadores de respuesta REST), PayGold, Google Pay (checkout y redirección), Apple Pay, Domiciliación Bancaria, Transferencia Bancaria, MasterPass y el cliente REST de InSite. Regresión introducida cuando se unificaron las comprobaciones de firma por pasarela en verify_signature_notif() con una comparación estricta y sensible al padding.
- Las notificaciones de Bizum en tiendas multidivisa / de doble terminal (las que enrutan cada pedido a un terminal distinto mediante el filtro bizum_modify_data_to_send o una regla condicional) ahora resuelven la clave de firma real por pedido antes de verificar la firma (la clave de los ajustes ajustada por el cliente, el transient guardado cuando se generó el formulario de pago, o el meta de pedido _redsys_secretsha256), y una notificación no verificable se rechaza con HTTP 400.
- Los pagos con tarjeta de InSite que requerían un challenge 3DS mostraban una pantalla de verificación en blanco en Safari (iPhone/Mac), de modo que el cliente no podía introducir el código SMS/PIN y el pago fallaba (el mismo flujo funcionaba en Chrome). El challenge en sí ya era una navegación de página completa y de nivel superior, pero antes de él el plugin redirigía el navegador a la URL del 3DS Method del emisor (threeDSMethodURL) para tomar la huella del navegador, y la Prevención de Rastreo Inteligente (ITP) de Safari bloquea las cookies de terceros que ese paso necesita, dejando una página ACS en blanco (síntoma: la URL del ACS llega con “;jsessionidpa=” porque las cookies no fluyen). El paso del 3DS Method en el navegador es opcional, así que el plugin ya no redirige el navegador a threeDSMethodURL: siempre envía threeDSCompInd = ‘N’ y continúa directamente a la autenticación. Aplicado a todos los flujos de InSite -tarjeta nueva y tarjeta guardada (one-click/token), tanto en checkout de bloques como clásico (shortcode)- y a los flujos REST/token de la pasarela de Redirección (pay_with_token_c, receipt_page y successful_request), ya que la misma pantalla en blanco podía aparecer al cobrar un token de tarjeta guardada.
- Cuando un pago con tarjeta de InSite fallaba porque faltaba un campo obligatorio del checkout o sus datos no coincidían (por ejemplo, un apellido vacío producía un “signature mismatch”), el mensaje de error culpaba a la tarjeta (“…vuelve a introducir los datos de tu tarjeta”), así que los clientes pensaban que su tarjeta había sido rechazada y abandonaban el pedido. El mensaje ahora es neutro y apunta primero a los campos del checkout: pide al cliente que compruebe que todos los campos del checkout (nombre, apellidos, dirección, etc.) y los datos de la tarjeta están rellenados correctamente antes de volver a intentarlo. Aplicado a los checkouts clásico y de bloques (Blocks).
- Los reembolsos y pagos diferidos de pedidos con IDs de pedido grandes (el “bug de los mil millones” ya corregido para el checkout instantáneo) se registraban contra el pedido equivocado, o no se registraban en absoluto. La notificación (IPN) de Redsys recupera el ID de pedido de WooCommerce a partir del número de pedido mediante clean_order_number(), que primero busca un transient guardado cuando se generó el número de pedido y, en su defecto, recurría a una heurística substr/ltrim que descarta los dígitos de orden alto de los IDs con 10 o más dígitos. Ese transient tiene un TTL de 1h, así que el fallback se activaba siempre que la notificación llega más de una hora después de generarse el número de pedido: cada reembolso (procesado días o semanas después) y los enlaces de pago de PayGold (pagados por el cliente más tarde), entre otros. Tres cambios lo hacen robusto para todos los métodos de pago: (1) la rutina de reembolso compartida (ask_for_refund) ahora vuelve a guardar el mapeo número de pedido -> ID real de pedido en el momento del reembolso con un TTL de 24h; (2) clean_order_number(), cuando el transient ya no existe, ahora busca el pedido de forma inversa por el meta persistido del número de pedido (_payment_order_number_redsys / _redsys_transaction_id2) antes de usar nunca la heurística con pérdida; y (3) PayGold ahora persiste el número de pedido en el meta cuando se crea el enlace, de modo que un enlace pagado mucho después sigue resolviéndose al pedido exacto. Se aplica a todas las pasarelas (Redirección, InSite, Bizum, Bizum InSite, Google Pay, Apple Pay, PayGold y Domiciliación Bancaria). Inespay no se ve afectado (relaciona los pedidos por su propio id de payin).
- Los pagos con tarjeta de InSite en el checkout de bloques (Blocks) fallaban la PRIMERA vez con “msg18” (el cliente veía “comprueba que los campos del checkout/tarjeta están rellenados”) y solo funcionaban en el segundo intento. El SDK de InSite de Redsys requiere que la página envíe un mensaje “domain” al iframe de la tarjeta para que Redsys pueda validar el comercio; el SDK lo hace mediante un onload=”setMerchantDomain(0)” en línea en el iframe. El script de bloques sobrescribía ese manejador con su propio iframe.onload (usado para dimensionar el iframe) justo después del primer renderizado, así que el dominio del comercio nunca se enviaba y Redsys rechazaba la tokenización con msg18 (“validación incorrecta por parte del comercio”). En el refresco automático del formulario la sobrescritura no se ejecutaba, así que el segundo intento funcionaba. El plugin ahora llama a setMerchantDomain explícitamente después de construir el formulario (como hace la propia integración del botón de pago del SDK) y ya no sobrescribe el onload del iframe, de modo que el primer intento funciona.
- InSite checkout de bloques: se ha hecho más robusto el manejo del postMessage de Redsys y se han añadido diagnósticos de extremo a extremo. El manejador ahora acepta mensajes de cualquier subdominio de Redsys (sis.redsys.es, sis-t.redsys.es, sis-d.redsys.es) en lugar de exigir una coincidencia exacta de host/puerto (Redsys publica desde más de un host), elimina cualquier listener dejado por un renderizado anterior para que un único token no pueda disparar múltiples envíos, y limpia los campos de token/error tras leerlos para que un re-disparo obsoleto no pueda reenviarlos. Cuando el registro de depuración de InSite está activado, todo el paso de tokenización del lado del cliente (número de pedido, postMessage de Redsys, token/código de error, guardado y place-order) se refleja en el log “insite” de WooCommerce, de modo que los fallos del primer intento que nunca llegan a PHP puedan diagnosticarse.
- Los pagos con tarjeta de InSite en el checkout de bloques (Blocks) eran rechazados por Redsys con SIS0574 (“operación de autenticación EMV3DS rechazada, browserUserAgent no indicado”): la huella 3DS del navegador (user agent, tamaño de pantalla, idioma, profundidad de color, zona horaria) nunca llegaba al pedido, porque el hook que normalmente la copia al pedido (woocommerce_checkout_create_order) no se ejecuta en el checkout de la Store API (Blocks). El formulario de InSite de Blocks ahora recoge la huella y la envía junto con el token de operación, y se escribe en el pedido real (junto al token) antes de que se ejecute process_payment, de modo que la autenticación EMV3DS tiene los datos que necesita.
- Los pagos con tarjeta de InSite en el checkout de bloques (Blocks) eran rechazados con un “código de error sin token” (mostrado al cliente como “comprueba que los campos del checkout/tarjeta están rellenados”), de modo que la tarjeta ni siquiera podía tokenizarse tras unos pocos intentos. Como el pedido todavía no existe en el checkout de Blocks (id de pedido 0), el número de pedido de Redsys preparado se generaba a partir del id 0, lo que producía un valor casi constante -solo ~999 números posibles, todos terminados en nueve ceros- y Redsys rechaza un número de pedido reutilizado (“pedido repetido”, SIS0051). Cuando el id del pedido borrador todavía no está disponible, el formulario de InSite de Blocks ahora usa el mismo número de pedido temporal que el checkout shortcode de InSite (create_checkout_insite_number: siempre empieza por 1, globalmente único mediante un contador incremental compartido), así que nunca colisiona; cuando el id del pedido borrador está disponible, el número de Redsys se construye a partir de él. El número se vuelve a vincular al id real del pedido cuando el pedido se realiza.
- Los pagos con tarjeta de InSite en el checkout de bloques (Blocks) fallaban: el cliente veía un mensaje engañoso “comprueba que los campos del checkout/tarjeta están rellenados” y el pedido nunca se pagaba (la consola del navegador mostraba un 404 en la petición save_order_data). La causa es que el WooCommerce actual ya no crea el pedido hasta que el cliente pulsa “Realizar el pedido” -durante la introducción de la tarjeta el store del checkout de Blocks devuelve id de pedido 0- así que el token de operación de InSite (idOper) y el número de pedido de Redsys preparado no podían persistirse: el flujo anterior los enviaba a una ruta REST (save_order_data) que intentaba adjuntarlos al pedido 0 y devolvía “Invalid order” (404), y el número de pedido generado se mapeaba al pedido 0 (de modo que ni siquiera una notificación posterior podía encontrar el pedido). Ahora, cuando la tarjeta se tokeniza, el token y el número de pedido preparado se guardan en la sesión de WooCommerce mediante admin-ajax (la sesión de WC no está disponible en rutas REST personalizadas, razón por la cual el enfoque REST anterior no podía usarla), y se trasladan al pedido real mientras WooCommerce lo crea a partir de la petición del checkout (woocommerce_store_api_checkout_update_order_from_request), antes de que se ejecute process_payment, de modo que process_payment_block() encuentra _insite_token y _payment_order_number_redsys como en el checkout clásico. El transient del número de pedido también se vuelve a mapear al id real del pedido para que la notificación (IPN) resuelva el pedido. Los datos de huella del navegador (3DS) ya usaban esta misma ruta de sesión. El checkout clásico/shortcode no se ve afectado.
Desarrollo:
- Eliminada la ruidosa salida de depuración console.log de los scripts de frontend de producción (minificados): Apple Pay, Google Pay, capture-order-id y los modales de express-checkout. El script de Apple Pay Express en particular registraba en cada cambio del DOM (vigila todo el documento en busca de su botón), lo que inundaba la consola del navegador en el checkout de Blocks. Los archivos minificados ahora eliminan console.log/console.warn/console.info/console.debug manteniendo console.error; las fuentes sin minificar quedan sin cambios para desarrollo.
31.0.4
Arreglado:
- Las renovaciones de suscripción (con cualquier plugin de suscripciones) ahora respetan el tipo de número de pedido de Redsys configurado y ya no fallan en el reintento con “número de pedido repetido” (SIS0051). Se corrigen dos problemas.
- El primero: el cargo de renovación generaba el número de pedido de Redsys ignorando el ajuste “Tipo de número de pedido” (redsysordertype/subfix); siempre recurría al esquema aleatorio por defecto en lugar del tipo configurado para la pasarela, a diferencia del pago inicial del checkout. Ahora se pasa la pasarela para que las renovaciones respeten el mismo tipo de número de pedido que el checkout.
- El segundo: el número de pedido se conservaba durante toda la vida del pedido, de modo que cuando un cargo de renovación era rechazado y el plugin de suscripciones lo reintentaba sobre el mismo pedido de renovación, se reenviaba exactamente el mismo DS_MERCHANT_ORDER y Redsys bloqueaba el reintento legítimo como duplicado. Ahora la distinción entre “flujo” y “reintento” es explícita: un único flujo de pago (su par iniciaPeticion/trataPeticion, los procesos concurrentes de renovación y cualquier reejecución mediante Action Scheduler de un proceso que murió a mitad del cargo) mantiene el mismo número de pedido para no cruzar Id Opers (SIS0502) y ser idempotente frente a cargos duplicados; pero una vez que Redsys confirma un rechazo (una respuesta firmada sin código de autorización, es decir, sin movimiento de dinero), el número se libera para que el siguiente reintento genere uno nuevo y único respetando el tipo configurado. El reinicio se produce SOLO ante un rechazo confirmado, nunca ante errores de transporte o de tiempo de espera donde el resultado es desconocido, de modo que una reejecución por respuesta perdida nunca puede duplicar el cargo.
- Se aplica a las pasarelas de Redirección e InSite y a todos los plugins de suscripciones que las utilizan (WooCommerce Subscriptions, Subscriptions for WooCommerce, YITH, WebToffee, SUMO, Advanced Subscriptions; Google Pay y Apple Pay delegan en la pasarela de Redirección).
- Como red de seguridad, cuando no hay configurado un tipo de número de pedido válido (vacío o un valor heredado o no reconocido), el generador recurre SIEMPRE al esquema por defecto “3 dígitos aleatorios + ceros + id del pedido”, el único que nunca colisiona en los reintentos. Nota: el tipo “número de pedido simple” no tiene componente aleatorio, por lo que es incompatible por diseño con los reintentos de suscripciones y no debería usarse cuando hay suscripciones activas.
31.0.5
Arreglado:
- Las renovaciones de suscripción todavía podían quedar atrapadas en un bucle infinito de SIS0051 (“número de pedido repetido”) tras la 31.0.4. El arreglo de la 31.0.4 solo liberaba el número de pedido almacenado cuando el rechazo llegaba como una Ds_Response firmada sin código de autorización, pero Redsys informa de un número de pedido duplicado como un errorCode simple (SIS0051) en la respuesta de trataPeticion, que el plugin gestiona por la vía de error de la petición; así que el número almacenado nunca se liberaba y cada reintento reenviaba exactamente el mismo DS_MERCHANT_ORDER (ya quemado en Redsys por los intentos previos a la actualización), fallando de nuevo con SIS0051 para siempre. Los cargos de renovación ahora SIEMPRE generan un número de pedido nuevo al inicio de cada intento y nunca leen uno almacenado previamente: el par iniciaPeticion/trataPeticion del intento comparte ese número recién generado (por lo que no pueden descuadrarse, SIS0502), los procesos concurrentes duplicados se detienen mediante el bloqueo de renovación antes de generar el número, y el último número enviado se sigue guardando en el meta del pedido para que los reembolsos y la notificación de pago (IPN) resuelvan el pedido correcto. Las tiendas atrapadas en el bucle SIS0051 se recuperan solas en el siguiente reintento programado tras actualizar, sin necesidad de ninguna acción manual. Se aplica a las pasarelas de Redirección e InSite y a todos los plugins de suscripciones que las utilizan (Google Pay y Apple Pay delegan en la de Redirección).
31.0.6
Arreglado:
- El despachador de webhooks de ACP ya no inunda el log con avisos “as_next_scheduled_action() / as_schedule_recurring_action() was called before the Action Scheduler data store was initialized”. Redsys_ACP_Webhook_Dispatcher::init() se ejecuta en plugins_loaded y programaba el worker de entrega de inmediato: en ese momento WooCommerce ya ha definido las funciones as_*, por lo que la comprobación function_exists() pasaba, pero Action Scheduler no inicializa su almacén de datos hasta el hook action_scheduler_init (durante init), de modo que cada llamada emitía un aviso — en peticiones públicas, /wp-admin/, admin-ajax.php y REST, incluso con ACP activado pero sin uso (tablas vacías). Ahora la programación se difiere a action_scheduler_init siempre que el almacén no esté listo todavía (comprobado mediante did_action()/doing_action()); si init ya se disparó en esa petición, programa de inmediato como antes, y cuando Action Scheduler no está presente sigue usando el respaldo de WP-Cron sin cambios. Se aplica tanto a schedule_worker_baseline() como a schedule_worker_now(). No hace falta ningún cambio de configuración; desactivar redsys_acp_enabled solo era un apaño porque impedía que el despachador se inicializara.
- Los pedidos de Apple Pay Express se guardaban sin ningún dato del cliente — sin nombre, apellidos, email, teléfono ni dirección de facturación o envío — y se registraban como compras de invitado, aunque el pago en sí se completaba correctamente y el pedido quedaba marcado como pagado. Google Pay Express nunca se vio afectado. El flujo express registraba el pedido que acababa de crear en la sesión de WooCommerce bajo ‘store_api_draft_order’, lo que le dice a WooCommerce Blocks que el pedido es un borrador desechable de su propiedad. Cuando el navegador cargaba después la página de pago (que renderiza el bloque de Checkout), la hidratación del bloque durante wp_footer llamaba a la ruta de checkout de la Store API y OrderController::update_order_from_cart() resincronizaba ese pedido a partir del carrito y de la sesión del cliente: borraba los datos de contacto de Apple Pay y ponía a 0 el id de cliente, un par de segundos antes de que llegara la notificación del banco — de ahí un pedido pagado pero anónimo que conservaba solo los cuatro campos necesarios para calcular el envío (ciudad, código postal, provincia y país). El flujo express ya no publica su pedido como el borrador de la Store API: lo transporta entre sus dos pasos AJAX mediante su propia clave de sesión, validada por hash del carrito y firma, exactamente como antes. Además, cuando el pago comienza el pedido se libera de la sesión de borrador de la Store API si estaba registrado ahí (que es el caso cuando se pulsa el botón express en el bloque de Checkout, donde el borrador lo crea el propio WooCommerce), de modo que una hidratación posterior del bloque de Checkout nunca pueda resincronizar un pedido que ya se está pagando. Se aplica al botón express de Apple Pay tanto en el bloque de Carrito como en el de Checkout; Google Pay nunca escribió esa clave de sesión y no necesitó cambios.
31.0.7
Arreglado:
- Los productos de suscripción de Advanced Subscriptions for WooCommerce nunca se detectaban como suscripciones. La comprobación se protegía con function_exists( ‘aswc_check_product_is_subscriptionn’ ) — una letra n de más — mientras que el cuerpo llamaba a la función correctamente escrita, por lo que check_subscription_aswc_checkout() siempre devolvía false independientemente del producto. La errata aparecía exactamente una vez en todo el plugin y databa del renombrado del prefijo sjcfwc_ a aswc_. Como consecuencia, la tarjeta usada para esos productos podía tokenizarse como un pago único en lugar de como uno recurrente, que es lo que necesita una suscripción.
- Cualquier visitante anónimo podía provocar un error fatal a través de admin-ajax.php. El plugin registraba la acción AJAX check_token_insite_from_action, para visitantes con y sin sesión, apuntando a un método de WC_Gateway_InSite_Redsys que no existe — la implementación se llama check_token_insite_from_action_checkout, que es también el nombre de acción que el JavaScript realmente envía y que estaba registrado correctamente en las dos líneas siguientes. Solicitar admin-ajax.php?action=check_token_insite_from_action producía un fatal de “call to undefined method”. Se han eliminado los dos registros obsoletos; nada los referenciaba.
- La página de administración de Tokens de Redsys registraba un manejador de formulario que nunca se escribió. admin_post_redsys_tokens_search apuntaba a un método process_search_tokens() que no existe, por lo que visitar admin-post.php?action=redsys_tokens_search producía un fatal para cualquier usuario con sesión. La búsqueda en esa página siempre ha funcionado a través de la propia página, no de ese manejador, así que se ha eliminado el registro obsoleto y nada cambia para el usuario.
- Doce textos visibles para el usuario decían “Redys” en lugar de “Redsys” — nueve títulos de email mostrados en WooCommerce, Ajustes, Emails, y tres en la descripción de la pasarela de Transferencia Bancaria. Las traducciones correspondientes en catalán, euskera, gallego, español, francés y portugués se han actualizado en el mismo lanzamiento, así que no se pierde ningún texto traducido.
- Una notificación bancaria malformada o manipulada podía llenar el log con avisos “Trying to access array offset on null”. Cuando la notificación no podía decodificarse, la rutina que lee el número de pedido de ella accedía a los datos decodificados de todos modos, y su respaldo leía un índice no definido siempre que no estuviera presente ninguna de las dos grafías de la clave del pedido. Ahora devuelve un número de pedido vacío, que los llamadores ya tratan como una notificación irresoluble.
- Con el formato de número de pedido “Número creado por WooCommerce solo con ceros” (simpleorder), añadir un método de pago podía ser rechazado por Redsys como número de pedido repetido (SIS0051). Los pedidos reales y las adiciones de tarjeta se numeraban desde el mismo espacio de 12 dígitos rellenado con ceros: un pedido con id 42 se enviaba como 000000000042, y también lo hacía la adición de tarjeta número 42, porque el contador de alta de tarjeta empezaba en 1 y recorría directamente el rango de los ids de pedido existentes. La misma colisión tenía una segunda consecuencia, más dañina, que podía llegar a un pedido pagado: el plugin decide si una notificación bancaria pertenece a una adición de tarjeta leyendo un transient que vive 48 horas y se indexa solo por el número, así que un pedido real pagado con un número que una adición de tarjeta había reservado en los dos días anteriores se procesaba como una adición de tarjeta y el pago se devolvía sin llegar a marcar el pedido como pagado — se cobraba el dinero y el pedido quedaba pendiente. Los números de adición de tarjeta usan ahora el prefijo reservado 200 seguido de un contador de 9 dígitos (por ejemplo 200000000042), y al generador de números de pedido ya no se le permite producir ese prefijo: bajo el formato por defecto un número de pedido es un número aleatorio de 1 a 999 seguido de los últimos 9 dígitos del id del pedido, así que los dos valores que caerían dentro de un espacio reservado — 100 y 200 — simplemente se vuelven a sortear. Si una notificación pertenece a una adición de tarjeta se decide ahora a partir del propio número más una comprobación de que ningún pedido real lo lleva, manteniendo el transient como respaldo para que las operaciones ya en curso al actualizar el plugin sigan funcionando; nada se migra y no se pierde ninguna adición de tarjeta pendiente. El formato “solo con ceros” sigue funcionando y sigue disponible: su limitación restante e inherente — pagar el mismo pedido dos veces reutiliza el número y Redsys responde SIS0051 — se indica ahora en la propia descripción del ajuste en lugar de en una advertencia vaga.
- El número de tokenización de InSite podía colisionar con el formato de número de pedido por defecto. El número que InSite usa para solicitar el token de la tarjeta antes de que exista el pedido empieza por 1, mientras que el formato por defecto (“3 números aleatorios seguidos de ceros”) construye un número de pedido a partir de un prefijo aleatorio de 1 a 999: siempre que ese prefijo aleatorio salía exactamente 100 y el contador de InSite coincidía con el id del pedido, ambos producían el mismo valor (por ejemplo 100000000042 para el pedido 42). A diferencia del punto anterior, este no necesitaba ningún ajuste concreto — afectaba a la configuración por defecto. El número de InSite conserva el prefijo 100 que siempre ha tenido, y el generador de números de pedido ahora vuelve a sortear siempre que fuera a producirlo.
- Dos adiciones de tarjeta iniciadas en el mismo instante podían recibir el mismo número y guardar la tarjeta en la cuenta equivocada. Ambos contadores internos se leían y se volvían a escribir como dos operaciones separadas, así que dos peticiones simultáneas leían el mismo valor, lo incrementaban hasta el mismo número, y la segunda petición sobrescribía el id de usuario guardado por la primera — el número es la única clave que tienen esas operaciones, así que la tarjeta acababa asociada a quien escribiera el último. Los contadores ahora reclaman cada valor de forma atómica, usando el mismo mecanismo ya empleado para los bloqueos de renovación de suscripciones, y una reclamación que se pierde simplemente pasa al siguiente valor. Nada da la vuelta y ningún número se reutiliza jamás: si un contador llegara a agotarse, o si se perdieran veinte intentos de reserva consecutivos, la operación se rechaza con un mensaje en lugar de enviarse a Redsys con un número repetido.
- Las operaciones de adición de tarjeta y de tokenización InSite no podían rastrearse desde los logs. El número reservado para una adición de tarjeta nunca se escribía en el log junto con el cliente al que pertenecía — el único vínculo era un transient que caduca en 48 horas — así que un número visto después en el panel de Redsys o en un log no podía asociarse a nadie. Peor aún, el flujo de adición de tarjeta registraba su número bajo una variable llamada $order_id, de modo que el log afirmaba que una operación era un pedido determinado cuando no existía ningún pedido en absoluto. Ambas operaciones escriben ahora una línea de identidad en el momento en que se reserva el número, indicando el número, el id de usuario, el login y el email, el tipo de operación, y declarando explícitamente que no es un número de pedido.
- Un email de Redsys que llegara después de que una adición de tarjeta hubiera caducado podía modificar un pedido no relacionado. Cuando el respaldo de email por IMAP procesa una notificación cuyo transient de adición de tarjeta de 48 horas ya ha desaparecido, resolvía el número a un id de pedido con una heurística heredada que elimina los tres primeros caracteres — lo que, para un número de adición de tarjeta, devolvía el id de un pedido real y escribía los datos de la transacción sobre él. Los números con prefijos reservados se reconocen ahora como tales y nunca se resuelven a un pedido. La comprobación se ejecuta después de las búsquedas normales de pedido en lugar de antes, de modo que el pequeño número de pedidos pagados en versiones anteriores cuyo prefijo aleatorio resultó ser exactamente 100 o 200 siguen resolviéndose a su propio pedido como siempre lo hicieron.
- Apple Pay elegido como método de pago normal en el checkout basado en bloques (Blocks) — no el botón express — producía un pedido pagado sin ningún dato del cliente: sin nombre, apellidos, email, teléfono ni país, y con DS_MERCHANT_TITULAR enviado en blanco a Redsys. El pago se autorizaba igualmente, así que la tienda se quedaba con el dinero cobrado y sin nadie a quien enviar. Introducido en la 31.0.6. El arreglo publicado en esa versión para Apple Pay Express — no entregar el pedido a WooCommerce Blocks como su borrador de la Store API, porque la hidratación del bloque lo resincroniza desde el carrito y borra los datos de contacto que aportó la hoja de Apple Pay — se aplicó a los dos modos de wallet, y ambos necesitan exactamente el comportamiento opuesto. Express lleva sus propios datos de contacto desde la hoja de Apple Pay, así que dejar que Blocks resincronice el pedido los destruye. El modo estándar no tiene datos propios: el comprador los teclea en el formulario de checkout, y registrar el pedido como el borrador de la Store API es precisamente lo que hace que WooCommerce lo rellene. La propiedad de esa clave de sesión se decide ahora por modo, usando un flag que el script de checkout ya enviaba: en modo estándar el pedido se registra como borrador para que el bloque lo rellene, y en modo express se libera exactamente como en la 31.0.6. Apple Pay Express no se ve afectado y mantiene el comportamiento de la 31.0.6.
- Apple Pay elegido como método de pago normal en el checkout basado en bloques podía fallar el pago cuando el comprador rellenaba la dirección en la página de checkout. La hoja de Apple Pay mostraba el importe sin impuestos (por ejemplo 30,00) mientras que el servidor enviaba el total real a Redsys (36,30), y el pago se rechazaba por el descuadre. El importe mostrado en la hoja se calculaba una sola vez, al renderizar la página de checkout, y no se actualizaba después — así que en un checkout de bloques, donde los impuestos y el envío se recalculan a medida que el comprador teclea una dirección, la hoja seguía mostrando la cifra de antes de que existieran esos campos. Los compradores que llegaban al checkout con su dirección ya guardada nunca lo veían, razón por la que sobrevivió al arreglo de impuestos de la versión anterior. El importe mostrado en la hoja sigue ahora el total del carrito en vivo, de modo que siempre coincide con lo que se cobra.
- Apple Pay elegido como método de pago normal en el checkout basado en bloques podía abrir la hoja de Apple Pay con campos obligatorios del checkout todavía vacíos, llevando al cliente a través de Face ID o Touch ID solo para fallar después, y dejaba visible el propio botón “Realizar el pedido” de WooCommerce junto al botón de Apple Pay, de modo que no quedaba claro cuál pagaba. Los campos obligatorios se comprueban ahora antes de abrir la hoja — los que faltan se marcan en el formulario de checkout — y el botón “Realizar el pedido” se oculta mientras Apple Pay es el método de pago seleccionado, reapareciendo cuando se elige otro método.







