Developer notes
Technical reference for developers who customize Mercado Pago Payments. Every endpoint and field below comes from the official Mercado Pago developer documentation (mercadopago.com/developers); the source is named next to it. See also Configuration, Installation, Usage, FAQ and Troubleshooting.
Add-on identity
Section titled “Add-on identity”| Item | Value |
|---|---|
| Add-on id | kv_mercado_pago |
| Function prefix | fn_kv_mercado_pago_ |
| Language variables | kv_mercado_pago.* |
| CSS scope | .kv-mercado-pago |
| PHP namespace | Tygh\Addons\KvMercadoPago |
| Preset | payment (generated by kv new --kind integration) |
Why Checkout Pro (Preferences API)
Section titled “Why Checkout Pro (Preferences API)”Mercado Pago offers Checkout Pro (redirect to the Mercado Pago page), Checkout API / Transparente (card form on your own page) and Checkout Bricks (embedded components). The add-on uses Checkout Pro via the Preferences API:
- It is available in all seven countries (AR, BR, CL, CO, MX, PE, UY: “Availability by country” on the Checkout Pro overview,
docs/checkout-pro-preferences/overview). - Every local method (cards with installments, account money, Pix, boleto, OXXO and other cash vouchers, bank transfer) is shown by Mercado Pago on its own page, including 3DS 2.0, guest checkout and the buyer’s saved methods; no card data touches the store (lowest PCI scope).
- Checkout API / Bricks would need the card tokenisation script and per-method screens (Pix QR, boleto, OXXO) on the store’s checkout, per country, and a much larger PCI surface. The newer “Checkout Pro via Orders API” is not needed for these features.
Endpoints used
Section titled “Endpoints used”All on https://api.mercadopago.com, with Authorization: Bearer <Access Token> (Credentials: test credentials of the
automatically created test seller start with APP_USR too, docs/checkout-pro-preferences/test-accounts). Error body:
{"message", "error", "status", "cause": [{"code", "description"}]}.
| Call | Endpoint | Used for | Official reference |
|---|---|---|---|
| Create preference | POST /checkout/preferences |
Start a payment | reference/online-payments/checkout-pro-preferences/create-preference/post |
| Get payment | GET /v1/payments/{id} |
Read back a payment named by a notification or the return | .../checkout-pro-preferences/get-payment/get |
| Search merchant orders | GET /merchant_orders/search?preference_id= |
Every payment of a preference (lost notification, return without id, cron) | .../merchant-orders/search-merchant-order/get |
| Get merchant order | GET /merchant_orders/{id} |
IPN topic merchant_order |
.../merchant-orders/get-merchant-order/get |
| Create refund | POST /v1/payments/{id}/refunds + X-Idempotency-Key |
Partial ({"amount"}) or total (empty body) refund |
.../checkout-pro-preferences/create-refund/post |
| Get refunds | GET /v1/payments/{id}/refunds |
Reconciliation | .../checkout-pro-preferences/get-refunds/get |
| Create cancellation | PUT /v1/payments/{id} {"status":"cancelled"} |
Cancel a payment that is still pending, in_process or authorized |
.../checkout-pro-preferences/create-cancellation/put |
| Payment methods | GET /v1/payment_methods |
“Test connection” (authenticated, no side effects) | .../checkout-pro-preferences/payment-methods/get |
GET /v1/payments/search is not used: its reference marks collector.id and payer.id as required filters, which the store does
not know; merchant orders by preference_id give the same information with optional filters only.
Preference fields sent (Create preference reference)
Section titled “Preference fields sent (Create preference reference)”| Field | Value | Rule from the reference |
|---|---|---|
items[0] |
id = order id, title = method description + #<order> (250 characters, cut by characters), quantity 1, currency_id, unit_price = order total |
unit_price “can contain two decimal places or none” |
payer |
name, surname, email from the billing address |
optional, helps the checkout |
back_urls.success / pending / failure |
payment_notification.return&...&r=<case> (https) |
https mandatory, http URLs are discarded |
auto_return |
approved |
redirect within 40 s after an approved card payment (docs/checkout-pro-preferences/configure-back-urls) |
notification_url |
kv_mercado_pago.webhook&c=<account> |
max. 248 characters, https; sent only when both hold |
external_reference |
kvmp-<order>-<txn>-<8 hex> |
max. 64 characters, letters, digits, -, _ only |
statement_descriptor |
method setting | up to 13 characters (additional-settings/invoice-description) |
binary_mode |
method setting | true = approved or rejected only (additional-settings/binary-mode) |
payment_methods.installments |
method setting | 1 to 36 (invalid_payment_methods error) |
payment_methods.excluded_payment_types[].id |
ticket, bank_transfer, debit_card, credit_card, prepaid_card |
account money is always allowed |
date_of_expiration |
now + N days, ISO 8601 with the shop’s offset | offline methods and Pix; at least 3 days recommended (additional-settings/expiration-date) |
metadata |
order_id, source |
free JSON |
Response: id (stored as provider_ref), init_point (the customer is redirected there; sandbox_init_point only as a fallback).
Countries and currencies
Section titled “Countries and currencies”Checkout Pro charges in the local currency of the account’s site. The account country is chosen on the Connection page:
| Country | Site | Currency | Sent as |
|---|---|---|---|
| Argentina | MLA | ARS | 2 decimals |
| Brazil | MLB | BRL | 2 decimals |
| Mexico | MLM | MXN | 2 decimals |
| Chile | MLC | CLP | whole pesos (ISO 4217 exponent 0) |
| Colombia | MCO | COP | whole pesos (no decimals; “two decimal places or none” allows it) |
| Peru | MPE | PEN | 2 decimals |
| Uruguay | MLU | UYU | 2 decimals |
The store must have that currency (as the primary currency or with an exchange rate). The order total is converted from the primary
currency with the store’s own rate, rounded as sent, and stored in minor units of the ISO 4217 table (Money). Payments in another
currency are booked and flagged, never applied to the order. If the store has no such currency, the method is hidden at checkout.
Notifications (webhooks) and the x-signature check
Section titled “Notifications (webhooks) and the x-signature check”Source: docs/checkout-pro-preferences/payment-notifications (“Validate the origin of a notification”, tab “Without SDK”) and
docs/your-integrations/notifications/webhooks. Checked 3/3 against the official PHP SDK class WebhookSignatureValidator.
- Request:
POST <notification_url>&type=payment&data.id=<payment id>(PHP readsdata.idas$_GET['data_id']), JSON body{action, api_version, data: {id}, date_created, id, live_mode, type, user_id}, headersx-signature: ts=<ts>,v1=<hex>andx-request-id. - Manifest:
id:<data.id>;request-id:<x-request-id>;ts:<ts>;(a pair is left out when its value is missing;data.idlower-cased);v1= hex HMAC-SHA256 of the manifest with the application’s secret signature; constant-time compare (Webhook::verify()). - Signed notification + secret saved: a wrong signature gets 401 and is never processed. Unsigned notifications (IPN
?topic=payment&id=/?topic=merchant_order&id=, or no secret saved) are only a trigger: the job reads the object back with the account’s own Access Token and accepts it only when itsexternal_referencebelongs to a transaction of that account. - Mercado Pago waits 22 seconds for 200/201 and retries (15 min, 30 min, 6 h, 48 h, 96 h …). The endpoint stores a
webhookjob (unique per account + object id), answersOKat once and runs the job; a failure is retried by our queue. - The endpoint neither prints a template nor uses the session: the full page cache (4.21) never serves or stores it.
Payment states (Get payment status / status_detail)
Section titled “Payment states (Get payment status / status_detail)”| Mercado Pago | Local | Order |
|---|---|---|
approved |
paid | paid status (default P) once the whole amount is in and the order still matches |
pending, in_process, authorized (boleto, OXXO, Pix code, review) |
pending | incomplete order (N) -> pending status (default O, Open); never cancelled |
rejected |
failed | unchanged: the buyer may retry on the same Checkout Pro page |
cancelled (by_payer, by_collector, by_admin) / cancelled + expired (30 days pending) |
canceled / expired | canceled status (default I) only from N or O and when no other attempt is open or paid |
refunded |
refunded | unchanged |
charged_back, in_mediation |
disputed | unchanged, flagged |
One preference can collect several payments (a rejected card and then Pix; two cards). They are stored in
?:kv_mercado_pago_payments; the transaction is paid when the approved payments cover the amount sent (Transition::aggregate()).
A paid status never goes back to pending or failed, and a late “pending” read never replaces a final state.
Money that does not match is booked on the payment, flagged on the order tab and does not mark the order paid: a second paid attempt of an already paid order (duplicate), less or more than the amount sent, a different currency, or an order total edited while the payment was pending (compared with the order’s current total).
Customer return (back_urls)
Section titled “Customer return (back_urls)”payment_notification.return is a top-level GET from Mercado Pago’s domain with payment_id, status, external_reference,
merchant_order_id, preference_id (configure-back-urls). The CS-Cart session cookie is SameSite=Lax (Tygh\Web\Session),
so it is sent on that navigation; the handler still works without it (order id + secret key in the URL). The query is only a hint:
the payment is read back. Paid and applied: the order page. Pending (or paid but not applied): the cart is emptied (session cart
and the signed-in customer’s saved ?:user_session_products rows, also by user id when the session was lost), shipping rates
reset, redirect to checkout.complete with a notice and the voucher link (transaction_details.external_resource_url). Nothing paid
and r=failure: core’s normal route gives the cart back.
Refunds and cancellations
Section titled “Refunds and cancellations”POST /v1/payments/{id}/refundswithX-Idempotency-Key: kvmp-refund-<refund id>-<payment id>. The refund row, the refunded amount and the body type (total or partial) are stored BEFORE the call; a timeout / 5xx keeps the reservation (unknown). The next try first readsGET /v1/payments/{id}/refundsand adopts a refund of that amount that no other row claims; only when none is found it repeats the identical request with the SAME key. A definite rejection (4xx) with nothing booked releases it, as does a refund that Mercado Pago answers as rejected / cancelled. One refund at a time per transaction (GET_LOCK); payment state changes are serialised per order. CLP and COP refunds are whole pesos.- A refund is spread over the transaction’s payments, oldest first; a whole untouched payment is refunded with an empty body (total refund: some methods accept no partial refund, error 3024). Limits: the amount paid minus earlier refunds, and in Multi-Vendor the child order’s share of a parent payment. Refunds are possible within 180 days of approval and need balance in the account.
- Cancellation (
PUT /v1/payments/{id}) only forpending/in_process/authorizedpayments: the order tab button, and the canceled order status (or the refund status) queues it. A payment that got paid meanwhile (error 2018) is read back instead.
Multi-Vendor
Section titled “Multi-Vendor”- Marketplace owner mode (default): one Mercado Pago account (company 0) collects all payments. A cart with several vendors is paid
once on the PARENT order; child orders show that payment and refund only their share (
Transactions::shareLeft()), the status refund works per child, and a child order never cancels the shared parent payment. - Owner mode off: each vendor connects their own Mercado Pago account (Connection page in the vendor panel). The method is then offered only for carts with products of ONE vendor whose account is ready (a cart of several vendors would be one payment on the parent order with no single vendor account to collect it).
- Vendor privileges: refunds, cancellations and refund / cancel job retries need
Access::canRefund()(root admin, or a vendor on an account of their own);cron,run_noware admin only (schemas/permissions/vendor_multivendor.post.php).
Database tables
Section titled “Database tables”| Table | Purpose |
|---|---|
?:kv_mercado_pago_accounts |
Public key, Access Token and secret signature (sealed, AES-256-GCM), country, last connection check |
?:kv_mercado_pago_transactions |
One row per preference: order, preference id, external_reference, amount sent, paid / refunded, status, note |
?:kv_mercado_pago_payments |
Mercado Pago payments of a transaction: status, detail, method, amount, refunded, voucher URL |
?:kv_mercado_pago_refunds |
Refunds per payment and order (idempotency, Multi-Vendor shares) |
?:kv_mercado_pago_jobs, ?:kv_mercado_pago_log |
Queue and log |
Uninstall keeps the payment records, refunds, queued jobs and the connection (retention-safe uninstall) unless the root setting “Permanently delete all payment records on uninstall” is on; only the log is always dropped.
Hooks used
Section titled “Hooks used”PHP: change_order_status_post (refund at the chosen status, cancel pending vouchers when the order is cancelled),
prepare_checkout_payment_methods (hides the method while it cannot charge). Template hooks: orders:tabs_extra (payment tab),
addons:manage / addons:tabs_content (licence), index:styles.
php81 tests/unit.phpandphp83 tests/unit.php: signature manifest, preference body, currencies, aggregation, refund allocation, HTTP contract against documented fixtures (tests/fixtures/).tests/mock-server/server.js: follows the reference (field limits, X-Idempotency-Key required, cancellation states, error body).tests/e2e/e2e.mjs: refuses to run unless the store points$config['kv_mercado_pago_base_url']at 127.0.0.1.
Mercado Pago is a trademark of its owner; this add-on is not affiliated with or endorsed by Mercado Pago.