Webhooks


Los webhooks envían notificaciones instantáneas a tu aplicación cuando ocurre un evento determinado, por ejemplo la aprobación, expiración o creación de un pago. Los mensajes son enviados en formato JSON desde ComproPago (vía POST) hacia una o más URLs que tu designes. Dentro de los usos más comunes estan los siguientes:

  • Cambiar el estado de una orden de pago en tu base de datos
  • Confirmar a tu cliente que su pago fue aprobado
  • Mandar un email a tu proveedor para informarle que requieres un nuevo producto

Requerimeintos


Para poder recibir los webhooks en tu aplicación o sitio web, debes cubrir con ciertos requisitos para asegurar que la recepción de estos sea la más óptima y segura posible. Los requisitos son los siguientes:

  • Timeout: Tu sitio o aplicación debe de responder a los webhooks en no mas de 30 segundos
  • Respuesta: El cuerpo de la respuesta que tendrás que devolver al webhook debera ser un JSON con el siguiente formato:
    {
      "status": "success",       /* success al finalizar con exito, error en caso contrario */
      "message": "asdasd",       /* mesaje opcional de retorno */
      "short_id": "asd123",      /* short_id enviado en el json del webhook */
      "reference": "asdasdasd"   /* identificador de la peticion (creado por ti) */
    }
  • Status Code: El status code esperado para una respuesta con el formato anteriór debe de estar en el rango de 200 a 299. En caso de no devolver ningún payload en la respuesta, se esperará un status code 200.

Tipos de notificaciones (eventos)


Cada vez que alguno de los siguientes eventos ocurra, mandaremos una notificación a la URL que hayas dado de alta en la sección panel/webhooks.

Tipo de evento Significado
charge.pending El pago está en espera de ser confirmado.
charge.success El pago ha sido confirmado exitosamente.
charge.expired El pago ha expirado.

Modo de pruebas vs modo activo


La herramienta está disponible de manera independiente tanto en el modo de pruebas como en el modo activo; es decir, las llamadas que realices en el modo de pruebas enviarán una notificación a la(s) URL(s) dada(s) de alta en el ambiente de pruebas y viceversa.

Una vez que tu aplicación esté en producción, verifica que la(s) URL(s) donde recibes las notificaciones estén dadas de alta en el modo activo.

Reintento de Notificaciones automáticas


Hemos detectado que en algunas ocasiones no se cambia el status de la orden en la tienda, esto se debe a la latencia de algunos servidores o a un problema de comunicación entre ComproPago y la tienda.

Para esto creamos el reintento de notificaciones automáticas, la funcionalidad es simple, se reintentarán siempre y cuando no se reciba como respuesta un status 200.

La activación es muy fácil, ya que tenemos dado de alta un webhook solo tenemos que activar la opción de reintentar.

Configurar una URL


El primer paso para recibir las notificaciones es configurar una URL que servirá como receptor de webhooks.

Debes configurar tu aplicación para recibir una llamada tipo POST en tu servidor, los siguientes extractos de código pueden ayudarte:


<?php
require_once 'vendor/autoload.php';

use CompropagoSdk\Factory\Factory;
use CompropagoSdk\Client;

$request = @file_get_contents('php://input');
header('Content-Type: application/json');

if(!$resp_webhook = Factory::getInstanceOf('CpOrderInfo', $request)){
    echo json_encode([
      "status" => "error",
      "message" => "invalid request",
      "short_id" => null,
      "reference" => null
    ]);
}

$publickey     = "pk_live_xxxxxxxxxxxxxxxxxxx";
$privatekey    = "sk_live_xxxxxxxxxxxxxxxxxxx";
$live          = true; // si es modo pruebas cambiar por 'false'

try{
    $client = new Client($publickey, $privatekey, $live );

    if($resp_webhook->id == "ch_00000-000-0000-000000"){
        echo json_encode([
          "status" => "success",
          "message" => "test success",
          "short_id" => $resp_webhook->short_id,
          "reference" => null
        ]);
    }

    $response = $client->api->verifyOrder($resp_webhook->id);

    switch ($response->type){
        case 'charge.success':
            // TODO: Actions on success payment
            break;
        case 'charge.pending':
            // TODO: Actions on pending payment
            break;
        case 'charge.expired':
            // TODO: Actions on expired payment
            break;
        default:
            echo json_encode([
              "status" => "error",
              "message" => "invalid request type",
              "short_id" => $response->short_id,
              "reference" => null
            ]);
    }

    echo json_encode([
      "status" => "success",
      "message" => "OK",
      "short_id" => $response->short_id,
      "reference" => 'internal-1234'
    ]);
}catch (Exception $e) {
    echo json_encode([
      "status" => "error",
      "message" => $e->getMessage(),
      "short_id" => $resp_webhook->short_id,
      "reference" => null
    ]);
}
 # Using Sinatra Framework

