Ir para conteúdo principal

Como integrar o Google reCAPTCHA Enterprise

Guia de integração do checkout personalizado

Migração do Fingerprint

A Ventrata está a substituir a integração anterior baseada em Fingerprint pelo Google reCAPTCHA Enterprise. Os checkouts personalizados existentes devem remover a integração com o Fingerprint e adotar as ações, localizações dos tokens e o comportamento de repetição descritos neste guia.

Durante o período de migração, alguns campos antigos do Fingerprint e de fraudAssessment poderão continuar a ser apresentados nas respostas da API para garantir a compatibilidade com clientes mais antigos. As novas integrações devem ignorar estes campos e não os devem utilizar para determinar se a avaliação de fraude foi concluída.

O Google reCAPTCHA Enterprise protege os fluxos de criação de checkouts e de pesquisa de identidade contra abusos automatizados e fornece avaliações do Transaction Defense para pagamentos com cartão.

A Ventrata gere a integração do lado do servidor com o Google. O seu checkout personalizado é responsável por gerar os tokens no navegador e colocá-los nos pedidos da API apropriados.

Visão geral da integração

  1. Obtenha a chave pública (site key) do reCAPTCHA junto da Ventrata.

  2. Carregue o script do reCAPTCHA Enterprise no navegador.

  3. Gere um token imediatamente antes de cada interação protegida.

  4. Utilize a ação esperada pela Ventrata.

  5. Coloque o token no objeto de pedido correto.

  6. Tente novamente com um novo token quando o errorCode for RECAPTCHA_REQUIRED.

📒 NOTA

O Fingerprint já não é necessário. Não instale o agente Fingerprint, não crie IDs associados, não consulte os campos de recibo do Fingerprint nem envie dados no objeto legado fraudAssessment.

Pré-requisitos:

  • Utilize um token do Ventrata Checkout.

    Este token tem o mesmo valor que o seu Checkout ID e destina-se a utilização pública no lado do cliente. Restringe o acesso aos endpoints disponíveis para esse checkout.

    const API_URL = "https://checkout-api.ventrata.com/octo"; 
    const CHECKOUT_TOKEN = "YOUR_CHECKOUT_TOKEN";

  • Inclua a capacidade ventrata/checkout e quaisquer outras capacidades OCTO exigidas pelo pedido.

    Authorization: Bearer YOUR_CHECKOUT_TOKEN 
    Octo-Capabilities: ventrata/checkout

  • Inclua as credenciais do navegador para que o cookie de sessão da Ventrata seja mantido.

    const commonFetchOptions = {
    credentials: "include",
    headers: {
    Authorization: `Bearer ${CHECKOUT_TOKEN}`,
    "Octo-Capabilities": "ventrata/checkout",
    },
    };

📒 NOTA

Os exemplos utilizam JavaScript e ilustram a estrutura dos pedidos. Adapte o tratamento de erros e os wrappers de serviço à sua aplicação.


Passo 1: Obter a chave do site do reCAPTCHA

Solicite a configuração do checkout e leia o valor de recaptchaEnterpriseSiteKey na resposta.

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

A site key é pública e segura para utilização no navegador. Nunca inclua um segredo do Google, credenciais de uma conta de serviço ou uma chave da API em código do lado do cliente. A Ventrata cria as avaliações do lado do servidor.

‼️ IMPORTANTE

Durante o período de migração, a resposta também contém fingerprintPublicKey. Trata-se de um marcador de compatibilidade que deve ser ignorado pelas novas integrações.


Passo 2: Carregar o reCAPTCHA Enterprise

Carregue o script do reCAPTCHA Enterprise baseado em pontuação apenas uma vez, assim que a configuração fornecer a site key. Inclua a site key no parâmetro render e aguarde por grecaptcha.enterprise.ready() antes de chamar execute(). Consulte as orientações da Google para instrumentar páginas Web com chaves baseadas em pontuação.

Se carregar o script dinamicamente, tenha em conta que o evento de carregamento do script e a disponibilidade de grecaptcha.enterprise podem ocorrer em momentos diferentes. Uma tentativa posterior deve conseguir reutilizar um script que tenha concluído o carregamento após um timeout anterior.

O seguinte helper utiliza www.recaptcha.net, que a Google suporta como alternativa para ambientes que não conseguem aceder a www.google.com. Consulte as FAQ do reCAPTCHA Enterprise.

O helper mantém os eventos de carregamento (load) e de erro (error) como caminhos rápidos, verifica periodicamente a disponibilidade até ao tempo limite global e preserva um script lento após o timeout para que uma tentativa posterior o possa reutilizar.

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;
}


Passo 3: Gerar tokens no momento certo

