Overview
Take payments with your own Mercado Pago account on OpenCart 4.1: cards with instalments and account money in all seven Checkout Pro countries (Argentina, Brazil, Mexico, Chile, Colombia, Peru, Uruguay), Pix and boleto in Brazil, OXXO and other cash vouchers in Mexico. The customer pays on the Mercado Pago page and comes back to your shop; no card data touches your server. The extension creates the Checkout Pro preference, reads the payments back with your own Access Token, books the order and refunds from the order page, fully or partly.
What it does
- Checkout Pro, all local methods. Mercado Pago shows the methods of the account's country on its own page: cards with instalments, account money, Pix and boleto (Brazil), OXXO and other cash vouchers (Mexico), bank transfer. You can hide payment types, limit instalments and set how many days a voucher stays payable.
- Paid means paid. An order moves to your paid status only after the shop read the payment back from Mercado Pago as approved, its external reference is ours, and its amount and currency equal what the shop sent AND what the order costs now. Money that does not match is kept, flagged on the order and never marks it paid, and you can refund it. That covers a wrong amount, another currency, a second payment of a paid order, a test payment and a payment for an order you closed meanwhile (that order is never reopened, and refunding the money never changes its status or stock).
- Pix, boleto and OXXO wait as pending orders. A payment that waits for the customer moves the order to a Pending status with the voucher link and shows the customer a voucher page that updates itself. It is never shown as cancelled; it is paid by webhook, cron or the waiting page, or it expires on Mercado Pago's side, and only then a waiting order is cancelled.
- Partial and full refunds from a Mercado Pago tab on the order page. Every refund is stored with its X-Idempotency-Key before the call; after a timeout the refund list of the payment is read first and the call is repeated only with the same key.
- Signed webhooks on a parameter-free address (
/extension/kv_mercado_pago/notify.php): with the secret signature saved, a notification without a valid x-signature is rejected; every notification is only a hint, the payment is always read back. An hourly cron recovers lost notifications. - Safe return. OpenCart 4.1 sets its session cookie with SameSite=Strict, so the customer comes back through a small same-site return file; a customer who pays and never returns finds the order released at the next checkout or basket visit (never paid twice).
- Your credentials, kept safe. The Access Token and the secret signature are stored encrypted and never shown again. Updates and uninstall keep your payment records, settings and keys.
Requirements
- Tested on OpenCart 4.1.0.4 (built for 4.1.x), PHP 8.1 or newer with cURL and OpenSSL.
- A Mercado Pago account (your own application with its Access Token) in one of the seven countries.
- The currency of that country (ARS, BRL, MXN, CLP, COP, PEN or UYU) enabled as a store currency and used by the customer; Mercado Pago is offered only when the order currency equals it, so the amount sent always equals the order.
- HTTPS for the live shop (Mercado Pago accepts only https return and notification addresses).
Mercado Pago is a trademark of its owner. This extension is independent software by Kavio Labs that integrates with the Mercado Pago API; it is not affiliated with or endorsed by Mercado Pago.
Installation
- Install the extension. Extensions > Installer > Upload
kv_mercado_pago.ocmod.zip, then Install. - Install the payment method. Extensions > Extensions > Payments > Mercado Pago Checkout Pro > Install, then Edit.
- Before you start. Enable the currency of your Mercado Pago country in System > Localisation > Currencies (ARS, BRL, MXN, CLP, COP, PEN or UYU). Make it the shop currency, or the currency your customers choose. The settings page warns you when the currency is not enabled.
- Connection tab. Choose the country of your account, enter the Access Token of your own Mercado Pago application (Your integrations > your application > Credentials) and, optionally, the secret signature of the webhooks (Your integrations > Webhooks > Configure notifications). Press "Save and test connection": the answer "Connected to Mercado Pago" proves the token works.
- Payment tab. Switch the method on; hide payment types you do not want, set the maximum instalments, the days a Pix, boleto or OXXO code stays payable and, optionally, a statement descriptor (up to 13 characters).
- Order statuses tab. The defaults: Pending while a voucher waits, Processing when paid, Canceled when the last open payment expired or was cancelled, Refunded when the order's recorded payment is refunded in full (refunding a held, not booked payment never changes the status).
- Webhook (recommended). Copy the Webhook URL from the Connection tab and enter it under Your integrations > Webhooks (event: Payments). The same address is also sent with every payment, so notifications arrive even before you do this; the secret signature lets the shop reject forged ones.
- Test. Use a test seller account of Mercado Pago and its test credentials: those payments carry live_mode false and are booked as paid only after you tick "Accept test payments" (leave it unticked on a live shop).
- Cron. Make sure the OpenCart cron runs (
*/10 * * * * php /path/to/cron.php). The extension adds an hourly job that reads waiting payments back.
Updating
Uninstall the payment method (Extensions > Payments > Uninstall), uninstall and delete the old files in the Installer, upload the new zip, install, install the payment method. Payment records, refunds, the log, your settings and your credentials are kept. Open the settings page once afterwards.
Removing all data
Log and cron tab > "Remove all data" (type DELETE). Refused while a payment waits for the customer. The licence stays.
Configuration
| Setting | Default | What it does |
|---|
| Status | off | Offers Mercado Pago at checkout (Access Token and country set, the customer shops in the currency of the country) |
| Name at checkout, note under the button | Mercado Pago, empty | Payment method name and an optional line on the payment step |
| Payment methods (hide) | none hidden | Hides a payment type on the Mercado Pago page: credit card, debit card, prepaid card, vouchers (boleto, OXXO), bank transfer (Pix). Account money is always allowed |
| Maximum instalments | Mercado Pago default | 1 to 36 |
| Days to pay a voucher | Mercado Pago default | 1 to 30: Pix, boleto and OXXO codes expire after this many days. The expiry is sent with the UTC offset of your shop time zone |
| Statement descriptor | empty | Up to 13 characters on the card statement |
| Binary mode | off | Approved or rejected only, no "in review" state |
| Geo zone, sort order | all, empty | Standard OpenCart payment options |
| Country of the account | not set | AR, BR, MX, CL, CO, PE, UY: Checkout Pro charges in the local currency of this country; orders in any other currency are not offered Mercado Pago |
| Access Token | empty | Your application's Access Token; stored encrypted (AES-256-GCM), never shown again |
| Webhook secret signature | empty | Optional; with it, a webhook notification without a valid x-signature (or with a timestamp more than 24 hours off) is rejected (401). Older IPN notifications (topic=), which Mercado Pago never signs, stay a trigger for a read-back. Without it every notification is only a trigger: the payment is always read back with your own Access Token |
| Accept test payments | off | Payments made with test credentials (live_mode false) are booked as paid with a TEST PAYMENT note and no e-mail |
| Waiting / Paid / Cancelled / Refunded status | Pending / Processing / Canceled / Refunded | Order statuses set by the extension; the waiting status never counts as "further along", so a paid order always moves on. A payment moves an order only from no status, the waiting status or "Payment amount mismatch"; an order you closed (cancelled, voided, denied, expired, refunded, reversed, chargeback) is never reopened. Refunded is set only when the order's recorded payment is refunded in full; set it to none to keep the status on refunds |
| E-mail the customer | on | Order status e-mail when the payment is confirmed |
| Debug log | off | Logs every request and response (Access Token, signatures and buyer data such as e-mail, phone, tax id (CPF, CURP ...), cardholder and IP address masked) |
COP and CLP are charged in whole pesos (Mercado Pago accepts "two decimal places or none"; the amount compared with the order is rounded the same way).
Usage
At checkout
The customer chooses Mercado Pago next to your other payment methods. The payment step explains, without promises, that the customer chooses how to pay on the Mercado Pago page. "Continue to Mercado Pago" opens the Checkout Pro page of that order. Pressing the button again opens the same payment page (no second preference).
- Card or account money, approved: the customer comes back through the return file, the shop reads the payment back and shows the success page; the order moves to your paid status.
- Pix, boleto or OXXO: the customer lands on a voucher page with a button to the voucher; the order is a real Pending order with the voucher link in its history and the basket is emptied (the same order must not be paid twice). The page updates itself when Mercado Pago confirms the payment, which can take from minutes to days; the webhook and the cron book the order even when the page is closed.
- Rejected card or left the page: back to the cart with a message, order not cancelled, nothing charged; the customer can pay again on a new payment page.
- Voucher expired or cancelled by Mercado Pago: only then a waiting order moves to the cancelled status, and never an order that is paid.
On the order page
Sales > Orders > the order > Mercado Pago tab: order amount, paid and refunded amounts, preference, every Mercado Pago payment (method, status, amount, voucher link), every refund (amount, body type, state), refund form and "Read back".
- Refund: full or partial, several times, up to what is left, with an optional reason. The amount is spread over the payments of the order, oldest first; a whole untouched payment is refunded with no amount (total refund), anything else with an amount (partial refund). Some payment methods accept no partial refund (Mercado Pago error 3024): the message says so and the whole amount can be refunded. Mercado Pago allows refunds for a limited time after approval and needs balance in the account.
- Money held, not booked: a payment with a wrong amount or currency, a second payment, a test payment or a payment after you closed the order (cancelled, voided, denied, expired, refunded ...) is flagged on the tab and in the order history; the order is not marked paid, a closed order keeps its status and stock, and the payment can be refunded. Refunding such a payment adds a note only: the order status and the stock stay as they are. Only a full refund of the order's recorded payment moves the order to the Refunded status (when one is chosen).
- Read back: reads the payments from Mercado Pago, picks up refunds made in the Mercado Pago panel, and reconciles refunds without a clear answer.
Log and cron
The Log and cron tab lists the calls with secrets masked, and shows the cron address. The cron reads waiting payments back and reconciles refunds; it also covers a closed browser and a lost notification.
Developer notes
Technical reference for developers who customize Mercado Pago Checkout Pro. Every endpoint and field below comes from the official Mercado Pago developer documentation (mercadopago.com/developers); the page is named next to it. Points that could not be verified are listed in Conformance and marked "not verified" here.
Identity
| Item | Value |
|---|
| Extension code | kv_mercado_pago (OpenCart 4.1, type payment) |
| Route | extension/kv_mercado_pago/payment/kv_mercado_pago |
| Namespace | KavioLabs\KvMercadoPago (shared core in system/library/) |
| Settings | payment_kv_mercado_pago_* (Access Token and secret signature sealed v2:) |
| Tables | kv_mercado_pago_txn (one row per preference), _pay (Mercado Pago payments), _refund, _log |
| Webhook | <shop>/extension/kv_mercado_pago/notify.php (fixed route, keeps only data_id, type, topic, id) |
| Return | <shop>/extension/kv_mercado_pago/return.php (boots nothing, no cookie; continues same-site to .back) |
| Events | admin/view/sale/order_info/before (tab), admin/controller/common/header/before (licence), the checkout guard events |
Why Checkout Pro (Preferences API)
Checkout Pro redirects to the Mercado Pago page, which shows every local method of the account's country (cards with instalments, account money, Pix, boleto, OXXO, bank transfer) including 3DS and the buyer's saved methods. No card data touches the shop. Checkout API / Bricks would need card tokenisation and per-method screens on the shop.
Endpoints used
Host https://api.mercadopago.com, header Authorization: Bearer <Access Token> on every call. Error body: {"message", "error", "status", "cause": [{"code", "description"}]}.
| Call | Endpoint | Used for | Reference |
|---|
| Create preference | POST /checkout/preferences | Start a payment | checkout-pro-preferences/create-preference/post |
| Search preferences | GET /checkout/preferences/search?external_reference= | Look a preference up after a create call without a clear answer (answer: elements[], total, next_offset; only the last 90 days) | checkout-pro-preferences/search-preferences/get (verified 2026-11-09) |
| Get payment | GET /v1/payments/{id} | The only source of a payment's state | checkout-pro-preferences/get-payment/get |
| Search merchant orders | GET /merchant_orders/search?preference_id= | Every payment of a preference | merchant orders (not verified today: page unavailable, taken from the CS-Cart build audit) |
| Get merchant order | GET /merchant_orders/{id} | Notification topic merchant_order | merchant orders |
| Create refund | POST /v1/payments/{id}/refunds | Total refund (no body) or partial {"amount"}; X-Idempotency-Key REQUIRED (verified 2026-11-09; errors 2063 state, 3024 no partial refund, 4292 key missing, 2024 / 15016 too old) | create-refund/post |
| List refunds | GET /v1/payments/{id}/refunds | Reconcile a refund whose answer was lost | get-refunds/get |
| Payment methods | GET /v1/payment_methods | "Save and test connection" | payment-methods/get |
Preference fields sent
items[0] (id = order id, title = store name + order number, cut at 250 characters by characters, quantity 1, currency_id, unit_price = the order total as a JSON number), payer (name, surname, email; billing name, else customer name), back_urls.success|pending|failure (https only; the return address + &r=<case>), auto_return = approved (needs back_urls.success), notification_url (https, at most 248 characters), external_reference (kvmp-<order>-<txn>-<8 hex>: at most 64 characters, letters, digits, -, _), statement_descriptor (up to 13 characters), binary_mode, payment_methods.installments (1 to 36), payment_methods.excluded_payment_types[].id (ticket, bank_transfer, debit_card, credit_card, prepaid_card), date_of_expiration (ISO 8601 with the offset of the shop time zone), metadata. Response: id, init_point (the customer is sent there; sandbox_init_point only as a fallback).
The documented request headers of Create preference are Authorization and Content-Type. The extension also sends X-Idempotency-Key with it (the stored key of the attempt); this is NOT documented for this call, so safety never depends on it: the row with its external_reference is stored before the call, a call without a clear answer is looked up with Search preferences before anything is created again, and an unanswered attempt is never shown to the customer.
Currencies
One account country per shop: AR ARS, BR BRL, MX MXN, CL CLP, CO COP, PE PEN, UY UYU (Checkout Pro "Availability by country"). The order currency must equal it; amount = order total x the rate stored on the order, rounded to the currency (2 decimals; CLP and COP whole). Anything else is refused before any call.
Notifications
POST <notification_url>?data.id=<id>&type=payment, JSON body {action, api_version, data: {id}, date_created, id, live_mode, type, user_id}, headers x-signature (ts=<ts>,v1=<hex>) and x-request-id; answer 200 or 201 within 22 seconds, otherwise Mercado Pago retries (payment-notifications page, verified 2026-11-09). The signed manifest id:<data.id>;request-id:<x-request-id>;ts:<ts>; and HMAC-SHA256 with the secret signature are taken from the official SDK validators; the current page text does not restate them (not verified in the page). Because of that nothing trusts a notification: with a secret saved, a webhook-format notification (type / data.id) needs a valid x-signature whose ts is within 24 hours (else 401; rejections are logged at most once a minute), an IPN notification (topic=, unsigned by design) and any notification without a secret saved is only a trigger, and in every case the named payment is read back with the merchant's own token and applied only when its external_reference is ours.
Payment states
Mercado Pago status | Local | Order |
|---|
approved | paid | paid status, only when currency and amount equal the order and the order total |
pending, in_process, authorized and anything unknown | pending | Pending status with the voucher link; never cancelled, never paid |
rejected | failed | unchanged (the buyer may retry on the same page) |
cancelled (+ status_detail expired) | canceled / expired | cancelled status only for a waiting order with no other open or paid payment |
refunded | refunded | unchanged (refund status when the whole order is refunded) |
charged_back, in_mediation | disputed | unchanged, never moved backwards |
Tests
php81 tests/unit.php and php83 tests/unit.php against tests/mock-server/server.js (a stand-in built from the reference; it follows field limits, the required X-Idempotency-Key and the error codes) and a throw-away MariaDB database; node tests/e2e.js (OpenCart 4.1.0.4, headless browser) refuses to run unless the provider address is a loopback mock. No test ever reaches a real Mercado Pago endpoint.
Mercado Pago is a trademark of its owner; this extension is not affiliated with or endorsed by Mercado Pago.
FAQ
Do I need a Mercado Pago account?
Yes, your own account and application with its Access Token (Your integrations > Credentials). Payments go to your account; Mercado Pago charges its own fees.
Which countries and currencies?
Argentina (ARS), Brazil (BRL), Mexico (MXN), Chile (CLP), Colombia (COP), Peru (PEN), Uruguay (UYU): the countries of Checkout Pro. One account country per shop; the order currency must equal that country's currency, so the amount and currency sent always equal the order. There is no conversion.
Are Pix, boleto and OXXO included?
Yes, they are shown by Mercado Pago on its own page when your account country offers them (Pix and boleto in Brazil, OXXO in Mexico). They wait as pending orders until they are paid.
What if the customer never comes back to the shop?
The webhook, the cron and the waiting page book the payment. A customer who paid and left the tab finds the basket emptied at the next checkout or basket visit.
Can I refund part of an order?
Yes, partially and repeatedly, from the order page. A few payment methods refuse partial refunds (the page tells you).
Why does a payment say "not booked"?
Its amount or currency differs from the order, it is a second payment of a paid order, a test payment, or you had closed the order (cancelled, voided, denied, expired, refunded ...) before it was paid; such an order keeps its status and its stock. The money is held at Mercado Pago and can be refunded on the tab; refunding it never changes the order status or the stock.
Why does the customer see the waiting page after paying by card?
Mercado Pago sometimes lists a new payment a few seconds late. The return reads the payment Mercado Pago named directly; while it cannot be read yet, the customer sees the waiting page (basket emptied, never "cancelled"), which moves on to the confirmation as soon as Mercado Pago answers.
Does it work with test credentials?
Yes: payments created with test credentials carry live_mode false and are booked as paid only after you tick "Accept test payments".
Does it work on OpenCart 3?
No, OpenCart 4.1 only.
Does it change core files?
No. Events and a cron job only, no OCMOD.
Is it affiliated with Mercado Pago?
No. It integrates with Mercado Pago's public API; Mercado Pago is a trademark of its owner.