Skip to content
Back to the blog

Mercado Pago solution for Odoo 18 – Return URL Fix

·

Odoo 18 with Mercado Pago as the payment gateway
Mercado Pago solution for Odoo 18 Return URL Fix

During a recent Odoo 18 implementation using Mercado Pago as the payment gateway, I encountered a problem that was not officially documented and had no working solution in forums or repositories: the Mercado Pago modal did not open at checkout. Instead, an error said the “Return URL” settings were missing.

This prevented users from completing payment. The most frustrating part was discovering there was no way to configure these URLs through the Odoo interface. Although the module payment_mercado_pago lets you enter credentials and enable the provider, it offers no field for setting the back_urls that Mercado Pago needs to redirect users correctly after payment.

Why can't back_urls be configured through the interface?

The official Mercado Pago module for Odoo, known as payment_mercado_pago, is community-maintained and does not follow the regular update cycle of official Odoo modules. Updates have no fixed schedule and depend on voluntary contributions.

The module therefore has no provider setting for return URLs. Because the backend builds the Mercado Pago payload automatically, the interface offers no way to modify it.

Why did I edit the code directly?

The ideal approach would be a custom module inheriting the required classes and overriding the methods to change. This keeps changes separate from the original code and protects them from future module updates.

However, this module is not updated frequently, and I had a staging environment for testing, so I edited the source file directly. This is not recommended for production without version control or prior testing.

For an example of extending functionality through a custom module without changing core files, see my article on customizing the Odoo frontend with CSS, JS and XML using module inheritance: 🔗

Editing payment_transaction.py

The solution was to edit the method _mercado_pago_prepare_preference_request_payload, which builds the payload sent to Mercado Pago to create the payment preference.

The method is located at:
addons/payment_mercado_pago/models/payment_transaction.py

Inside that method, I manually added the back_urls block to the returned dictionary. This dictionary represents the request body sent to Mercado Pago and must contain the return URLs.

Modified snippet:


				
					return {
    'auto_return': 'approved',
    'back_urls': {
        'success': "https://tusitio.com/payment/mp/return",
        'pending': "https://tusitio.com/payment/mp/return",
        'failure': "https://tusitio.com/payment/mp/return",
    },
    'external_reference': self.reference,
    ...
}

				
			

This block belongs to the method that also defines parameters such as external_reference, the items, the notification_url and the details of the payer. The back_urls must be at this level, because Mercado Pago expects them in the preference payload.

Implementing the return controller

Once Mercado Pago has the return URLs, those routes must exist in Odoo. Otherwise, users will be redirected to a 404 page.

The January 2026 update includes the complete controller I use to preserve the cart session and send customers to the order confirmation page.

I added save_session=True to the route. The controller reads status or, if it is absent, collection_status, and logs the reference and status through the standard Odoo logger.

The approved, success, pending and in_process statuses lead to /shop/confirmation. All other statuses return to /shop.

Showing the confirmation page for a pending or processing payment does not mean the funds have been received. A browser redirect does not validate payment: the transaction must be updated through a verified Mercado Pago notification.

File: return_controller.py Location: payment_mercado_pago/controllers/

# path: payment_mercado_pago/controllers/return_controller.py

import logging

from odoo import http

from odoo.http import request



_logger = logging.getLogger(__name__)



class MercadoPagoReturnController(http.Controller):



    # Agregamos save_session=True para asegurar que no se pierda el carrito/usuario

    @http.route('/payment/mp/return', type='http', auth='public', csrf=False, save_session=True)

    def mercadopago_return(self, **kwargs):

        """Captura el retorno del cliente desde Mercado Pago y redirige."""

        

        # 1. Obtenemos estado y referencia

        # MP a veces usa 'collection_status' en lugar de 'status'

        status = kwargs.get('status') or kwargs.get('collection_status')

        reference = kwargs.get('external_reference')



        # 2. Log estándar de Odoo (más rápido que escribir en ir.logging manualmente)

        _logger.info("MercadoPago Return: Ref=%s - Status=%s - Params=%s", reference, status, kwargs)



        # 3. Estados que muestran la confirmación del pedido

        

        confirmation_states = ['approved', 'success', 'pending', 'in_process']



        if status in confirmation_states:

            # Si es aprobado O pendiente (rapipago/tarjeta en revision), va a la Thank You Page

            return request.redirect('/shop/confirmation')

            

        # 4. Solo si falla realmente, volvemos al shop

        _logger.warning("MercadoPago Return no aprobado. Redirigiendo a shop. Status: %s", status)

        return request.redirect('/shop')

And imported it in the corresponding __init__.py file:


				
					from . import main
from . import return_controller

				
			

After restarting the Odoo service (sudo systemctl restart odoo), the flow worked.

Result

If it helps you, great. If you adapt it, even better. Questions, suggestions and experiences are welcome in the comments. I hope this helps other technicians working with Odoo and payment gateways.

Author's note

I'm Sasha Herscovich, and I design, develop and provide technical support for online stores built with WordPress and Odoo. I solve real problems every day and occasionally share what I encounter in case it helps someone else.

This blog contains ideas, solutions and code snippets I use in real projects. Some are for people who manage their own websites; others are for developers and designers looking to improve their work, save time or understand how to solve a particular issue.

I don't sell magic formulas or universal solutions. I share what works for me, what I test and what I learn along the way. If it helps you, welcome. And if you'd like to comment, improve or adapt something, I'd love you to.

Leave a comment

Your comment will be reviewed before it appears. Your email address stays private. Fields marked * are required.