Gere um token imediatamente antes da interação que pretende proteger. Não gere tokens continuamente em segundo plano nem reutilize um token em pedidos distintos.

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;
}

📒 NOTA

Estas três tentativas de geração de token ocorrem antes do pedido à Checkout API. São independentes e não aumentam o limite de três pedidos à API descrito no Passo 5.

Os tokens expiram ao fim de dois minutos e, normalmente, só podem ser avaliados uma vez. Gere um novo token cart_add ou purchase para cada pedido que o exija. O fluxo de início de sessão (login) tem uma exceção limitada de reutilização, descrita abaixo.

📗 DICA

Para obter mais informações sobre a validade e utilização dos tokens, consulte as orientações da Google sobre obtenção de tokens do reCAPTCHA.


Passo 4: Utilizar a ação e a localização do payload corretas

Os nomes das ações são escritos em minúsculas e têm de corresponder exatamente ao valor esperado pela Ventrata. Consulte as orientações da Google sobre nomes de ações.

Ação

Utilização

Localização no pedido

cart_add

Criar uma nova encomenda, reserva, compra ou vale-presente

recaptchaEnterprise ao nível superior

purchase

Todos os pedidos que contenham cardPayment

cardPayment.recaptchaEnterprise

login

Fluxos de adesão, check-in, concierge e pesquisa de identidade

recaptchaEnterprise ao nível superior

Criar uma encomenda, reserva, compra ou vale-presente

Gere um novo token cart_add para cada pedido de criação e coloque-o no objeto recaptchaEnterprise ao nível superior.

{
"currency": "USD",
"recaptchaEnterprise": {
"token": "CART_ADD_TOKEN"
}
}

📒 NOTA

Isto aplica-se aos pedidos POST que criam encomendas, reservas, compras e vales-presente. Não adicione cart_add a pedidos de atualização ou de nova reserva de registos já existentes. Se repetir um pedido de criação após um erro, gere um novo token.


Enviar um pagamento com cartão

Sempre que um pedido contenha cardPayment, gere um novo token purchase para esse pedido e coloque-o diretamente em cardPayment.recaptchaEnterprise. Isto também se aplica quando repetir um pedido após uma atualização do gateway ou uma resposta intermédia.

📒 NOTA

Não atualize o token separadamente. Gere o token purchase como parte do pedido de pagamento. Não efetue uma atualização adicional da encomenda apenas para atualizar o token.

{
"currency": "USD",
"cardPayment": {
"gateway": "adyen",
"adyen": {
"sessionId": "ADYEN_SESSION_ID",
"sessionResult": "ADYEN_SESSION_RESULT"
},
"recaptchaEnterprise": {
"token": "PURCHASE_TOKEN"
}
}
}
  • Utilize purchase para atualizações ou confirmações de encomendas e reservas que contenham cardPayment.

    Utilize-o nos fluxos de pagamento do Checkout e do Manage My Booking.

    Utilize-o com todos os gateways de pagamento por cartão suportados.

📒 NOTA

A Ventrata exige a ação purchase para pedidos que contenham cardPayment. Para obter informações adicionais sobre o Transaction Defense, consulte a documentação da Google.


Criar uma encomenda com pagamento por cartão

Um pedido de criação que também contenha cardPayment necessita de dois tokens distintos:

  • cart_add ao nível superior;

  • purchase dentro de cardPayment.

Um único token não pode ser utilizado para ambas as ações.

{
"currency": "USD",
"recaptchaEnterprise": {
"token": "CART_ADD_TOKEN"
},
"cardPayment": {
"gateway": "adyen",
"recaptchaEnterprise": {
"token": "PURCHASE_TOKEN"
}
}
}


Início de sessão e pesquisa de identidade

Gere um token login para pedidos relacionados com adesões, check-in, concierge e pesquisa de identidade.

{
"email": "[email protected]",
"recaptchaEnterprise": {
"token": "LOGIN_TOKEN"
}
}

No mesmo fluxo de identificação, pode reutilizar o token em pedidos subsequentes que utilizem o mesmo endereço de e-mail normalizado, número de telemóvel ou referência de reserva.

Descarte o token e gere um novo quando:

  • a Ventrata devolver RECAPTCHA_REQUIRED;

  • o cliente alterar o endereço de e-mail, número de telemóvel, país ou referência da reserva;

  • for iniciado um novo fluxo de início de sessão ou pesquisa.


Passo 5: Tratar RECAPTCHA_REQUIRED

Um pedido protegido de criação ou pesquisa de identidade pode devolver RECAPTCHA_REQUIRED quando o token estiver em falta, tiver expirado, já tiver sido avaliado ou for afetado por um erro do navegador passível de repetição.