require 'sinatra'
require 'json'
require 'compropago_sdk'

post '/webhook' do
  content_type :json

  data = request.body.read

  resp_webhook = Factory::get_instance_of('CpOrderInfo', data)

  if resp_webhook.nil?
    return {
      status: 'error',
      message: 'invalid request',
      short_id: nil,
      reference: nil
    }.to_json
  end

  if resp_webhook.id == 'ch_00000-000-0000-000000'
    return {
      status: 'success',
      message: 'test success',
      short_id: resp_webhook.short_id,
      reference: nil
    }.to_json
  end

  publickey = 'pk_test_638e8b14112423a086'
  privatekey = 'sk_test_9c95e149614142822f'
  mode = false

  begin
    client = Client.new(publickey, privatekey, mode)

    verified = client.api.verify_order(resp_webhook.id)

    case verified.type
      when 'charge.success'
        # TODO: Actions for success payments
        return 'Success payment'
      when 'charge.pending'
        # TODO: Actions for pending payments
        return 'Pending payment'
      when 'charge.expired'
        # TODO: Actions for expired payments
        return 'Expired payment'
      else
        return {
          status: 'error',
          message: 'invalid request type',
          short_id: verified.short_id,
          reference: nil
        }.to_json
    end

    return {
      status: 'success',
      message: 'OK',
      short_id: verified.short_id,
      reference: 'internal-123'
    }.to_json
  rescue => ex
    return {
      status: 'error',
      message: ex.message,
      short_id: resp_webhook.short_id,
      reference: nil
    }.to_json
  end
end
# Using CherryPy Framework

import json
import cherrypy
from compropago.client import Client
from compropago.factory.factory import Factory

class HelloWorld(object):
    @cherrypy.expose
    def webhook(self):
        if 'Content-Length' in cherrypy.request.headers:
            cl = cherrypy.request.headers['Content-Length']
            rawbody = cherrypy.request.body.read(int(cl)).decode("iso-8859-1")
        else:
            return json.dumps({
              "status": "error",
              "message": "invalid request",
              "short_id": None,
              "reference": None
            })

        resp_webhook = Factory.get_instance_of('CpOrderInfo', rawbody)

        if len(resp_webhook.id) == 0:
            return json.dumps({
              "status": "error",
              "message": "invalid request",
              "short_id": None,
              "reference": None
            })

        if resp_webhook.id == 'ch_00000-000-0000-000000':
            return json.dumps({
              "status": "success",
              "message": "test success",
              "short_id": resp_webhook.short_id,
              "reference": None
            })

        publickey = 'pk_test_638e8b14112423a086'
        privatekey = 'sk_test_9c95e149614142822f'
        mode = False

        try:
            client = Client(publickey, privatekey, mode)

            verified = client.api.verify_order(resp_webhook.id)

            if verified.type == 'charge.success':
                # TODO: Actions for success payments
                return 'Success payment'
            elif verified.type == 'charge.pending':
                # TODO: Actions for pending payments
                return 'Pending payment'
            elif verified.type == 'charge.expired':
                # TODO: Actions for expired payments
                return 'Expired payment'
            else:
                return json.dumps({
                  "status": "error",
                  "message": "invalid request type",
                  "short_id": verified.short_id,
                  "reference": None
                })

            return json.dumps({
              "status": "success",
              "message": "OK",
              "short_id": verified.short_id,
              "reference": "internal-123"
            })
        except Exception as e:
            return json.dumps({
              "status": "success",
              "message": str(e),
              "short_id": resp_webhook.short_id,
              "reference": None
            })


cherrypy.quickstart(HelloWorld())
using System;
using System.IO;
using System.Web.Mvc;
using CompropagoSdk;
using CompropagoSdk.Factory;
using Newtonsoft.Json.Linq;

