Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Dx Php Laravel Package

mercadopago/dx-php

Official Mercado Pago SDK for PHP, making it easy to add payments to Laravel or any PHP app. Create checkout preferences, process payments, manage subscriptions, and handle webhooks and notifications through a clean, object-oriented API.

Deep Wiki
Context7

Getting Started

Install and configure

composer require mercadopago/dx-php

The package ships no Laravel service provider, so wire it yourself. Put credentials in config/services.php (never call env() from a controller — config:cache will break it):

// config/services.php
'mercadopago' => [
    'access_token'   => env('MP_ACCESS_TOKEN'),
    'public_key'     => env('MP_PUBLIC_KEY'),
    'webhook_secret' => env('MP_WEBHOOK_SECRET'),
    'sandbox'        => env('MP_SANDBOX', true),
],
// app/Providers/MercadoPagoServiceProvider.php (register())
$this->app->singleton(PreferenceClient::class, function () {
    MercadoPagoConfig::setAccessToken(config('services.mercadopago.access_token'));
    MercadoPagoConfig::setRuntimeEnviroment(
        config('services.mercadopago.sandbox')
            ? MercadoPagoConfig::LOCAL
            : MercadoPagoConfig::SERVER
    );

    return new PreferenceClient();
});

Register clients as singletons (PreferenceClient, PaymentClient, CustomerClient, …). The SDK keeps config in static state, so configuring it once at container resolution is the cleanest fit.

Where to look first

  • src/Client/** — one class per resource; method names (create, get, update, search, cancel, refund) map almost 1:1 to API endpoints.
  • src/Resources/** — the typed response objects. $payment->status, $preference->init_point.
  • src/Net/MPSearchRequest.php and src/Net/MPRequestOptions.php — the two escape hatches you will actually use.
  • src/Exceptions/MPApiException.php — always caught, always logged.

First useful flow: Checkout Pro

$preference = app(PreferenceClient::class)->create([
    'items' => [[
        'id'          => (string) $order->id,
        'title'       => "Order #{$order->id}",
        'quantity'    => 1,
        'unit_price'  => (float) $order->total,
        'currency_id' => 'BRL', // or ARS, MXN, CLP, COP, PEN, UYU
    ]],
    'external_reference' => (string) $order->id,
    'notification_url'   => route('webhooks.mercadopago'),
    'back_urls' => [
        'success' => route('checkout.success'),
        'failure' => route('checkout.failure'),
        'pending' => route('checkout.pending'),
    ],
    'auto_return' => 'approved',
]);

$order->update(['mp_preference_id' => $preference->id]);

return redirect(config('services.mercadopago.sandbox')
    ? $preference->sandbox_init_point
    : $preference->init_point);

init_point and sandbox_init_point both exist on the resource — picking the wrong one is the single most common "it charges real money in dev" bug.

Implementation Patterns

Webhook controller: verify, enqueue, return fast

public function __invoke(Request $request)
{
    abort_unless($this->signatureIsValid($request), 403);

    if ($request->input('type') === 'payment') {
        SyncMercadoPagoPayment::dispatch($request->input('data.id'))
            ->onQueue('payments');
    }

    return response()->noContent(); // 204 — do not do work inline
}

private function signatureIsValid(Request $request): bool
{
    $header = $request->header('x-signature'); // "ts=1700000000,v1=<hex>"
    if (! $header) {
        return false;
    }

    parse_str(str_replace(',', '&', $header), $parts);
    $manifest = "id:{$request->input('data.id')};"
        . "request-id:{$request->header('x-request-id')};"
        . "ts:{$parts['ts']};";

    return hash_equals(
        hash_hmac('sha256', $manifest, config('services.mercadopago.webhook_secret')),
        $parts['v1'] ?? ''
    );
}

Job: re-fetch, then reconcile under a lock

public function handle(PaymentClient $client): void
{
    $payment = $client->get($this->paymentId); // authoritative source of truth

    DB::transaction(function () use ($payment) {
        $order = Order::where('id', $payment->external_reference)
            ->lockForUpdate()
            ->firstOrFail();

        if ($order->mp_payment_id === $payment->id && $order->status === $payment->status) {
            return; // idempotent
        }

        $order->update([
            'mp_payment_id' => $payment->id,
            'status'        => $payment->status, // approved|pending|in_process|rejected|cancelled|refunded|charged_back
            'paid_at'       => $payment->status === 'approved' ? now() : null,
        ]);
    });
}

Idempotency and per-call options

$options = (new MPRequestOptions())
    ->setCustomHeaders(['X-Idempotency-Key' => (string) Str::uuid()])
    ->setTimeout(10);

$client->create($payload, $options);

Reuse a stable key derived from your order (e.g. order-{$order->id}) rather than a fresh UUID, otherwise a retried request after a network timeout can double-charge. MPRequestOptions also carries setAccessToken(), which is how you support marketplaces / multi-tenant sellers without touching the global config.

Reconciliation cron

Webhooks are at-least-once but not guaranteed. Add a scheduled command that searches payments for orders stuck in pending for more than N minutes:

$results = $client->search(new MPSearchRequest(50, 0, [
    'external_reference' => (string) $order->id,
    'sort'                => 'date_created',
    'criteria'            => 'desc',
]));

foreach ($results->getResults() as $payment) { /* same reconcile logic */ }