{
"error": "BAD_REQUEST",
"errorMessage": "A new Google reCAPTCHA token is required",
"errorCode": "RECAPTCHA_REQUIRED",
"reason": "MISSING"
}

Motivo

Significado

MISSING

O pedido não incluiu o token ou enviou null.

EXPIRED

O token deixou de ser válido.

DUPE

O token já tinha sido avaliado.

BROWSER_ERROR

A Google comunicou um erro do navegador passível de repetição.

Ao decidir se deve repetir um pedido, verifique apenas o errorCode.

Para cada resposta RECAPTCHA_REQUIRED, descarte o token anterior e repita o pedido com um token recém-gerado. Se a geração do token falhar, envie null para que a Ventrata devolva o erro explícito e aguarde dois segundos antes de tentar novamente. Pare após um total de três tentativas à Checkout API.

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);
}
}
}

O exemplo seguinte aplica este comportamento de repetição a um pedido protegido de criação de encomenda. Cada tentativa gera um novo token cart_add e coloca-o no objeto recaptchaEnterprise ao nível 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

Aplique o mesmo comportamento de repetição a outros pedidos protegidos de criação e pesquisa de identidade, utilizando a ação e a localização do payload correspondentes. Se um pedido repetido contiver cardPayment, gere também um novo token purchase aninhado para essa tentativa.

Não repita automaticamente os pedidos nos seguintes casos:

  • VERIFICATION_FAILED — a avaliação falhou por um motivo não repetível, como uma pontuação baixa ou uma ação inesperada.

  • PAYMENT_DECLINE_LIMIT_EXCEEDED — a encomenda excedeu o número permitido de tentativas de pagamento recusadas.


Política de Segurança de Conteúdos (Content Security Policy)

A Google recomenda a utilização de um CSP nonce. Aplique o nonce ao script do Enterprise. O reCAPTCHA também suporta strict-dynamic em navegadores compatíveis. Consulte as FAQ do reCAPTCHA Enterprise e as orientações da Google sobre CSP.

Se a sua política utilizar listas de permissões de hosts e a integração carregar recursos a partir de www.recaptcha.net, permita as origens necessárias para scripts, frames e ligações.

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/;

Teste a política em todos os navegadores suportados e monitorize os relatórios de violações de CSP.


Migração do Fingerprint

Remova a integração anterior com o Fingerprint antes de ativar a aplicação do reCAPTCHA no seu checkout:

  • Remova @fingerprint/agent e a configuração dos endpoints do Fingerprint.

  • Deixe de utilizar fingerprintPublicKey, fingerprintLinkedId e fingerprintReceived.

  • Remova a lógica de polling, callbacks, código de browser relacionado com webhooks e processos em segundo plano do Fingerprint.

  • Deixe de construir objetos de pedido fraudAssessment.

  • Remova os intervalos em segundo plano para geração de tokens do reCAPTCHA e os nomes de ações legados.

  • Não envie siteKey no corpo dos pedidos à API; envie apenas o token gerado.

‼️ IMPORTANTE

Poderá continuar a ver temporariamente campos legados do Fingerprint ou de avaliação de fraude nas respostas da API. As novas integrações devem ignorar esses campos e não os utilizar como prova de que existe uma avaliação de pagamento.

Lista de verificação

Antes de ativar a aplicação do reCAPTCHA para o checkout, confirme que:

  • a integração utiliza https://checkout-api.ventrata.com/octo e mantém as credenciais;

  • a configuração do checkout devolve recaptchaEnterpriseSiteKey;

  • o script Enterprise é carregado apenas uma vez e execute() aguarda por grecaptcha.enterprise.ready();

  • o carregador aguarda pela disponibilidade de grecaptcha.enterprise, não dependendo apenas do evento de carregamento do script;

  • um erro real do script permite um novo carregamento posterior, enquanto um timeout preserva o script lento para reutilização;

  • os nomes das ações estão em minúsculas e correspondem exatamente a cart_add, purchase ou login;

  • apenas os pedidos de criação recebem cart_add; pedidos de atualização e de nova reserva não o utilizam;

  • todos os pedidos que contenham cardPayment recebem um novo token purchase aninhado;

  • os pedidos de criação com cardPayment contêm dois tokens distintos;

  • os tokens login são reutilizados apenas no mesmo fluxo de identidade;

  • RECAPTCHA_REQUIRED é identificado através de errorCode e as tentativas param após um máximo de três pedidos à API;

  • as falhas na geração de tokens são registadas sem incluir tokens, cabeçalhos de autorização ou dados do cliente;

  • nenhum pedido depende dos campos fraudAssessment ou Fingerprint.

Isto respondeu à sua pergunta?