Webhooks | Klap Developers

> Webhooks

Para finalizar el flujo de integración con nuestro Klap Checkout, Klap debe notificar el éxito o rechazo de la orden a través de los webhooks que fueron proporcionados por el comercio en la creación de orden. Para esto se deben cumplir las siguientes condiciones:

  • Deben ser URL válidas y públicas.
  • El endpoint se debe realizar mediante un protocolo "https".
  • Deben ser métodos "POST".
  • El comercio debe responder esta notificación a Klap con un código de estatus HTTP entre 200 y 299, además de un body en formato json, ejemplo: {"status":"ok"}.
  • La respuesta hacia KLAP se debe realizar en un tiempo máximo de 10 segundos.

En caso de que no se cumplan estas condiciones, Klap procederá a realizar la reversa automática de la transacción y el estado de la orden cambiará a refund.
 

Notifica al comercio cuando una orden ha sido pagada. En el payload se envía: {"order_id":"valor", "reference_id":"valor", "mc_code":"valor", "token_id":"valor", "card_type":"valor", "brand":"valor", "bin":"valor", "last_digits":"valor", "payment_method":"valor", "amount":"valor", "quotas_number":"valor", "quotas_type":"valor"}
  • order_id: Es el id de la orden entregado por Klap.
  • reference_id: Es el id de la orden entregado por el comercio.
  • mc_code: Es el identificador interno klap de la transacción. (Opcional)
  • token_id: Es el token asociado a una tarjeta.(Opcional, cuando se tokeniza la tarjeta)
  • card_type: Es el tipo de tarjeta.(Opcional, pago con tarjeta)
  • brand: Es la marca de la tarjeta.(Opcional, pago con tarjeta)
  • bin: Son los primeros 6 números de la tarjeta.(Opcional, pago con tarjeta)
  • last_digits: Son los últimos 4 dígitos de la tarjeta.(Opcional, pago con tarjeta)
  • quotas_number: Es el número de cuotas utilizadas en la transacción. (Opcional, pago con tarjeta crédito)
  • quotas_type: Es el tipo de cuotas utilizadas en la transacción. ISSUER / MERCHANT. (Opcional, pago con tarjeta crédito)
  • payment_method: Es el método de pago con que se realizó la transacción.
  • amount: Es el monto de la transacción.

Notifica al comercio cuando una orden ha sido rechazada / anulada. (En el payload solamente se envía {"order_id":"valor", "reference_id":"valor", "code":"valor", "message":"valor" })
  • order_id: Es el id de la orden entregado por Klap.
  • reference_id: Es el id de la orden entregado por el comercio.
  • code: Es el código del error.(Opcional)
  • message: Es el mensaje del error.(Opcional)