namespace webhookcs.Controllers
{
    public class WebhookController : Controller
    {
        [HttpPost]
        public ActionResult Index()
        {
            JObject response = new JObject();

            /**
              * Optener el payload enviado a la aplicacion con la informacion del pago
              */
            Stream req = Request.InputStream;
            req.Seek(0, SeekOrigin.Begin);
            string json = new StreamReader(req).ReadToEnd();

            /**
              * Convertir el payload string en un objeto del SDK para su manejo
              */
            var request = Factory.CpOrderInfo(json);

            /**
              * Validar que la información de la orden sea valida
              */
            if (request.id == null || request.id == "")
            {
                response["status"] = "error";
                response["message"] = "invalid request";
                response["short_id"] = null;
                response["reference"] = null;
                return Content(response.ToString(), "application/json");
            }

            /**
              * Llaves de la cuenta de compropago asi como el modo del panel
              */
            var publicKey = "pk_test_638e8b14112423a086";
            var privateKey = "sk_test_9c95e149614142822f";
            var mode = false;

            try 
            {
                /**
                  * Instanciar el cliente del SDK para posteriores validaciones
                  */
                var client = new Client(publicKey, privateKey, mode);

                /**
                  * Validar si la peticion es una peticion de prueba
                  */
                if (request.id == "ch_00000-000-0000-000000")
                {
                    response["status"] = "success";
                    response["message"] = "test success";
                    response["short_id"] = request.short_id;
                    response["reference"] = null;
                    return Content(response.ToString(), "application/json");
                }

                /**
                  * Verificacion de la autenticidad de la información
                  */
                var verifyed = client.Api.VerifyOrder(request.id);

                switch(verifyed.type)
                {
                    case "charge.success":
                        // TODO: actions for success payments
                        break;
                    case "charge.pending":
                        // TODO: actions for pending payments
                        break;
                    case "charge.expired":
                        // TODO: actions for expired payments
                        break;
                    default:
                        response["status"] = "error";
                        response["message"] = "invalid request type";
                        response["short_id"] = verifyed.short_id;
                        response["reference"] = null;
                        return Content(response.ToString(), "application/json");
                }

                response["status"] = "success";
                response["message"] = "OK";
                response["short_id"] = verifyed.short_id;
                response["reference"] = "internal-123";
                return Content(response.ToString(), "application/json");
            } 
            catch (Exception e)
            {
                response["status"] = "error";
                response["message"] = e.Message;
                response["short_id"] = verifyed.short_id;
                response["reference"] = "internal-123";
                return Content(response.ToString(), "application/json");
            }
        }
    }
}

Dar de alta una URL

Una vez configurada la URL que servirá como receptor de webhooks, el siguiente paso es darla de alta en la sección panel/webhooks:

Webhook-panel

Sabrás que la URL está operando satisfactoriamente si la solicitud enviada mediante el botón Probar, genera una respuesta similar a la siguiente:

Webhook-response

Ejemplo de notificación

charge.pending - nuevo pago generado en espera de ser confirmado

{        
  "id": "ch_fe92a1a5-abec-49e3-877c-5024c1464dc3",
  "type": "charge.pending",
  "object": "charge",
  "created_at": 1424024955774,
  "accepted_at": null,
  "expires_at": 1424024955774
  "paid": true,
  "amount": 150.00,
  "livemode": true,
  "currency": "mxn",
  "refunded": false,
  "fee": 7.50,
  "fee_details": {
    "amount": 7.50,
    "currency": "mxn",
    "type":"compropago_fee",
    "application": null,
    "amount_refunded": 0,
    "tax": 0.94
  },
  "order_info": {
    "order_id": "FBIPC5",
    "order_price": 150.00,
    "order_name": "Nokia 5520",
    "payment_method": "cash",
    "store": "SEVEN_ELEVEN",
    "country": "MX",
    "image_url": null,
    "success_url": null,
    "failed_url": null,
    "exchange": {
      "rate": 1,
      "request": 1485224234, 
      "origin_amount": "150.0",         
      "final_amount": "150.0",                    
      "origin_currency": "MXN",
      "final_currency": "MXN",
      "exchange_id": null
    }
  },
  "customer": {
    "customer_name": "Alejandra Leyva",
    "customer_email": "noreply@compropago.com",
    "customer_phone": "2221515801"
  },
  "api_version": "1.1"
}

ComproPago espera que una vez que recibas la notificación, tu servidor confirme de recibido enviando una respuesta HTTP 200 de regreso.

Seguridad

Si la seguridad es una de tus prioridades, si desas confirmar que las notificaciones envíadas a tu servidor provienen de ComproPago y no de algún servidor desconocido, te sugerimos lo siguiente:

  • Utilizar el id de pago incluido en el objeto JSON del webhook y obtener la información restante a través del siguiente recurso del API: /v1/charges/{payment_id}.

Además, es importante que uses el protocolo de seguridad https en todas tus comunicaciones con el servidor de ComproPago.


Otras secciones que podrían ser de tu interes