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
Install Dependencies
The MercadoPago SDK is already included in the project:
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 );
}
}
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
Customer Initiates Payment
Customer selects MercadoPago and proceeds to checkout. The system creates a sale record with PENDIENTE_PAGO status.
Preference Creation
Frontend calls POST /api/sales/:id/mp-preference to generate the MercadoPago checkout URL.
Customer Completes Payment
Customer is redirected to MercadoPago’s checkout page and completes the payment.
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 } `
);
};
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
POST /api/sales/:id/mp-preference
GET /api/sales/mp-callback
POST /api/sales/webhook
# 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 Number Security Code Expiration 5031 7557 3453 0604 123 11/25 4509 9535 6623 3704 123 11/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