> Webhooks

Para finalizar el flujo de integración con nuestro Klap Checkout Flex o Klap Checkout Transparente, 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