Skip to main content

Overview

PC Fix integrates with MercadoPago to provide a seamless checkout experience for customers. The integration handles preference creation, payment callbacks, and webhook notifications for automatic payment status updates.

Prerequisites

Before setting up MercadoPago, you’ll need:
  • A MercadoPago account (production or sandbox)
  • Access token from your MercadoPago dashboard
  • Backend URL configured for callbacks and webhooks

Environment Variables

Add the following environment variables to your .env file:
MP_ACCESS_TOKEN=your_mercadopago_access_token
API_URL=https://your-backend-domain.com
FRONTEND_URL=https://your-frontend-domain.com
For testing, you can use MercadoPago’s test credentials. Production credentials are required for live transactions.

Configuration

1

Install Dependencies

The MercadoPago SDK is already included in the project:
npm install mercadopago
2

Configure Service

The MercadoPagoService is located at /packages/api/src/shared/services/MercadoPagoService.ts:
import { MercadoPagoConfig, Preference } from 'mercadopago';

export class MercadoPagoService {
    private client: MercadoPagoConfig;
    private preference: Preference;

    constructor() {
        const accessToken = process.env.MP_ACCESS_TOKEN;
        if (!accessToken) {
            console.warn('MP_ACCESS_TOKEN is not defined');
        }
        this.client = new MercadoPagoConfig({ 
            accessToken: accessToken || '' 
        });
        this.preference = new Preference(this.client);
    }
}
3

Set Up Webhooks

Configure your MercadoPago webhook URL in the dashboard:
https://your-backend-domain.com/api/sales/webhook
Webhooks are only configured for production environments (HTTPS). Local development uses callback URLs only.

Creating Payment Preferences

When a customer selects MercadoPago as their payment method, the system creates a payment preference:
async createPreference(
    saleId: number, 
    items: any[], 
    payerEmail: string, 
    shipmentCost?: number
) {
    // Map product items to MercadoPago format
    const mpItems = items.map(item => ({
        ...item,
        title: item.title
    }));

    // Add shipping cost if applicable
    if (shipmentCost && shipmentCost > 0) {
        mpItems.push({
            id: 'shipping',
            title: 'Costo de envio',
            quantity: 1,
            unit_price: shipmentCost,
            currency_id: 'ARS'
        });
    }

    const backendUrl = process.env.API_URL || 
        `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`;
    const successUrl = 
        `${backendUrl}/api/sales/mp-callback?external_reference=${saleId}`;

    const body: any = {
        items: mpItems,
        external_reference: String(saleId),
        back_urls: {
            success: successUrl,
            failure: successUrl,
            pending: successUrl
        },
    };

    const result = await this.preference.create({ body });
    return result.init_point;
}

Usage in Sales Controller

The preference is created through the sales controller at /packages/api/src/modules/sales/sales.controller.ts:
export const createMPPreference = async (req: Request, res: Response) => {
    const { id } = req.params;
    const sale = await service.findById(Number(id));
    
    if (!sale) {
        return res.status(404).json({ 
            success: false, 
            error: 'Venta no encontrada' 
        });
    }

    const items: any[] = sale.lineasVenta.map((line: any) => {
        const unitPrice = Number(line.subTotal) / Number(line.cantidad);
        return {
            id: String(line.productoId),
            title: line.producto.nombre || 'Producto',
            quantity: Number(line.cantidad),
            unit_price: Number(unitPrice.toFixed(2)),
            currency_id: 'ARS'
        };
    });

    const costoEnvioNum = sale.tipoEntrega === 'ENVIO' 
        ? Number(sale.costoEnvio ?? 0) 
        : 0;
    const payerEmail = sale.cliente?.user?.email || 
        '[email protected]';
    const link = await mpService.createPreference(
        Number(id), 
        items, 
        payerEmail, 
        costoEnvioNum
    );
    
    res.json({ success: true, data: { url: link } });
};

Payment Flow

