Skip to content

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.

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)

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.

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).

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 reads data.id as $_GET['data_id']), 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.
  • Manifest: id:<data.id>;request-id:<x-request-id>;ts:<ts>; (a pair is left out when its value is missing; data.id lower-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 its external_reference belongs 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 webhook job (unique per account + object id), answers OK at 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).

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.

  • POST /v1/payments/{id}/refunds with X-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 reads GET /v1/payments/{id}/refunds and 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 for pending / in_process / authorized payments: 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.
  • 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_now are admin only (schemas/permissions/vendor_multivendor.post.php).
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.

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.php and php83 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.