Ejemplo de implementación:

  // webhooks.js
  // https://micomercio/webhook_validation
  // https://micomercio/webhook_confirm
  // https://micomercio/webhook_reject
  
  const express = require('express');
  const bodyParser = require("body-parser");
  const app = express();
  const router = express.Router();
  const crypto = require('crypto');

  function validateApikey(headerApikey, order) {
    let ecommerceReferenceId = order.reference_id;
    let klapOrderId = order.order_id;
    console.log('ecommerceReferenceId: ', ecommerceReferenceId);
    console.log('klapOrderId: ', klapOrderId);
  
    //es tu apikey privada otorgada por Klap:
    let apikey = "SKSJKFH84JKDFNGDFGNDFGNN284895060"; 
    let key = ecommerceReferenceId + klapOrderId + apikey;
    
    // este hash es el que debes comparar con el que te llegará en el header "Apikey" 
    // mediante el request a tus webhooks   
    let hashApiKey = crypto.createHash("sha256").update(key).digest("hex");
    return hashApiKey == headerApikey;
  }

  router.post('/webhook_validation', function(req, res) {
    try {
      let order = req.body;
      let ecommerceReferenceId = order.reference_id;
      console.log('ecommerceReferenceId: ', ecommerceReferenceId);
  
      // hacer algo...
  
      res.status(200);
      res.json(order);
    } catch(error) {
      res.status(500);
      res.end('error');
    }
  });

  router.post('/webhook_confirm', function(req, res) {
    try {
      let order = req.body;
      let headerApikey = req.header('Apikey');
  
      if (!validateApikey(headerApikey, order)) {
        throw new Error('Error en autenticación');
      }
  
      // hacer algo...
  
      res.status(200);
      res.json(order);
    } catch(error) {
      res.status(500);
      res.end('error');
    }
  });
  
  router.post('/webhook_reject', function(req, res) {
    try {
      let order = req.body;  
      let headerApikey = req.header('Apikey');
  
      if (!validateApikey(headerApikey, order)) {
        throw new Error('Error en autenticación');
      }
  
      // hacer algo...
  
      res.status(200);
      res.json(order);
    } catch(error) {
      res.status(500);
      res.end('error');
    }
  });
  
  app.use(bodyParser.urlencoded({ extended: false }));
  app.use(bodyParser.json());
  app.use(router);
  app.listen(7000);
  
  /*
  <dependency>
     <groupId>commons-codec</groupId>
     <artifactId>commons-codec</artifactId>
     <version>1.11</version>
  </dependency>
  */
                    
  // WebhooksResource.java
  // https://micomercio/webhook_validation
  // https://micomercio/webhook_confirm
  // https://micomercio/webhook_reject
  
  package resources;
                    
  import org.springframework.http.*;
  import org.springframework.web.bind.annotation.*;
  import java.util.Map;
  import org.apache.commons.codec.digest.DigestUtils;
                    
  @RestController
  @RequestMapping("/")
  public class WebhooksResource {
                    
  private boolean validateApikey(String headerApikey, Map<String, Object> order) {
   String ecommerceReferenceId = String.valueOf(order.get("reference_id"));
   String klapOrderId = String.valueOf(order.get("order_id"));
   System.out.println("ecommerceReferenceId: " + ecommerceReferenceId);
   System.out.println("klapOrderId: " + klapOrderId);
                        
  //es tu apikey privada otorgada por Klap:
   String apikey = "SKSJKFH84JKDFNGDFGNDFGNN284895060"; 
   String key = ecommerceReferenceId + klapOrderId + apikey;
                        
  //este hash es el que debes comparar con el que te llegará en el 
  //header "Apikey" mediante el request a tus webhooks:
   String hashApiKey = DigestUtils.sha256Hex(key);
   return hashApiKey.equals(headerApikey);
  }
                    
  @RequestMapping(path  = "/webhook_validation", method = RequestMethod.POST,
   consumes = "application/json", produces = "application/json")
  public ResponseEntity<?> webhookValidation(@RequestBody(required=true) Map<String, Object> order) {
   try {
     String ecommerceReferenceId = String.valueOf(order.get("reference_id"));
     System.out.println("ecommerceReferenceId: " + ecommerceReferenceId);
                    
    // hacer algo...
                    
     return new ResponseEntity<>(order, HttpStatus.OK);
   } catch(Exception ex) {
     return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR);
   }
  }
                    
  @RequestMapping(path = "/webhook_confirm", method = RequestMethod.POST,
   consumes = "application/json", produces = "application/json")
  public ResponseEntity<?> webhookConfirm(@RequestBody(required=true) Map<String, Object> order,
   @RequestHeader(value="Apikey", required = true) String headerApikey) {
    try {     
                    
      if (!validateApikey(headerApikey, order)) {
       throw new Exception("Error en autenticación");
      }
                    
      // hacer algo...
                    
      return new ResponseEntity<>(order, HttpStatus.OK);
    } catch(Exception ex) {
      return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }
                    
  @RequestMapping(path = "/webhook_reject", method = RequestMethod.POST,
   consumes = "application/json", produces = "application/json")
  public ResponseEntity<?> webhookReject(@RequestBody(required=true) Map<String, Object> order,
   @RequestHeader(value="Apikey", required = true) String headerApikey) {
    try {     
                    
      if (!validateApikey(headerApikey, order)) {
       throw new Exception("Error en autenticación");
      }
                    
      // hacer algo...
                    
      return new ResponseEntity<>(order, HttpStatus.OK);
    } catch(Exception ex) {
      return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR);
    }
  }

 }
  
  
  <?php 
  // webhooks.php
  // https://micomercio/webhwebhook=validation
  // https://micomercio/webhwebhook=confirm
  // https://micomercio/webhwebhook=reject
                
  function getHeader($headers, $name) {
    foreach ($headers as $hname => $hvalue) {
      if (strtolower($hname) == strtolower($name)){
        return $hvalue;
      }
    }
    return null;
  }
  
  function validateApikey($headerApikey, $decoded) {

    $ecommerceReferenceId = $decoded['reference_id'];  
    $klapOrderId = $decoded['order_id'];
    error_log(print_r('ecommerceReferenceId: ' . $ecommerceReferenceId, true));
    error_log(print_r('klapOrderId: ' . $klapOrderId, true));

    # es tu apikey privada otorgada por Klap:
    $apikey = "SKSJKFH84JKDFNGDFGNDFGNN284895060"; 
    $key = $ecommerceReferenceId . $klapOrderId . $apikey;
    
    // este hash es el que debes comparar con el que te llegará 
    // en el header "Apikey" el request a tus webhooks:
    $hashApiKey = hash('sha256', $key);
    return $hashApiKey == $headerApikey;
  }
  
  try {
  
    if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){
       throw new NotFoundException('Request method must be POST!');
    }
  
    $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ';
    if(strcasecmp($contentType, 'application/json') != 0){
      throw new NotFoundException('Content type must be:application/json');
    }
  
    //Recibe los datos post raw
    $json = trim(file_get_contents("php://input"));
  
    //decodifica el json
    $decoded = json_decode($json, true);
  
    //si json_decode falla, el json es inválido
    if(!is_array($decoded)){
      throw new BadRequestException('Received content contained invalid JSON!');
    }
  
    $webhook = $_GET['webhook'];
    error_log(print_r('webhook: ' . $webhook, true));
                    
  
    if(strcasecmp($webhook, 'validation') == 0) {

    // hacer algo...
  
    } else {
        $headerApikey = $this->getHeader(apache_request_headers(), 'Apikey');
        if (!$this->validateApikey($headerApikey, $decoded)) {
          throw new UnauthorizedException('Error en autenticación');
        }
        if(strcasecmp($webhook, 'confirm') == 0) {     
  
            // hacer algo confirm...
  
        } else if(strcasecmp($webhook, 'reject') == 0) {
      
  
            // hacer algo para reject...
        }

    }

    //retorna el json
    http_response_code(200);
    echo $json;

  } catch (Exception $e) {
    error_log(print_r('Excepción capturada: ',  $e->getMessage()));
    http_response_code(500);
    echo "error";
  }
                
  # webhooks.rb
  # https://micomercio/webhook_validation
  # https://micomercio/webhook_confirm
  # https://micomercio/webhook_reject

  require 'sinatra'
  require 'json'
  require 'digest'

  set :port, 7000
  set :bind, '0.0.0.0'

  def validateApikey(headerApikey, order)
    ecommerceReferenceId = order['reference_id']
    klapOrderId = order['order_id']
    puts "ecommerceReferenceId: #{ecommerceReferenceId}"
    puts "klapOrderId: #{klapOrderId}"
    
    #es tu apikey privada otorgada por Klap:
    apikey = "SKSJKFH84JKDFNGDFGNDFGNN284895060" 
    key = ecommerceReferenceId + klapOrderId + apikey
    
    #este hash es el que debes comparar con el que te llegará en el 
    #header "Apikey" mediante el request a tus webhooks:
    hashApiKey = Digest::SHA256.hexdigest(key)
    return hashApiKey ==  headerApikey
  end

  post '/webhook_validation' do
    begin
      request.body.rewind
      order = JSON.parse request.body.read
      ecommerceReferenceId = order['reference_id']
      puts "ecommerceReferenceId: #{ecommerceReferenceId}"

      # hacer algo...

      content_type :json
      halt 200, order.to_json
    rescue StandardError => error
      halt 500, 'error'
    end
  end

  post '/webhook_confirm' do
    begin
      request.body.rewind
      order = JSON.parse request.body.read

      headerApikey = request.env["Apikey"]

      if (!validateApikey(headerApikey, order))
       raise "Error en autenticación"
      end

      # hacer algo...

      content_type :json
      halt 200, order.to_json
    rescue StandardError => error
      halt 500, 'error'
    end
  end

  post '/webhook_reject' do
    begin
      equest.body.rewind
      order = JSON.parse request.body.read
      headerApikey = request.env["Apikey"]

      if (!validateApikey(headerApikey, order))
      raise "Error en autenticación"
      end

      # hacer algo...

      content_type :json
      halt 200, order.to_json
    rescue StandardError => error
      halt 500, 'error'
    end
  end