1

Customer Initiates Payment

Customer selects MercadoPago and proceeds to checkout. The system creates a sale record with PENDIENTE_PAGO status.
2

Preference Creation

Frontend calls POST /api/sales/:id/mp-preference to generate the MercadoPago checkout URL.
3

Customer Completes Payment

Customer is redirected to MercadoPago’s checkout page and completes the payment.
4

Callback Handling

After payment, MercadoPago redirects to the callback URL:
export const handleMPCallback = async (req: Request, res: Response) => {
    const { status, external_reference } = req.query;
    const saleId = Number(external_reference);

    if (saleId && status === 'approved') {
        await prisma.venta.update({
            where: { id: saleId },
            data: {
                estado: VentaEstado.APROBADO,
                medioPago: 'MERCADOPAGO'
            }
        });
    }

    const frontendUrl = process.env.FRONTEND_URL || 
        'http://localhost:4321';
    res.redirect(
        `${frontendUrl}/cuenta/miscompras?status=${status}`
    );
};
5

Webhook Confirmation

MercadoPago sends a webhook notification for final confirmation:
export const handleMPWebhook = async (req: Request, res: Response) => {
    const { type, data } = req.body;

    if (type === 'payment') {
        const paymentId = data.id;
        await service.processMPWebhook(paymentId);
    }

    res.status(200).send('OK');
};

Webhook Processing

The webhook handler fetches payment details and updates the sale status:
async processMPWebhook(paymentId: string) {
    const mpService = new MercadoPagoService();
    const payment = await mpService.getPayment(paymentId);

    if (payment.status === 'approved' && payment.external_reference) {
        const saleId = Number(payment.external_reference);
        const sale = await prisma.venta.findUnique({ 
            where: { id: saleId } 
        });

        if (sale && sale.estado !== VentaEstado.APROBADO) {
            await this.updateStatus(saleId, VentaEstado.APROBADO);
            await this.updatePaymentMethod(saleId, 'MERCADOPAGO');
        }
    }
}

Price Calculation

Products in PC Fix have different pricing for MercadoPago vs. other payment methods. When not using MercadoPago, a 8% discount is applied (multiplied by 0.92).
let precio = Number(dbProduct.precio);
if (medioPago !== 'MERCADOPAGO') {
    precio = precio * 0.92; // 8% discount for non-MP payments
}

API Endpoints

# Create MercadoPago payment preference
curl -X POST https://api.example.com/api/sales/123/mp-preference \
  -H "Authorization: Bearer YOUR_TOKEN"

Testing

Test User Creation

PC Fix includes a script to create test users in MercadoPago sandbox:
// packages/api/scripts/create-test-user.ts
import { MercadoPagoConfig } from 'mercadopago';

const response = await fetch(
    'https://api.mercadopago.com/users/test_user',
    {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${MP_ACCESS_TOKEN}`,
            'Content-Type': 'application/json'
        }
    }
);

Test Cards

Use these test cards in sandbox mode:
Card NumberSecurity CodeExpiration
5031 7557 3453 060412311/25
4509 9535 6623 370412311/25

Troubleshooting

If payments are not being updated automatically, verify:
  • MP_ACCESS_TOKEN is correctly set
  • Webhook URL is configured in MercadoPago dashboard
  • Your backend is accessible via HTTPS (webhooks require HTTPS)
  • The external_reference matches your sale ID

Common Issues

Preference creation fails:
  • Check that MP_ACCESS_TOKEN is valid
  • Verify item prices are positive numbers
  • Ensure currency_id is set to ‘ARS’
Webhooks not received:
  • Webhooks only work in production (HTTPS)
  • Check MercadoPago dashboard for delivery errors
  • Verify your endpoint returns 200 OK
Payment approved but sale not updated:
  • Check that external_reference is being passed correctly
  • Verify database connection is stable
  • Review application logs for errors

Additional Resources

Build docs developers (and LLMs) love