Lucas Fiege

Integrando una app con SDK PHP de MercadoPago (dx-php)

Dashboard MercadoPago

Muchos de los que recién comenzamos a dar nuestros primeros pasos dentro del mundo de las plataformas de ecommerce nos encontramos con una infinidad de posibilidades al momento de elegir herramientas para que nuestras aplicaciones puedan operar en conjunto con alguna API externa y con ella recibir pagos, por ejemplo.

En mi caso particular me encuentro desarrollando varios sistemas para vender productos digitales y necesitaba emplear una API para gestionar la cobranza de las órdenes que se generen (crearlas y notificar al usuario el monto, actualizar los estados según haya pagado o no, etc.); entonces me decidí por mercadopago, por poseer relativamente bastante documentación respecto a otras alternativas, entre otros factores como comisiones, beneficios, etc.

En este post no es la idea mostrar el desarrollo completo de un ecommerce, solamente responder a una cuestión esencial que llega a la mente de cualquier desarrollador al momento de realizar una integración con un servicio de cobros. Asumiendo de que ya tenemos desarrollado lo mínimo en un sistema para crear órdenes de compra, en el cual por cada compra que realice un cliente se genera un registro interno (orden) con los datos del mismo (más los ítems adquiridos, impuestos, etc. ) podemos ir al punto en cuestión: ¿Como le digo a mercadopago “ésta es mi orden, cobrala y después avisame qué pasa con los pagos”?

Supongo que como en todo el mundo del desarrollo de software, hay varias maneras de solucionar esto, les aporto un enfoque que puede utilizarse, empleando para ello el web checkout que proveen.

Agregando el componente de mercadopago

Para agregar el componente de mercadopago a nuestra aplicación, se debe ejecutar el siguiente comando:

composer require mercadopago/dx-php

Para el siguiente paso se debe contar con una credencial generara para utilizar el web checkout (checkout básico)

¿Cómo funciona el web checkout?

Básicamente, se debe crear una Preferencia de pago (1), la cual es un objeto que contendrá la información relativa al producto o servicio que se vaya a pagar, la información del comprador (email, nombre, dirección, etc), los métodos de pago que se aceptarán, id de referencia de la orden generada en tu aplicación, entre otras configuraciones posibles. Luego de crear esta preferencia, se redirige al usuario al web checkout de MercadoPago (2), allí este podrá seleccionar el medio de pago que desee utilizar. El web checkout interactuará con el cliente en todo momento hasta finalizar o cancelar la operación (3) y finalmente llevará al usuario nuevamente a la aplicación (4).

Enfoque para integrar MercadoPago

Un enfoque muy útil consiste en emplear una clase especifica para encauzar el funcionamiento del gateway de pago dentro de una aplicación (A lo mejor, si se piensa emplear diferentes gateways, la mejor opción sea definir una Interfaz para unificar el flujo de trabajo y los métodos, OOP es tu amigo)

Definir variables de entorno

Una vez generadas las credenciales de MercadoPago, agregarlas dentro del archivo .env de la aplicación:

MP_CLIENT=XXXXXXXXXXXXXXXX
MP_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Nota: es recomendable almacenar las claves vacías en el archivo .env.example

Crear archivo de configuración

Dentro del directorio config , crear un nuevo archivo de configuración llamado payment-methods.php con un contenido similar al siguiente:

<?php

return [

    'enabled' => [
        'mercadopago',
    ],

    'use_sandbox' => env('SANDBOX_GATEWAYS', true),

    'mercadopago' => [
        'logo' => '/img/payment/mercadopago.png',
        'display' => 'MercadoPago',
        'client' => env('MP_CLIENT'),
        'secret' => env('MP_SECRET'),
    ],

    ...

];

Mediante estos parámetros de configuración podremos controlar si la aplicación estará en “modo sandbox” para realizar pagos o bien si pasaremos a “modo producción”, y tendremos alojados allí los valores de las variables de entorno previamente configuradas, adicionalmente podemos establecer otros valores de configuración como ser imagen de logo, texto a mostrar, etc.

Esta es una buena práctica por si posteriormente se necesita cachear la configuración de Laravel en el entorno de producción.

