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