Mercadopago
Accept payments across Latin America
@nicogorga/medusa-payment-mercadopago
Receive payments on your Medusa commerce application using Mercado Pago.
Medusa Payment Mercadopago Repository | Medusa Website | Medusa Repository
[!WARNING] This plugin is a WIP and has only been tested for Credit / Debit Card methods following Mercado Pago docs for Uruguay. You can sumbit issues through GitHub Issues. Feel free to make contributions by making pull requests and proposing ideas / new flows to implement via Discussions
Features
- Mercado Pago integration via Checkout API
- Payments created asynchronously via webhook event.
- Payments automatically captured (so far as for Uruguay, Credit / Debit is auto capture)
- Customers and Cards automatically saved to Mercado Pago, so you can implement saved cards in the frontend
- Recurring payments via Mercado Pago Subscriptions (preapproval)
Prerequisites
- Node.js v20.19 or greater (Copy to clipboard
^20.19.0 || >=22.12.0) - A Medusa backend on v2.21.0 or greater
- For local testing, you need to expose localhost. You can use ngrok
- Mercado Pago developers setup:
- Mercadopago developer account
- Mercado Pago Checkout API application
- Name your app
- Choose Pagos Online under "Solution Type"
- Select Yes to ecommerce platform question and select Otrasplataformas from the dropdown
- Select CheckoutAPI from the "Product to integrate" dropdown
- Create application. For more information visit Your Integrations
- Setup Mercado Pago (credentials)[https://www.mercadopago.com.uy/developers/es/docs/your-integrations/credentials]:
- Generate test credentials and optionally, production credentials.
- Setup Mercado Pago webhook notifications
- Under "Eventos", select Pagos. If you use subscriptions, also select Planes y Suscripciones (topics Copy to clipboard
subscription_preapprovaland Copy to clipboardsubscription_authorized_payment) - (Optional) Generate a webhook secret. Although it is optional, it is recommended for security purposes.
- Go to your Medusa backend, run Copy to clipboard
yarn devand in a separate terminal Copy to clipboardngrok http 9000. If you are serving the backend in a port other than 9000, change the last argument accordingly.- Your localhost will be exposed by a URL like: Copy to clipboard
https://d76b-2800-a4-15d2-2900-1105-b8e5-c64-7697.ngrok-free.app. - Grab the generated URL and go to Mercado Pago webhook configuration. Under "URL para prueba", specify (replacing Copy to clipboard
ngrok URLaccordingly):- Copy to clipboard
[ngrok URL]/hooks/payment/mercadopago_mercadopagoif you only use one-off card payments - Copy to clipboard
[ngrok URL]/hooks/payment/mercadopago-subscription_mercadopagoif you use subscriptions — the subscription provider also handles the one-off Copy to clipboardpaymenttopic, so this single URL serves both providers (Mercado Pago only allows one webhook URL per application). These are Medusa's standard webhook routes, which process events asynchronously with built-in delay and retries (configurable via the payment module's Copy to clipboardwebhook_delay/ Copy to clipboardwebhook_retriesoptions).
- Copy to clipboard
- Your localhost will be exposed by a URL like: Copy to clipboard
- Under "Eventos", select Pagos. If you use subscriptions, also select Planes y Suscripciones (topics Copy to clipboard
- A frontend that integrates Payment brick. I suggest you clone this Storefront
How to Install
1. Run the following command in the directory of the Medusa backend using your package manager (for example for npm):
1npm install @nicogorga/medusa-payment-mercadopago
2. Set the following environment variables in Copy to clipboard.env:
1234# Access Token available in your Mercado Pago application Test Credentials sectionMERCADOPAGO_ACCESS_TOKEN=# (Optional) Webhook secret available in your Mercado Pago application Webhooks sectionMERCADOPAGO_WEBHOOK_SECRET=
3. In Copy to clipboardmedusa-config.ts add the following at the end of the Copy to clipboardplugins array in your project config object:
123456789projectConfig: {plugins = [// ...{resolve: `@nicogorga/medusa-payment-mercadopago`,options: {},},];}
4. In Copy to clipboardmedusa-config.ts add the following to the Copy to clipboardmodules array in your project config object:
1234567891011121314151617181920modules: [{resolve: '@medusajs/medusa/payment',options: {providers: [{resolve: '@nicogorga/medusa-payment-mercadopago/providers',id: 'mercadopago',options: {accessToken: process.env.MERCADOPAGO_ACCESS_TOKEN,webhookSecret: process.env.MERCADOPAGO_WEBHOOK_SECRET,},dependencies: [ContainerRegistrationKeys.LOGGER,Modules.EVENT_BUS]}],}}
The single Copy to clipboardproviders entry registers two payment providers that share a common base:
Provider Payment provider id Use case Regular payments Copy to clipboardpp_mercadopago_mercadopago One-off card payments Recurring payments Copy to clipboardpp_mercadopago-subscription_mercadopago Mercado Pago subscriptions
Both become selectable at checkout — enable whichever you need per region/payment configuration. They share the Copy to clipboardoptions block above.
Subscriptions (recurring payments)
The Copy to clipboardmercadopago-subscription provider creates a preapproval (a recurring charge mandate) at checkout, reusing the same Payment Brick card tokenization as one-off payments. Mercado Pago's engine then charges the buyer automatically every cycle — no cron or capture logic needed on your side.
Checkout flow
- The storefront initiates a payment session for Copy to clipboard
pp_mercadopago-subscription_mercadopagowhen the buyer selects the method (no Copy to clipboarddatayet — nothing is created at Mercado Pago). - The Payment Brick tokenizes the card. The storefront then re-initiates the session through the standard Copy to clipboard
POST /store/payment-collections/:id/payment-sessions(Copy to clipboardsdk.store.payment.initiatePaymentSession), passing the subscription config as the session Copy to clipboarddata:
123456789101112await sdk.store.payment.initiatePaymentSession(cart, {provider_id: "pp_mercadopago-subscription_mercadopago",data: {card_token_id: "<Brick formData.token>",payer_email: "buyer@example.com", // optional — falls back to the authenticated customer's emailreason: "Monthly box", // optionalauto_recurring: {frequency: 1,frequency_type: "months", // or "days"; end_date and free_trial also supported},},});
The provider's Copy to clipboardinitiatePayment creates the preapproval with Copy to clipboardstatus: authorized and Copy to clipboardexternal_reference = payment session id. Re-initiating a session deletes the previous one, which cancels any stale preapproval automatically. 3. The storefront completes the cart as usual (Copy to clipboardplaceOrder → Copy to clipboardcompleteCartWorkflow); Copy to clipboardauthorizePayment verifies the preapproval and the Medusa payment is authorized. 4. Mercado Pago creates the first charge asynchronously (it can take minutes to hours). When its webhook reports the charge approved, the Medusa payment flips to captured.
Canceling the Medusa payment cancels the whole subscription at Mercado Pago. See Refunds below.
Events for your subscription engine
Recurring charges beyond the first have no Medusa payment session, so while handling webhooks the provider re-emits every subscription notification on Medusa's event bus. Subscribe from your own subscription module — the plugin is intentionally not coupled to any:
Event Emitted when Payload Copy to clipboardmercadopago.subscription.updated Preapproval created / status change (authorized, paused, cancelled) Copy to clipboard{ preapproval_id, session_id, status, next_payment_date, payer_id, transaction_amount, currency_id } Copy to clipboardmercadopago.subscription.charge.updated A recurring charge (invoice) is created or updated — every cycle, approved or rejected Copy to clipboard{ preapproval_id, session_id, invoice_id, status, payment: { id, status, status_detail }, transaction_amount, currency_id, debit_date, retry_attempt }
Copy to clipboardsession_id is the Medusa payment session id used at checkout — correlate it (or Copy to clipboardpreapproval_id, stored in the payment's Copy to clipboarddata) with your own subscription records. Event names and payload types are exported from the package (Copy to clipboardMercadopagoSubscriptionEvents, Copy to clipboardSubscriptionUpdatedEventPayload, Copy to clipboardSubscriptionChargeEventPayload).
[!NOTE] Delivery is at-least-once: Mercado Pago re-sends webhooks and Medusa's webhook subscriber retries failed events, so the same notification can produce duplicate events. Deduplicate in your subscriber — e.g. key on Copy to clipboardinvoice_id+ Copy to clipboardstatusfor charges, Copy to clipboardpreapproval_id+ Copy to clipboardstatusfor subscription updates.
Example subscriber:
12345678910111213// src/subscribers/mp-subscription-charge.tsimport { SubscriberArgs, SubscriberConfig } from "@medusajs/framework";import { SubscriptionChargeEventPayload } from "@nicogorga/medusa-payment-mercadopago/types";export default async function chargeHandler({event: { data },}: SubscriberArgs<SubscriptionChargeEventPayload>) {// e.g. create a new order for this cycle, or flag a failed charge}export const config: SubscriberConfig = {event: "mercadopago.subscription.charge.updated",};
Refunds
Use Medusa's standard refund (admin UI or the payment module's Copy to clipboardrefundPayment) — the provider maps it to refunding the most recent charged cycle at Mercado Pago (partial amounts supported). Only the checkout cycle has a Medusa payment record, so older cycles are refunded directly at Mercado Pago (dashboard or refunds API) using the Copy to clipboardpayment.id from the Copy to clipboardmercadopago.subscription.charge.updated event.
Testing subscriptions
Subscriptions have stricter test-mode requirements than one-off payments:
- In your Mercado Pago application, create two test users (Test accounts section): a seller and a buyer.
- Log into the seller test user (use an incognito window), open its own developer panel and use its Access Token as Copy to clipboard
MERCADOPAGO_ACCESS_TOKENand its Public Key in the storefront. - Configure the webhook (URL + secret) inside the seller test user's application, pointing at your ngrok URL Copy to clipboard
/hooks/payment/mercadopago-subscription_mercadopago, with the payment + subscription topics enabled. Re-check after every ngrok restart. - Copy to clipboard
payer_emailmust be the buyer test user's email (it must differ from the seller's). Checkout as guest or with a Medusa customer whose email matches the buyer test user. - Use test cards — cardholder name Copy to clipboard
APROapproves, Copy to clipboardOTHErejects. - After checkout, verify the preapproval in the seller test user's subscriptions panel, and expect the first charge webhook within minutes to a few hours.
Deferred for now (contributions welcome): redirect-based authorization via Copy to clipboardinit_point (subscription without card token), preapproval plans (Copy to clipboardpreapproval_plan_id).
Test the Plugin
1. Run the following command in the directory of the Medusa backend to run the backend:
1npm run dev
2. Enable Mercadopago in a region in the admin. Alternatively, you can use the Admin APIs.
3. Place an order using a frontend that collects payment data using Mercadopago Payment brick like this. Send a POST to Copy to clipboardlocalhost:9000/store/mercadopago/payment with a body that adheres to validator