Crear Orden en el sistema y redirigir a MercadoPago

Dentro de la implementación que tengamos, en nuestro controlador de Checkout, debemos tener un método similar al siguiente:

public function createOrder(PreOrder $preOrder, Request $request)
{
    $allowedPaymentMethods = config('payment-methods.enabled');

    $request->validate([
        'payment_method' => [
            'required',
            Rule::in($allowedPaymentMethods),
        ],
        'terms' => 'accepted',
    ]);

    $order = $this->setUpOrder($preOrder, $request);

    $this->notify($order);
    $url = $this->generatePaymentGateway(
             $request->get('payment_method'), 
             $order
         );
    return redirect()->to($url);
}

No creo necesario detallar los métodos setupOrder y notify ya que estos pueden variar dependiendo de la aplicación, la finalidad de los mismos son: crear una nueva orden en el sistema para el cliente y notificar a los administradores de la plataforma sobre dicha orden.

El método generatePaymentGateway se encargará de obtener la clase que gestionará el Gateway de MercadoPago y retornará la URL a la que se redirigirá al usuario para que realice el pago, de la siguiente forma:

protected function generatePaymentGateway($paymentMethod, Order $order) : string
{
    $method = new \App\PaymentMethods\MercadoPago;

    return $method->setupPaymentAndGetRedirectURL($order);
}

Nótese que esta granularidad permite modificar este método para emplear otros medios de pago de manera dinámica, aumentando un poco el nivel de abstracción.

Finalmente el método setupPaymentAndGetRedirectURL se encargará de toda la lógica para enviar la información necesaria a MercadoPago y debe ser implementado en una nueva clase, la cual podemos crear dentro del namespace App\PaymentMethods\MercadoPago

La descripción de la implementación de dicha clase sigue a continuación

Implementación de la clase App\PaymentMethods\MercadoPago

Podría entonces definirse una clase en app\PaymentMethods llamada MercadoPago con la siguiente definición

<?php

namespace App\PaymentMethods;

use App\Order;
use Illuminate\Http\Request;
use MercadoPago\Item;
use MercadoPago\MerchantOrder;
use MercadoPago\Payer;
use MercadoPago\Payment;
use MercadoPago\Preference;
use MercadoPago\SDK;

class MercadoPago
{
  public function __construct()
  {
     SDK::setClientId(
          config("payment-methods.mercadopago.client")
     );
SDK::setClientSecret(
          config("payment-methods.mercadopago.secret")
     );
  }


  public function setupPaymentAndGetRedirectURL(Order $order): string
  {
     # Create a preference object
     $preference = new Preference();

      # Create an item object
      $item = new Item();
      $item->id = $order->id;
      $item->title = $order->title;
      $item->quantity = 1;
      $item->currency_id = 'ARS';
      $item->unit_price = $order->total_price;
      $item->picture_url = $order->featured_img;

      # Create a payer object
      $payer = new Payer();
      $payer->email = $order->preorder->billing['email'];

      # Setting preference properties
      $preference->items = [$item];
      $preference->payer = $payer;

      # Save External Reference
      $preference->external_reference = $order->id;
      $preference->back_urls = [
        "success" => route('checkout.thanks'),
        "pending" => route('checkout.pending'),
        "failure" => route('checkout.error'),
      ];

      $preference->auto_return = "all";
      $preference->notification_url = route('ipn');
      # Save and POST preference
      $preference->save();

      if (config('payment-methods.use_sandbox')) {
        return $preference->sandbox_init_point;
      }

      return $preference->init_point;
  }

}

Code Breakdown

Constructor de la clase

Dentro del constructor de la clase inicializamos MercadoPago con nuestras credenciales obtenidas desde el archivo de configuración

public function __construct()
{
  SDK::setClientId(
    config("payment-methods.mercadopago.client")
  );
  SDK::setClientSecret(
    config("payment-methods.mercadopago.secret")
  );
}

Método setupPaymentAndGetRedirectURL

Este método está compuesto por diferentes etapas, podría incluso refactorizarse y descomponerse en sub-métodos para mejorar la estructura del código.

1. Crear objeto de Preferencia de Pago

# Create a preference object
$preference = new Preference();

