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