The development of this version has cost €2,800. The accumulated cost for this year is €13,350. The accumulated cost since the first version is €212,080, but the cost for you is only the license of €79.
New version 31.0.0 of the Redsys plugin for WooCommerce from WooCommerce.com.
31.0.0
New:
- New A2A (Agent2Agent) protocol surface. Optional and disabled by default. Publishes an Agent Card at /.well-known/agent-card.json and a JSON-RPC 2.0 endpoint at /wp-json/wc-redsys-a2a/v1/rpc with seven skills: query_payment_status, create_payment, capture_payment, refund_payment, list_payments, tokenize_card, and recurrent_charge. Authentication reuses the OAuth 2.1 + PKCE authorization server from UCP with scopes a2a:payments:* (and an optional sandbox bearer for testing under WP_DEBUG).
- Resumable Server-Sent Events stream at /wp-json/wc-redsys-a2a/v1/tasks/{taskId}/stream for live task updates, with resumption via Last-Event-ID and heartbeats every 15s.
- Outgoing push notifications signed to a push_url registered by the client. Header X-A2A-Signature: sha256=<hmac> over <timestamp>.<body>, with retries at 1m/5m/30m/2h (maximum 4), delivered via Action Scheduler.
- New admin tab "A2A (Agent2Agent)" within Advanced Redsys -> Agentic Commerce, with Status / Clients (create / revoke / rotate sandbox bearers) / Limits and modes (max_amount, sensitive_threshold, and exposed payment modes) / Confirmations (approve refunds or stopped recurring charges in input-required).
- Limits per skill: max_amount is a strict maximum limit that fails the task with limit_exceeded; sensitive_threshold stops the task in input-required until an administrator confirms it. Both have no default limit (optional).
- Append-only audit log in wp_redsys_a2a_audit_log. Raw payloads are never stored: only a SHA-256 hash plus a sanitized one-line summary that masks PAN, tokens, secrets, and the local part of emails.
- Self-repairing storage. The four A2A tables are recreated on demand using ensure_table() from each store, so a deleted table never causes a fatal error.
- Support for AI shopping agents (ChatGPT, Claude, Gemini, Perplexity, and others). AI agents can now discover your products, build carts, complete payments, and receive order updates from your store. Both the OpenAI/Stripe ACP standard and the open UCP standard (backed by Google, Shopify, and others) are supported, and each can be activated or deactivated independently.
- New "Agentic Commerce" subsection within the Advanced Redsys settings, with function switches (carts, discounts, fulfillment, buyer consent, dynamic customer registration, etc.) and one-click buttons to test agent discovery flows, OAuth, and webhook.
- Webhook signing with a "Rotate signing key" button. Rotated keys remain valid for 7 days so existing integrations continue to work during the change.
- The Express custom fields modal (Apple Pay / Google Pay) now also collects fields registered via the Blocks API in classic checkouts, so NIF/DNI and similar fields always appear regardless of the checkout you use.
- Optional bulk action "Approve preauthorization" for the order list (Redsys Redirection). Disabled by default for security; enable it in the gateway settings only if you need to confirm preauthorized orders in bulk.
Note:
- Enabling this support DOES NOT mean that AI assistants will start completing purchases in your store from day one. The full agent-driven checkout flow is still being rolled out, especially in Europe, where SCA/3DS, PSD2, and GDPR consent requirements cause most agents to stop at product discovery or pre-cart and return the final payment to the customer. Your store is ready for the day when each agent activates the full flow in your market.
Updated:
- The Express custom fields modal now preloads its configuration when the page loads and initiates Apple Pay synchronously on click, fixing cases where iOS Safari aborted the payment sheet.
- The Express custom fields on the product page revert to using the default modal. The inline form introduced in 30.4.1 is now optional via a filter.
Fixed:
- [Critical] Subscription renewals could fail with the Redsys SIS0502 error ("Id Oper does not match") on hosts with Redis, Memcached, or other persistent object caches. Two renewal workers in parallel could send two different order references to Redsys for the same subscription, causing the renewal to fail and retry many times. Duplicate payment locking and order reference are now reliably stored regardless of the cache backend. Affects Redsys Redirection and InSite gateways.
- InSite subscription renewals had no concurrency protection (the lock was removed on errors but never created). The same protection used by the redirection gateway now applies to InSite renewals.
- The cached internal state used during checkout (3DS data, saved card identifier, signature cache, OAuth state, temporary IMAP state, etc.) is now stored in a way that works correctly on hosts with persistent object caches. Previously, on poorly behaving caches, this state could silently disappear mid-checkout and cause SIS0502 errors.
- The Express Apple Pay and Google Pay buttons (block checkout and product page) failed in iOS Safari with "Must create a new ApplePaySession from a user gesture handler" when the Express custom fields modal was activated. The modal no longer consumes the user click before launching Apple Pay.
- The Premium Self-Employed IRPF was calculated incorrectly in Express Payment (Apple Pay / Google Pay) on the product page when the customer selected the user type in the Express modal, because the modal saving competed with the first server call of Apple Pay. The user type is now sent with each Express Pay request, so totals are always correct.
- On product pages with virtual products, the Apple Pay sheet briefly showed the correct total (subtotal + taxes) and then reverted to the product price without taxes. The submission callback now retains the last total returned by the server.
- Saving the Agentic Commerce settings silently discarded the WooCommerce "Save changes" submission due to nested HTML forms. The settings page has been restructured and all single action buttons (test discovery, test OAuth, webhook actions, etc.) now work correctly alongside the main Save button.
- The Express Apple Pay and Google Pay buttons on the product page assigned the wrong shipping zone when the customer's region was restricted by province. Apple/Google Pay sends the localized state name (e.g., "Barcelona") in administrativeArea, but WooCommerce shipping zones match by state code (e.g., "B"); the country-by-state zone did not match and the cart fell into a more expensive zone (e.g., "Rest of Europe"). State names are now normalized to WooCommerce state codes before shipping zone lookup and order creation.
- InSite checkout with Blocks could create duplicate orders when Redsys issued the same payment token postMessage more than once (or when saving was executed concurrently). A protection now prevents the creation of duplicate/concurrent orders and resets if the payment form refreshes after an error.
- The InSite REST debug log referenced the wrong object to read the debug flag, so the "REST trataPeticion" payload could be logged (or skipped) regardless of the gateway's debug setting. It now uses the gateway's own debug flag.
31.0.1
Updated:
- The countdown of the Bizum modal on the payment page showed 7 minutes. It now correctly shows 5 minutes, and the server-side time control that redirects to checkout has been adjusted accordingly (60 iterations * 5 seconds = 5 minutes).
Fixed:
- Bizum on the payment page (Bizum InSite) did not send the description to Redsys. The actual charge from the modal is made through the REST API (trataPeticionREST), and that request did not include the parameter DS_MERCHANT_PRODUCTDESCRIPTION, so the configured "Redsys Description" (for example, "Order ID") arrived empty at the Redsys panel. The description is now calculated based on the selected setting and sent in the REST request.
31.0.2
Security:
- The SHA-256 secret key of the merchant is no longer written in plain text in the WooCommerce debug logs. All debug entries now mask the key, showing only its last 4 characters, using the new helper WCRed()->mask_secret(). Applied across all gateways (Redirection, InSite, Bizum, Google Pay, Apple Pay, PayGold, Direct Debit, and the support classes of Blocks).
- The signatures of the notifications (IPN) from Redsys are now verified with hash_equals() (constant time comparison) in all notification handlers, unifying them with the verification already used by InSite and the REST client. Hardening against timing attacks.
- The AJAX handler for deleting a single token on the token management screen now requires the manage_woocommerce capability in addition to the nonce, matching it to the bulk delete handler.
Updated:
- The filter redsys_modify_data_to_send now receives 'context' => 'add_payment_method' and 'user_id' in the data array for the "Add payment method" flow, allowing custom snippets and conditional rules to route tokenization to the correct terminal (terminal + SHA256) without relying on an order that does not exist in this flow.
- All translations updated.
Fixed:
- Adding a card from My Account ("Add payment method") in multi-currency stores could be rejected by Redsys with a currency error. The request was sent with the customer's session currency (for example, ARS, 32) but with the default terminal, which is configured for another currency (for example, EUR, 978). Card tokenization is a zero-amount operation without an order behind it, so if no filter or conditional rule routes the request to another terminal, the store's base currency is now sent, always matching the default terminal's configuration.
- Adding a card from My Account (or via the email link from the profile) always registered the card in Redsys as one-click (DS_MERCHANT_COF_TYPE 'C'), even when the customer selected the subscription option. The token type was read from the wrong internal key, so 'R' was never sent. The token was correctly saved as R in WooCommerce, but the COF agreement was created as C in Redsys, which could cause some issuers to reject subsequent subscription renewals (MIT) made with those tokens. Cards added with the subscription option now correctly send DS_MERCHANT_COF_TYPE 'R'. Tokens created while the error was present cannot be reclassified; if a renewal is rejected, ask the customer to re-add the card.
- Payments made with a saved card (token R) and subscription renewals did not respect pre-authorization settings. With "Preauthorize all orders" enabled (or a product marked for pre-authorization), the charge was executed as a normal sale (type 0) but the order was still marked as "Preauthorized", causing the pre-authorization confirmation to fail later in Redsys with SIS0059 ("No operation exists to confirm"). Both flows now send a real pre-authorization of type 1 that can be confirmed later, and an order is only marked as "Preauthorized" when a real pre-authorization has been executed. This also enables pre-authorized subscription renewals (for example, goods sold by weight, where each renewal is adjusted before the final charge).
31.0.3
New:
- Section "Apps and Plugins" within Redsys Advanced -> Advanced Settings of Redsys. A read-only landing page that shows the native management app for macOS (with download, requirements, and platforms "coming soon") and lists the rest of free and premium plugins, websites and portals, Claude skills, and José Conti's developer profiles, each grouped by type with a link. Each list is filterable (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).
Security:
- Removed a notification signature verification bypass that could allow an anonymous request to mark an order as paid when the SHA-256 secret was left empty or misconfigured. The Redsys IPN listener (public endpoint wc-api) had a legacy fallback that, when no secret was configured, accepted a notification solely because the Ds_MerchantCode sent matched the merchant's FUC, a public and non-secret value that an attacker can supply. This acceptance based solely on the merchant code has been removed from all gateways (Redirection, Bizum, Bizum InSite, Google Pay checkout and redirection, Apple Pay, PayGold, Direct Debit, MasterPass, and Bank Transfer): when there is no secret to verify the HMAC, the request now fails securely (HTTP rejection) instead of being trusted. Additionally, the shared verification routine (WooRedsysAPI::verify_signature_notif) now returns false whenever the merchant key or the received signature is empty, so neither check_ipn_request_is_valid() nor successful_request() can complete a payment with a calculable empty key HMAC by the attacker. InSite already required a valid signature (no fallback by merchant code) and Inespay is not affected (relates orders by its own payin id). Real stores are not affected, as having a configured secret is mandatory to collect payments.
Fixed:
- Bizum notifications were rejected with a signature verification error, leaving the order unpaid even though the payment had been authorized in Redsys. The merchant key and order number were correct (the request signature matched perfectly), but the notification signature is an HMAC calculated over the string Ds_MerchantParameters exactly as received, and Bizum notifications arrive with the final base64 padding "=" removed, while Redsys signs over the value with canonical padding, so the locally calculated HMAC never matched. The shared notification routine (create_merchant_signature_notif in WooRedsysAPI) now re-pads the payload to a multiple of four before hashing, and the verification (verify_signature_notif) now compares both signatures ignoring the final padding "=" (which does not add entropy, so the comparison remains constant time). Card payloads are already canonical and left intact, so the rest of the gateways continue to function unchanged. This routine is shared by all notification handlers (Redirection, Bizum, InSite, PayGold, Google Pay, Apple Pay, Direct Debit, Bank Transfer, and REST/A2A clients).
- Card and wallet notifications could be rejected with a "signature mismatch", causing the customer returning to the thank you page (and the server-to-server IPN) to not mark the order as paid until a subsequent IPN retry succeeded by chance; refunds and later paid PayGold links could remain unconfirmed. There are two padding issues involved: in addition to the previous Bizum payload padding, Redsys delivers the notification signature (Ds_Signature) in base64 URL-safe with the final "=" removed, while the locally calculated signature keeps it, so a strict comparison also fails for normal card payments. The padding-tolerant comparison lives in verify_signature_notif(), but most handlers continued to compare the signature inline with a strict hash_equals()/!== that reintroduced sensitivity. Now all handlers delegate to verify_signature_notif(), so the fix reaches everyone: Card Redirection (both IPN validators and REST-SOAP capture/refund response checks), Bizum, Bizum InSite, InSite (IPN, successful_request, and the two REST response validators), PayGold, Google Pay (checkout and redirection), Apple Pay, Direct Debit, Bank Transfer, MasterPass, and the InSite REST client. Regression introduced when signature checks by gateway were unified in verify_signature_notif() with a strict and padding-sensitive comparison.
- Bizum notifications in multi-currency / dual terminal stores (those that route each order to a different terminal via the bizum_modify_data_to_send filter or a conditional rule) now resolve the actual signature key per order before verifying the signature (the key from the settings adjusted by the customer, the transient saved when the payment form was generated, or the order meta _redsys_secretsha256), and a non-verifiable notification is rejected with HTTP 400.
- InSite card payments that required a 3DS challenge displayed a blank verification screen in Safari (iPhone/Mac), preventing the customer from entering the SMS/PIN code and causing the payment to fail (the same flow worked in Chrome). The challenge itself was already a full-page, top-level navigation, but before it, the plugin redirected the browser to the issuer's 3DS Method URL (threeDSMethodURL) to take the browser fingerprint, and Safari's Intelligent Tracking Prevention (ITP) blocks the third-party cookies that this step needs, leaving a blank ACS page (symptom: the ACS URL arrives with ";jsessionidpa=" because the cookies do not flow). The 3DS Method step in the browser is optional, so the plugin no longer redirects the browser to threeDSMethodURL: it always sends threeDSCompInd = 'N' and goes directly to authentication. Applied to all InSite flows – new card and saved card (one-click/token), both in block checkout and classic (shortcode) – and to the REST/token flows of the Redirect gateway (pay_with_token_c, receipt_page, and successful_request), as the same blank screen could appear when charging a saved card token.
- When an InSite card payment failed due to a missing required checkout field or mismatched data (for example, an empty last name produced a "signature mismatch"), the error message blamed the card ("…please re-enter your card details"), leading customers to think their card had been declined and abandoning the order. The message is now neutral and first points to the checkout fields: it asks the customer to ensure that all checkout fields (first name, last name, address, etc.) and card details are filled out correctly before trying again. Applied to both classic and block checkouts (Blocks).
- Refunds and deferred payments for orders with large order IDs (the "billion-dollar bug" already fixed for instant checkout) were recorded against the wrong order, or not recorded at all. The notification (IPN) from Redsys retrieves the WooCommerce order ID from the order number using clean_order_number(), which first looks for a saved transient when the order number was generated and, if not found, resorted to a substr/ltrim heuristic that discards high-order digits from IDs with 10 or more digits. That transient has a TTL of 1h, so the fallback was triggered whenever the notification arrived more than an hour after the order number was generated: each refund (processed days or weeks later) and PayGold payment links (paid by the customer later), among others. Three changes make it robust for all payment methods: (1) the shared refund routine (ask_for_refund) now re-saves the mapping order number -> real order ID at the time of refund with a TTL of 24h; (2) clean_order_number(), when the transient no longer exists, now looks for the order in reverse by the persisted meta of the order number (_payment_order_number_redsys / _redsys_transaction_id2) before ever using the loss heuristic; and (3) PayGold now persists the order number in the meta when the link is created, so a link paid much later still resolves to the exact order. It applies to all gateways (Redirect, InSite, Bizum, Bizum InSite, Google Pay, Apple Pay, PayGold, and Direct Debit). Inespay is unaffected (it relates orders by its own payin id).
- InSite card payments in block checkout (Blocks) failed the FIRST time with "msg18" (the customer saw "check that the checkout/card fields are filled out") and only worked on the second attempt. The InSite SDK from Redsys requires the page to send a "domain" message to the card iframe so that Redsys can validate the merchant; the SDK does this via an onload="setMerchantDomain(0)" inline in the iframe. The block script overwrote that handler with its own iframe.onload (used for sizing the iframe) just after the first render, so the merchant domain was never sent and Redsys rejected the tokenization with msg18 ("invalid validation by the merchant"). In the automatic form refresh, the overwrite did not execute, so the second attempt worked. The plugin now explicitly calls setMerchantDomain after building the form (as the SDK's own payment button integration does) and no longer overwrites the iframe's onload, so the first attempt works.
- InSite block checkout: the handling of Redsys postMessage has been made more robust and end-to-end diagnostics have been added. The handler now accepts messages from any Redsys subdomain (sis.redsys.es, sis-t.redsys.es, sis-d.redsys.es) instead of requiring an exact host/port match (Redsys publishes from more than one host), removes any listener left by a previous render so that a single token cannot trigger multiple submissions, and clears the token/error fields after reading them so that an obsolete re-trigger cannot resend them. When InSite's debug logging is enabled, the entire client-side tokenization step (order number, Redsys postMessage, token/error code, save, and place-order) is reflected in WooCommerce's "insite" log, so that failures of the first attempt that never reach PHP can be diagnosed.
- InSite card payments in block checkout (Blocks) were rejected by Redsys with SIS0574 ("EMV3DS authentication operation rejected, browserUserAgent not specified"): the 3DS fingerprint of the browser (user agent, screen size, language, color depth, time zone) never reached the order, because the hook that normally copies it to the order (woocommerce_checkout_create_order) does not execute in the Store API checkout (Blocks). The InSite Blocks form now collects the fingerprint and sends it along with the operation token, and it is written to the actual order (along with the token) before process_payment is executed, so that the EMV3DS authentication has the data it needs.
- InSite card payments in block checkout (Blocks) were rejected with an "error code without token" (shown to the customer as "check that the checkout/card fields are filled out"), so the card could not even be tokenized after a few attempts. Since the order does not yet exist in the Blocks checkout (order id 0), the prepared Redsys order number was generated from id 0, resulting in an almost constant value – only ~999 possible numbers, all ending in nine zeros – and Redsys rejects a reused order number ("repeated order", SIS0051). When the draft order id is still not available, the InSite Blocks form now uses the same temporary order number as the InSite checkout shortcode (create_checkout_insite_number: always starts with 1, globally unique via a shared incremental counter), so it never collides; when the draft order id is available, the Redsys number is built from it. The number is re-linked to the real order id when the order is placed.
- InSite card payments in block checkout (Blocks) failed: the customer saw a misleading message "check that the checkout/card fields are filled out" and the order was never paid (the browser console showed a 404 on the save_order_data request). The cause is that the current WooCommerce no longer creates the order until the customer clicks "Place Order" – during card entry, the Blocks checkout store returns order id 0 – so the InSite operation token (idOper) and the prepared Redsys order number could not be persisted: the previous flow sent them to a REST route (save_order_data) that tried to attach them to order 0 and returned "Invalid order" (404), and the generated order number was mapped to order 0 (so that even a later notification could not find the order). Now, when the card is tokenized, the token and prepared order number are saved in the WooCommerce session via admin-ajax (the WC session is not available in custom REST routes, which is why the previous REST approach could not use it), and they are transferred to the actual order while WooCommerce creates it from the checkout request (woocommerce_store_api_checkout_update_order_from_request), before process_payment is executed, so that process_payment_block() finds _insite_token and _payment_order_number_redsys as in classic checkout. The transient of the order number is also re-mapped to the real order id so that the notification (IPN) resolves the order. The browser fingerprint data (3DS) already used this same session route. The classic/shortcode checkout is unaffected.
Development:
- The noisy debug output console.log from production frontend scripts (minified) has been removed: Apple Pay, Google Pay, capture-order-id, and express-checkout modals. The Apple Pay Express script in particular logged on every DOM change (watches the entire document for its button), flooding the browser console in the Blocks checkout. Minified files now remove console.log/console.warn/console.info/console.debug while keeping console.error; unminified sources remain unchanged for development.