# Create an item object
$item = new Item();
$item->id = $order->id;
$item->title = $order->title;
$item->quantity = 1;
$item->currency_id = 'ARS';
$item->unit_price = $order->total_price;
$item->picture_url = $order->featured_img;
# Create a payer object
$payer = new Payer();
$payer->email = $order->preorder->billing['email'];
# Setting preference properties
$preference->items = [$item]; 
$preference->payer = $payer;

En este paso se debe indicar la información referente al/los item/s a cobrar y la información del usuario al que se le realizará el cobro, nótese que se instancia un objeto de la clase MercadoPago\Item y un objeto de la clase MercadoPago\Payer . Pueden agregarse tantos items como se deseen, pero para ello deberá almacenarselos en un array para luego asignarlos a la preferencia de pago.

2. Añadir id de referencia externa Luego se deben indicar algunas configuraciones adicionales de la preferencia de pago, en este caso podemos comenzar con el id de referencia externa (id interno de la Orden en nuestra aplicación)

# Save and POST preference
$preference->external_reference = $order->id;

Aquí tenemos un aspecto clave: el atributo external_reference, el cual será útil posteriormente para poder identificar nuestra orden del sistema al momento de cotejar la información que nos de la notificación IPN de mercadopago.

A efectos prácticos utilicé solamente la ID de la orden en limpio para este ejemplo; pero el código de referencia externo podría ser algo más “vistoso”, por ejemplo REFOR0007896 (tener en cuenta que después deberá limpiarse lo que no sirva para obtener el ID de la orden, por ejemplo 7896 para este ejemplo).A efectos prácticos utilicé solamente la ID de la orden en limpio para este ejemplo; pero el código de referencia externo podría ser algo más “vistoso” ya que figurará en la información resumen de la operación en el checkout que verá el usuario, por ejemplo OR0007896 (tener en cuenta que después deberá limpiarse lo que no sirva para obtener el ID de la orden, por ejemplo 7896 para este ejemplo).

3. Otras configuraciones adicionales de preferencia de pago

$preference->back_urls = [
  "success" => route('checkout.thanks'),
  "pending" => route('checkout.pending'),
  "failure" => route('checkout.error'),
];

$preference->auto_return = "all";

En estas líneas podemos especificar diferentes opciones, por ejemplo las URL de retorno luego de que el cliente finalice la operación en el web checkout (debemos tener creadas dichas rutas), además indicar bajo que condiciones se redireccionará nuevamente a la aplicación al finalizar la operación (en este ejemplo se redireccionará en todos los casos), establecer métodos de pago permitidos, fecha de expiración del link de pago, etc. Todo ello puede consultarse aquí.

4. Indicar ruta para notificaciones IPN

$preference->notification_url = route('ipn');
# Save and POST preference
$preference->save();

Por ultimo (de manera opcional) podemos indicar una ruta que utilizaremos para recibir las notificaciones IPN de MercadoPago, y guardamos la preferencia. (Este tema no será cubierto en este post)

5. Retornar URL de redirección para web checkout

if (config('payment-methods.use_sandbox')) {
  return $preference->sandbox_init_point;
}

return $preference->init_point;

De acuerdo a los parámetros indicados dentro del archivo de configuración de medios de pago, la aplicación redirigirá al usuario al init point de producción o bien al init point del modo sandbox, desde allí este podrá realizar el pago con el métodos de pago que desee y luego retornará a nuestra aplicación al finalizar la operación.

Con esto ya tenemos lo suficiente para que el cliente sea redirigido a mercadopago, elija un medio de pago y posteriormente realice el pago del producto a adquirir.

Para cerrar

Este enfoque permite utilizar multiples medios de pago realizando una simple modificación al método generatePaymentGateway del controlador de Checkout y la adicción de una Interface para estandarizar el funcionamiento del método setupPaymentAndGetRedirectURL.

Además el enfoque que se utilizó nos permite encapsular todo el comportamiento del gateway en una simple clase, la cual puede mejorarse y hasta incluso, ser probada de manera aislada utilizando técnicas de mocking, entre otras.

Si te gustó este post y queres ayudarme invitame un café Invitame un café en cafecito.app


almost 4 years ago

Lucas Fiege