Migración desde Fingerprint
Ventrata está sustituyendo la integración anterior basada en Fingerprint por Google reCAPTCHA Enterprise. Los Checkouts personalizados existentes deben eliminar su integración con Fingerprint y adoptar las acciones, ubicaciones de los tokens y el comportamiento de reintento de reCAPTCHA descritos en esta guía.
Durante el período de migración, algunos campos heredados de Fingerprint y fraudAssessment pueden seguir apareciendo en las respuestas de la API para mantener la compatibilidad con clientes antiguos. Las nuevas integraciones deben ignorar estos campos y no deben utilizarlos para determinar si la evaluación de fraude se ha completado.
Google reCAPTCHA Enterprise protege los flujos de creación de checkout y búsqueda de identidad frente a abusos automatizados y proporciona evaluaciones de Transaction Defense para los pagos con tarjeta.
Ventrata gestiona la integración con Google del lado del servidor. Tu Checkout personalizado es responsable de generar los tokens en el navegador e incluirlos en las solicitudes de API correspondientes.
Descripción general de la integración
Obtén la clave pública del sitio (site key) de reCAPTCHA desde Ventrata.
Carga el script de reCAPTCHA Enterprise en el navegador.
Genera un token inmediatamente antes de cada interacción protegida.
Utiliza la acción esperada por Ventrata.
Coloca el token en el objeto de solicitud correspondiente.
Reintenta la solicitud con un token nuevo cuando
errorCodeseaRECAPTCHA_REQUIRED.
📒 NOTA
Fingerprint ya no es necesario. No instales el agente de Fingerprint, no crees linked IDs, no consultes los campos heredados de recibos de Fingerprint ni envíes datos en el objeto heredado fraudAssessment.
Requisitos previos:
Utiliza un token de Checkout de Ventrata.
Este token tiene el mismo valor que tu Checkout ID y está diseñado para uso público en el lado del cliente. Restringe el acceso a los endpoints disponibles para ese Checkout.
const API_URL = "https://checkout-api.ventrata.com/octo";
const CHECKOUT_TOKEN = "YOUR_CHECKOUT_TOKEN";Incluye la capacidad
ventrata/checkouty cualquier otra capacidad OCTO requerida por la solicitud.Authorization: Bearer YOUR_CHECKOUT_TOKEN
Octo-Capabilities: ventrata/checkoutIncluye las credenciales del navegador para que se conserve la cookie de sesión de Ventrata.
const commonFetchOptions = {
credentials: "include",
headers: {
Authorization: `Bearer ${CHECKOUT_TOKEN}`,
"Octo-Capabilities": "ventrata/checkout",
},
};
📒 NOTA
Los ejemplos utilizan JavaScript e ilustran la estructura de las solicitudes. Adapta el manejo de errores y los service wrappers a tu aplicación.
Paso 1: Obtener la clave del sitio de reCAPTCHA
Solicita la configuración de Checkout y lee el valor de recaptchaEnterpriseSiteKey de la respuesta.
Solicita la configuración de Checkout y lee el valor de recaptchaEnterpriseSiteKey de la respuesta.
const response = await fetch(`${API_URL}/ventrata/checkout/config`, { ...commonFetchOptions,
});
if (!response.ok) {
throw new Error("Unable to load the Ventrata checkout configuration");
}
const checkoutConfig = await response.json();
const recaptchaSiteKey = checkoutConfig.recaptchaEnterpriseSiteKey;
if (!recaptchaSiteKey) {
throw new Error("The checkout does not provide a reCAPTCHA site key");
}📒 NOTA
La site key es pública y puede utilizarse de forma segura en el navegador. Nunca incluyas un secreto de Google, credenciales de una cuenta de servicio ni una clave de API en el código del lado del cliente. Ventrata crea las evaluaciones del lado del servidor.
‼️ IMPORTANTE
Durante el período de migración, la respuesta también contiene fingerprintPublicKey. Se trata de un marcador de compatibilidad y las nuevas integraciones deben ignorarlo.
Paso 2: Cargar reCAPTCHA Enterprise
Carga el script de reCAPTCHA Enterprise basado en puntuación una sola vez, tan pronto como la configuración proporcione la site key. Incluye la site key en el parámetro render y espera a que grecaptcha.enterprise.ready() se complete antes de llamar a execute(). Consulta la documentación de Google sobre la integración de páginas web con claves basadas en puntuación.
Carga el script de reCAPTCHA Enterprise basado en puntuación una sola vez, tan pronto como la configuración proporcione la site key. Incluye la site key en el parámetro render y espera a que grecaptcha.enterprise.ready() se complete antes de llamar a execute(). Consulta la documentación de Google sobre la integración de páginas web con claves basadas en puntuación.
Si cargas el script dinámicamente, ten en cuenta que el evento de carga del script y la disponibilidad de grecaptcha.enterprise pueden producirse en momentos distintos. Un intento posterior debe poder reutilizar un script que haya terminado de cargarse después de que un intento anterior haya agotado el tiempo de espera.
El siguiente helper utiliza www.recaptcha.net, que Google admite como alternativa para entornos que no pueden acceder a www.google.com. Consulta las Preguntas frecuentes de reCAPTCHA Enterprise.
El siguiente helper utiliza www.recaptcha.net, que Google admite como alternativa para entornos que no pueden acceder a www.google.com. Consulta las Preguntas frecuentes de reCAPTCHA Enterprise.
Este helper trata los eventos load y error como rutas rápidas, comprueba periódicamente la disponibilidad hasta que se alcanza el tiempo de espera global y conserva un script cuya carga sea lenta para que un intento posterior pueda reutilizarlo.
const RECAPTCHA_SCRIPT_ID = "ventrata-recaptcha-enterprise-script";
const RECAPTCHA_LOAD_TIMEOUT_MS = 15_000;
const RECAPTCHA_READINESS_POLL_MS = 250;
const RECAPTCHA_EXECUTE_RETRY_DELAY_MS = 300;
const RECAPTCHA_EXECUTE_ATTEMPTS = 3;
const delay = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));
let recaptchaReadyPromise;
function getErrorMessage(error) {
return error instanceof Error ? error.message : String(error);
}
function reportRecaptchaFailure(stage, error, context = {}) {
const event = {
stage,
reason: getErrorMessage(error),
...context,
};
// Replace this with the integration's central browser telemetry.
// Never include the generated token, checkout token, or customer data.
console.warn("[reCAPTCHA]", event);
}
function recaptchaEnterpriseIsAvailable() {
return (
typeof window.grecaptcha?.enterprise?.ready === "function" &&
typeof window.grecaptcha?.enterprise?.execute === "function"
);
}
function loadRecaptchaEnterprise(siteKey, nonce) {
if (recaptchaEnterpriseIsAvailable()) {
return new Promise((resolve) =>
window.grecaptcha.enterprise.ready(resolve),
);
}
if (recaptchaReadyPromise) {
return recaptchaReadyPromise;
}
recaptchaReadyPromise = new Promise((resolve, reject) => {
let script = document.getElementById(RECAPTCHA_SCRIPT_ID);
let settled = false;
let timeout;
let readinessPoll;
const stopWaiting = () => {
clearTimeout(timeout);
clearInterval(readinessPoll);
};
const fail = (error, { keepScript = false } = {}) => {
if (settled) {
return;
}
settled = true;
stopWaiting();
if (!keepScript) {
script?.remove();
}
recaptchaReadyPromise = undefined;
reportRecaptchaFailure("recaptcha.loader_failed", error);
reject(error);
};
const adoptEnterprise = () => {
if (settled || !recaptchaEnterpriseIsAvailable()) {
return;
}
window.grecaptcha.enterprise.ready(() => {
if (settled) {
return;
}
settled = true;
stopWaiting();
recaptchaReadyPromise = undefined;
resolve();
});
};
timeout = setTimeout(
() =>
fail(new Error("reCAPTCHA Enterprise loading timed out"), {
// The browser might still be downloading this script. Leave it in
// place so a later attempt can observe or adopt it.
keepScript: true,
}),
RECAPTCHA_LOAD_TIMEOUT_MS,
);
readinessPoll = setInterval(
adoptEnterprise,
RECAPTCHA_READINESS_POLL_MS,
);
if (!script) {
script = document.createElement("script");
script.id = RECAPTCHA_SCRIPT_ID;
script.src = `https://www.recaptcha.net/recaptcha/enterprise.js?render=${encodeURIComponent(siteKey)}`;
script.async = true;
script.defer = true;
if (nonce) {
script.nonce = nonce;
}
}
script.addEventListener("load", adoptEnterprise, { once: true });
script.addEventListener(
"error",
() => fail(new Error("Unable to load reCAPTCHA Enterprise")),
{ once: true },
);
if (!script.isConnected) {
document.head.appendChild(script);
}
// Covers Enterprise loaded by another component before these listeners
// were attached.
adoptEnterprise();
});
return recaptchaReadyPromise;
}
Paso 3: Generar los tokens justo antes de usarlos
Genera un token inmediatamente antes de la interacción que vaya a proteger. No generes tokens continuamente en segundo plano ni reutilices un mismo token para solicitudes independientes.
Genera un token inmediatamente antes de la interacción que vaya a proteger. No generes tokens continuamente en segundo plano ni reutilices un mismo token para solicitudes independientes.
async function createRecaptchaToken(action) {
await loadRecaptchaEnterprise(recaptchaSiteKey);
let lastError; for (
let attempt = 1;
attempt <= RECAPTCHA_EXECUTE_ATTEMPTS;
attempt++
) {
try {
const token = await window.grecaptcha.enterprise.execute(
recaptchaSiteKey,
{ action },
);
if (typeof token !== "string" || token.length === 0) {
throw new Error("reCAPTCHA Enterprise returned an empty token");
}
return token;
} catch (error) {
lastError = error;
if (attempt < RECAPTCHA_EXECUTE_ATTEMPTS) {
await delay(RECAPTCHA_EXECUTE_RETRY_DELAY_MS);
}
}
}
reportRecaptchaFailure("recaptcha.token_failed", lastError, { action });
throw lastError;
}📒 NOTE
Estos tres intentos de generación de tokens se realizan antes de la solicitud a la API de Checkout. Son independientes y no aumentan el límite de tres solicitudes totales descrito en el Paso 5.
Los tokens caducan a los dos minutos y, normalmente, solo pueden evaluarse una vez. Genera un nuevo token cart_add o purchase para cada solicitud que lo requiera. El flujo login dispone de una excepción limitada de reutilización que se describe más adelante.
📗 CONSEJO
Para obtener información sobre la duración y el uso de los tokens, consulta la documentación de Google sobre la obtención de tokens de reCAPTCHA.
Paso 4: Utilizar la acción y la ubicación de la carga útil correctas
Los nombres de las acciones están en minúsculas y deben coincidir exactamente con el valor esperado por Ventrata. Consulta la documentación de Google sobre los nombres de las acciones.
Los nombres de las acciones están en minúsculas y deben coincidir exactamente con el valor esperado por Ventrata. Consulta la documentación de Google sobre los nombres de las acciones.
Acción | Se utiliza para | Ubicación en la solicitud |
| Crear un nuevo pedido, reserva, compra o regalo |
|
| Todas las solicitudes que contienen |
|
| Flujos de inicio de sesión de membresías, check-in, concierge y búsqueda de identidad |
|
Crear un pedido, una reserva, una compra o un regalo
Genera un nuevo token cart_add para cada solicitud de creación y colócalo en el objeto recaptchaEnterprise de nivel superior.
{
"currency": "USD",
"recaptchaEnterprise": {
"token": "CART_ADD_TOKEN"
}
}📒 NOTA
Esto se aplica a las solicitudes POST que crean pedidos, reservas, compras y regalos. No añadas cart_add a solicitudes de actualización o de nueva reserva de registros existentes. Si repites una solicitud de creación tras un error, genera un nuevo token.
Enviar un pago con tarjeta
Siempre que una solicitud contenga cardPayment, genera un nuevo token purchase para esa solicitud e inclúyelo directamente en cardPayment.recaptchaEnterprise. Esto también se aplica cuando se repite una solicitud después de una actualización del gateway o de una respuesta intermedia.
📒 NOTE
No actualices el token por separado. Genera el token purchase como parte de la propia solicitud de pago. No realices una actualización adicional del pedido únicamente para actualizar el token.
{
"currency": "USD",
"cardPayment": {
"gateway": "adyen",
"adyen": {
"sessionId": "ADYEN_SESSION_ID",
"sessionResult": "ADYEN_SESSION_RESULT"
},
"recaptchaEnterprise": {
"token": "PURCHASE_TOKEN"
}
}
}Utiliza
purchasepara las actualizaciones o confirmaciones de pedidos y reservas que contengancardPayment.Utilízalo tanto en Checkout como en los flujos de pago de Manage My Booking.
Utilízalo con todos los gateways de pago con tarjeta compatibles.
📒 NOTA
Ventrata requiere la acción purchase para las solicitudes que contienen cardPayment. Para obtener información adicional sobre Transaction Defense, consulta la documentación correspondiente de Google.
Crear un pedido con pago con tarjeta
Una solicitud de creación que también incluya cardPayment necesita dos tokens diferentes:
cart_adden el nivel superior;purchasedentro decardPayment.
No es posible utilizar un mismo token para ambas acciones.
{
"currency": "USD",
"recaptchaEnterprise": {
"token": "CART_ADD_TOKEN"
},
"cardPayment": {
"gateway": "adyen",
"recaptchaEnterprise": {
"token": "PURCHASE_TOKEN"
}
}
}
Inicio de sesión y búsqueda de identidad
Genera un token login para las solicitudes de inicio de sesión de membresías, check-in, concierge y búsqueda de identidad.
{
"email": "[email protected]",
"recaptchaEnterprise": {
"token": "LOGIN_TOKEN"
}
}
Dentro de un mismo flujo de identidad, puedes conservar el token para las solicitudes posteriores que utilicen la misma dirección de correo electrónico normalizada, número de teléfono móvil o referencia de reserva.
Descarta el token y genera uno nuevo cuando:
Ventrata devuelva
RECAPTCHA_REQUIRED.El cliente cambie la dirección de correo electrónico, el número de teléfono móvil, el país o la referencia de la reserva.
Se inicie un nuevo flujo de inicio de sesión o de búsqueda.
Paso 5: Gestionar RECAPTCHA_REQUIRED
Una solicitud protegida de creación o búsqueda de identidad puede devolver RECAPTCHA_REQUIRED cuando el token falta, ha caducado, ya ha sido evaluado o está afectado por un error del navegador que permite reintentos.
Una solicitud protegida de creación o búsqueda de identidad puede devolver RECAPTCHA_REQUIRED cuando el token falta, ha caducado, ya ha sido evaluado o está afectado por un error del navegador que permite reintentos.
{
"error": "BAD_REQUEST",
"errorMessage": "A new Google reCAPTCHA token is required",
"errorCode": "RECAPTCHA_REQUIRED",
"reason": "MISSING"
}Motivo | Significado |
| La solicitud omitió el token o envió |
| El token ya no era válido. |
| El token ya había sido evaluado. |
| Google informó de un error del navegador que permite reintentos. |
Comprueba únicamente errorCode para decidir si debes reintentar la solicitud. En todas las respuestas RECAPTCHA_REQUIRED, descarta el token anterior y vuelve a intentarlo con un token recién generado. Si la generación del token falla, envía null para que Ventrata devuelva el error explícito y espera dos segundos antes de volver a intentarlo. Deja de reintentar después de un máximo de tres solicitudes a la API de Checkout.
function isRecaptchaRequired(data) {
return data?.errorCode === "RECAPTCHA_REQUIRED";
}
async function withRecaptchaRetry(action, sendRequest) {
const maximumAttempts = 3;
for (let attempt = 1; attempt <= maximumAttempts; attempt++) {
let token = null;
try {
token = await createRecaptchaToken(action);
} catch (error) {
// Sending null lets Ventrata return the explicit retryable error.
reportRecaptchaFailure("recaptcha.request_token_unavailable", error, {
action,
requestAttempt: attempt,
});
}
const response = await sendRequest(token);
const data = await response.json();
if (response.ok) {
return data;
}
if (!isRecaptchaRequired(data) || attempt === maximumAttempts) {
const error = new Error(
data.errorMessage || data.error || "The request failed",
);
error.response = data;
throw error;
}
if (token === null) {
await delay(2_000);
}
}
}
El siguiente ejemplo aplica este comportamiento de reintento a una solicitud protegida de creación de pedidos. En cada intento se genera un nuevo token cart_add y se coloca en el objeto recaptchaEnterprise de nivel superior.
const order = await withRecaptchaRetry("cart_add", (token) =>
fetch(`${API_URL}/orders`, {
...commonFetchOptions,
method: "POST",
headers: {
...commonFetchOptions.headers,
"Content-Type": "application/json",
"Octo-Capabilities": "ventrata/checkout,octo/cart",
},
body: JSON.stringify({
currency: "USD",
recaptchaEnterprise: { token },
}),
}),
);📒 NOTA
Aplica el mismo comportamiento de reintento al resto de solicitudes protegidas de creación y búsqueda de identidad, utilizando la acción y la ubicación correspondientes de la carga útil. Si una solicitud repetida contiene cardPayment, genera también un nuevo token purchase anidado para ese intento.
No reintentes automáticamente en los siguientes casos:
VERIFICATION_FAILED: la evaluación falló por un motivo que no admite reintentos, como una puntuación baja o una acción inesperada.PAYMENT_DECLINE_LIMIT_EXCEEDED: el pedido ha superado el número máximo permitido de intentos de pago rechazados.
Política de seguridad de contenido (Content Security Policy)
Google recomienda utilizar un CSP nonce. Aplícalo al script de Enterprise. reCAPTCHA también admite strict-dynamic en los navegadores compatibles. Consulta las Preguntas frecuentes de reCAPTCHA Enterprise y la documentación de Google sobre CSP.
Google recomienda utilizar un CSP nonce. Aplícalo al script de Enterprise. reCAPTCHA también admite strict-dynamic en los navegadores compatibles. Consulta las Preguntas frecuentes de reCAPTCHA Enterprise y la documentación de Google sobre CSP.
Si tu política utiliza listas de hosts permitidos y la integración carga recursos desde www.recaptcha.net, permite los orígenes necesarios para scripts, frames y conexiones.
script-src https://www.recaptcha.net/recaptcha/ https://www.gstatic.com/recaptcha/;
frame-src https://www.recaptcha.net/recaptcha/ https://recaptcha.google.com/recaptcha/;
connect-src https://www.recaptcha.net/recaptcha/;
Prueba la política en todos los navegadores compatibles y supervisa los informes de infracciones de CSP.
Migración desde Fingerprint
Elimina la integración anterior con Fingerprint antes de habilitar la aplicación de reCAPTCHA en tu Checkout:
Elimina la integración anterior con Fingerprint antes de habilitar la aplicación de reCAPTCHA en tu Checkout:
Elimina
@fingerprint/agenty la configuración de los endpoints de Fingerprint.Deja de utilizar
fingerprintPublicKey,fingerprintLinkedIdyfingerprintReceived.Elimina el sondeo de Fingerprint, los callbacks, el código del navegador relacionado con webhooks y los procesos en segundo plano.
Deja de construir objetos de solicitud
fraudAssessment.Elimina la generación periódica de tokens de reCAPTCHA en segundo plano y los nombres de acción heredados.
No envíes
siteKeyen el cuerpo de las solicitudes de la API; envía únicamente el token generado.
‼️ IMPORTANTE
Es posible que, temporalmente, sigas viendo campos heredados de Fingerprint o fraudAssessment en las respuestas de la API. Las nuevas integraciones deben ignorarlos y no utilizarlos como prueba de que existe una evaluación de pago.
Lista de verificación
Antes de habilitar la aplicación de reCAPTCHA para el Checkout, verifica que:
la integración utiliza
https://checkout-api.ventrata.com/octoy conserva las credenciales;la configuración de Checkout devuelve
recaptchaEnterpriseSiteKey;el script de Enterprise se carga una sola vez y
execute()espera agrecaptcha.enterprise.ready();el cargador espera a
grecaptcha.enterprisey no depende únicamente del evento de carga del script;un error real del script permite una carga posterior, mientras que un tiempo de espera agotado conserva el script lento para que pueda reutilizarse más adelante;
los nombres de las acciones están en minúsculas y coinciden exactamente con
cart_add,purchaseologin;solo las solicitudes de creación reciben
cart_add; las actualizaciones y nuevas reservas no lo reciben;todas las solicitudes que contienen
cardPaymentreciben un nuevo tokenpurchaseanidado;las solicitudes de creación que contienen
cardPaymentincluyen dos tokens distintos;los tokens
loginsolo se conservan dentro del mismo flujo de identidad;RECAPTCHA_REQUIREDse identifica medianteerrorCodey el proceso de reintento se detiene después de un máximo de tres solicitudes a la API;los errores en la generación de tokens se registran sin almacenar los propios tokens, las cabeceras de autorización ni los datos de los clientes;
ninguna solicitud depende de los campos
fraudAssessmento de Fingerprint.