Customers and saved cards — create a CustomerClient record keyed to your users.id, then let Checkout/Bricks attach cards. Card tokens must be generated client-side by the MercadoPago JS SDK; never post raw PANs to your server.

Newer Orders API — MercadoPago\Client\Order\OrderClient consolidates preference + payment into one flow and is the direction the platform is moving. Check country availability before committing; Checkout Pro via PreferenceClient remains the safe default.

Gotchas and Tips

  • v2 vs v3 are different packages in spirit. Old code (MercadoPago\SDK::setAccessToken(), new MercadoPago\Preference(), $pref->save(), Payment::find_by_id()) belongs to 2.x. In 3.x everything goes through client classes and typed resources. Don't mix tutorials from both.
  • Never trust the webhook body. It contains an ID, not a status. Always PaymentClient->get() before mutating an order — payloads can be replayed, or arrive out of order (approved before pending).
  • auto_return requires HTTPS back_urls.success. Valet/Herd .test domains over HTTP will silently drop the redirect. Use a tunnel for local webhook + redirect testing.
  • Sandbox needs test users. A TEST- access token plus a real account email in the payer object produces confusing 401s and invalid_grant. Create buyer/seller accounts from the panel's Test users section and use those emails.
  • Currency is bound to the collector's country. Mismatched currency_id yields invalid_currency; the error arrives in cause, not message.
  • Money is float in the SDK. Store integer cents locally, convert at the boundary. transaction_amount may come back with float rounding.
  • MPApiException hides the useful part. $e->getMessage() is generic; the actionable detail is $e->getApiResponse()->getContent()['cause'], an array of field-level errors. Log the whole content array at error level.
  • Search limits: limit caps at 50, offset at 10 000, and date-range filters (begin_date/end_date with range => date_created) max out at 12 months. For backfills, page by id from your own table instead.
  • Timeouts are your problem. Default Guzzle timeouts are generous enough that a slow MP response can tie up PHP-FPM workers. Set setTimeout() per call, and only retry create-type calls when you attached an idempotency key.
  • Webhook secret ≠ access token, and it differs between test and production applications. Rotating one does not rotate the other. Reject requests with a missing x-signature only after confirming your panel is configured to sign that notification type.
  • Configure notification_url in the panel too, as a fallback. Per-preference URLs override it, but payments created outside your flow (refunds, chargebacks initiated by the buyer) only reach the panel-level URL.
  • Refunds: $paymentClient->refund($id, $amount) for partial — refunded status lags behind by minutes; treat it asynchronously like any other status transition.
  • No Http::fake() here. The SDK uses its own HTTP layer; Laravel's HTTP fake won't intercept it. Swap the transport via MercadoPagoConfig::setHttpClient() (or bind your own thin gateway class) so tests can fake at your boundary.
  • PII in logs. Access tokens and payer documents show up in raw request dumps. Scrub before logging; disable Guzzle debug middleware in production.
  • binary_mode removes in_process and makes digital-goods fulfilment simpler, but tends to lower approval rates for physical goods. Leave it off unless you have a reason.
  • Preference expiry: set expires / expiration_date_to for time-boxed carts, otherwise stale checkouts remain payable indefinitely.
  • Rate limits and 429s are real during flash sales. Wrap outbound calls in a short exponential backoff for safe/idempotent reads (get, search), never for un-keyed create.
  • PHP baseline: 3.x requires PHP 8.1+. Legacy apps pinned to 2.x will not migrate cleanly — plan a side-by-side gateway abstraction before upgrading.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings