NAVEGAÇÃO DE ETAPAS COM TRAVA DE PAGAMENTO EM TEMPO REAL
======================================================== */
function mostrarEtapa(etapaId) {
// REQUISITO 1: BLOQUEIO
// A etapa de atendimento (etapa-3) ou conclusão (etapa-4) SÓ pode ser
// acessada se houver confirmação de pagamento para o cliente atual.
if (etapaId === "etapa-3" || etapaId === "etapa-4") {
const pago = verificarStatusPagamentoArmazenado(externalReferenceAtual);
if (!pago && !paymentConfirmed) {
// Bloqueia e redireciona de volta para a tela de pagamento pendente
mostrarToast("Pagamento pendente — aguardando confirmação em tempo real...");
etapaId = "etapa-2";
const lockOverlay = document.getElementById("chat-lock-overlay");
if (lockOverlay) lockOverlay.style.display = "flex";
} else {
paymentConfirmed = true;
const lockOverlay = document.getElementById("chat-lock-overlay");
if (lockOverlay) lockOverlay.style.display = "none";
}
}
document.querySelectorAll(".flow-stage").forEach(el => el.style.display = "none");
const target = document.getElementById(etapaId);
if (target) target.style.display = "block";
const stepper = document.getElementById("flow-stepper");
const navBtnAdmin = document.getElementById("nav-btn-admin");
const navBtnConsulta = document.getElementById("nav-btn-consulta");
if (etapaId === "painel-admin") {
if (stepper) stepper.style.display = "none";
navBtnAdmin.classList.remove("btn-nav-outline");
navBtnAdmin.classList.add("btn-nav-primary");
navBtnConsulta.classList.remove("btn-nav-primary");
navBtnConsulta.classList.add("btn-nav-outline");
} else {
if (stepper) stepper.style.display = "flex";
navBtnAdmin.classList.remove("btn-nav-primary");
navBtnAdmin.classList.add("btn-nav-outline");
navBtnConsulta.classList.remove("btn-nav-outline");
navBtnConsulta.classList.add("btn-nav-primary");
// Atualizar classes do Stepper
const stepNum = parseInt(etapaId.replace("etapa-", "")) || 1;
for (let i = 1; i <= 4; i++) {
const item = document.getElementById(`step-nav-${i}`);
if (item) {
item.classList.remove("active", "completed");
if (i === stepNum) item.classList.add("active");
else if (i < stepNum) item.classList.add("completed");
}
}
}
if (etapaId === "etapa-2") {
prepararEtapaPagamento();
}
if (etapaId === "etapa-3" && !chatIniciado && paymentConfirmed) {
chatIniciado = true;
iniciarChatDrRafael();
}
window.scrollTo({ top: 0, behavior: "smooth" });
}
function abrirPainelAdmin() {
mostrarEtapa("painel-admin");
renderizarVendas();
carregarConfiguracaoGatewayUI();
renderizarClientesAdmin();
}
=======
INICIALIZAÇÃO NO CARREGAMENTO DA PÁGINA
======================================================== */
document.addEventListener("DOMContentLoaded", () => {
inicializarStorage();
// REQUISITO 4: LISTENER DE STORAGE PARA LIBERAÇÃO MANUAL DE BACKUP
// Quando o admin confirmar no painel (em outra aba ou mesma origem),
// a página da consulta do cliente desbloqueia automaticamente sem recarregar.
window.addEventListener("storage", (e) => {
if (e.key && e.key.startsWith("rs_pagamento_")) {
const refDaChave = e.key.replace("rs_pagamento_", "");
if (refDaChave === externalReferenceAtual && e.newValue === "true") {
desbloquearPagamentoSucessoEmTempoReal();
}
}
if (e.key === "rs_admin_payment_confirmed_event") {
try {
const data = JSON.parse(e.newValue || "{}");
if (data.ref && data.ref === externalReferenceAtual) {
desbloquearPagamentoSucessoEmTempoReal();
}
} catch (_) {}
}
});
// Recuperar sessão salva do cliente se houver
const sessaoSalva = localStorage.getItem("rs_cliente_ativo_sessao");
if (sessaoSalva) {
try {
const parsed = JSON.parse(sessaoSalva);
if (parsed && parsed.nome) {
clienteAtivo = { ...clienteAtivo, ...parsed };
externalReferenceAtual = clienteAtivo.externalReference || "";
// Preenche campos do formulário da etapa 1
if (document.getElementById("cad-nome")) document.getElementById("cad-nome").value = clienteAtivo.nome || "";
if (document.getElementById("cad-cpf")) document.getElementById("cad-cpf").value = clienteAtivo.cpf || "";
if (document.getElementById("cad-email")) document.getElementById("cad-email").value = clienteAtivo.email || "";
if (document.getElementById("cad-celular")) document.getElementById("cad-celular").value = clienteAtivo.celular || "";
if (document.getElementById("cad-area") && clienteAtivo.area) document.getElementById("cad-area").value = clienteAtivo.area;
if (document.getElementById("cad-descricao")) document.getElementById("cad-descricao").value = clienteAtivo.descricao || "";
}
} catch (_) {}
}
// REQUISITO 6: PERSISTÊNCIA
// Se o cliente recarregar a página, checa se a referência atual já foi confirmada
if (externalReferenceAtual && verificarStatusPagamentoArmazenado(externalReferenceAtual)) {
paymentConfirmed = true;
}
=======
CONFIGURAÇÃO DA API CAKTO (ADMIN & ENDPOINTS)
======================================================== */
function obterConfiguracaoCakto() {
const raw = localStorage.getItem("rs_cakto_config");
if (raw) {
try {
return JSON.parse(raw);
} catch (_) {}
}
return {
gateway: "Cakto",
clientId: CAKTO_DEFAULT_CLIENT_ID,
clientSecret: CAKTO_DEFAULT_CLIENT_SECRET,
webhookSecret: CAKTO_DEFAULT_WEBHOOK_SECRET,
offerId: ""
};
}
function mascararTexto(texto, visiveisFinais = 4) {
if (!texto) return "Não configurado";
const str = String(texto).trim();
if (str.length <= visiveisFinais) return str;
const ultimos = str.slice(-visiveisFinais);
return "••••••••••••" + ultimos;
}
function carregarConfiguracaoGatewayUI() {
const cfg = obterConfiguracaoCakto();
const inputClientId = document.getElementById("cfg-cakto-clientid");
const inputClientSecret = document.getElementById("cfg-cakto-clientsecret");
const inputWebhook = document.getElementById("cfg-cakto-webhook");
const inputOfferId = document.getElementById("cfg-cakto-offerid");
const maskClientId = document.getElementById("mask-clientid-display");
const maskSecret = document.getElementById("mask-secret-display");
const maskWebhook = document.getElementById("mask-webhook-display");
if (inputClientId) inputClientId.value = cfg.clientId || CAKTO_DEFAULT_CLIENT_ID;
if (inputClientSecret) inputClientSecret.value = cfg.clientSecret || CAKTO_DEFAULT_CLIENT_SECRET;
if (inputWebhook) inputWebhook.value = cfg.webhookSecret || CAKTO_DEFAULT_WEBHOOK_SECRET;
if (inputOfferId) inputOfferId.value = cfg.offerId || "";
if (maskClientId) maskClientId.textContent = mascararTexto(cfg.clientId || CAKTO_DEFAULT_CLIENT_ID, 4);
if (maskSecret) maskSecret.textContent = mascararTexto(cfg.clientSecret || CAKTO_DEFAULT_CLIENT_SECRET, 4);
if (maskWebhook) maskWebhook.textContent = mascararTexto(cfg.webhookSecret || CAKTO_DEFAULT_WEBHOOK_SECRET, 4);
}
function salvarConfiguracaoCakto(e) {
e.preventDefault();
const offerId = document.getElementById("cfg-cakto-offerid").value.trim();
const atual = obterConfiguracaoCakto();
atual.offerId = offerId;
localStorage.setItem("rs_cakto_config", JSON.stringify(atual));
const ind = document.getElementById("gateway-saved-indicator");
if (ind) {
ind.style.display = "inline-block";
setTimeout(() => { ind.style.display = "none"; }, 3500);
}
mostrarToast("Configurações da Cakto salvas com sucesso!");
}
// Endpoint 1: Obter Token Cakto com Cache em localStorage (rs_cakto_token)
async function obterTokenCakto(forcarRenovacao = false) {
const cfg = obterConfiguracaoCakto();
const clientId = cfg.clientId || CAKTO_DEFAULT_CLIENT_ID;
const clientSecret = cfg.clientSecret || CAKTO_DEFAULT_CLIENT_SECRET;
if (!forcarRenovacao) {
const cachedRaw = localStorage.getItem("rs_cakto_token");
if (cachedRaw) {
try {
const cached = JSON.parse(cachedRaw);
const agora = Date.now();
// Renova 2 minutos antes do vencimento
if (cached && cached.access_token && cached.expires_at > (agora + 120000)) {
return cached.access_token;
}
} catch (_) {}
}
}
const bodyParams = new URLSearchParams();
bodyParams.append("client_id", clientId);
bodyParams.append("client_secret", clientSecret);
const resp = await fetch(`${CAKTO_BASE_URL}/public_api/token/`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: bodyParams.toString()
});
if (!resp.ok) {
const errText = await resp.text().catch(() => "");
throw new Error(`Falha de autenticação Cakto (HTTP ${resp.status}): ${errText}`);
}
const data = await resp.json();
const expiresInSec = data.expires_in || 36000;
const tokenObj = {
access_token: data.access_token,
token_type: data.token_type || "Bearer",
expires_at: Date.now() + (expiresInSec * 1000)
};
localStorage.setItem("rs_cakto_token", JSON.stringify(tokenObj));
return data.access_token;
}
// Botão Testar Conexão
async function testarConexaoCakto() {
const resEl = document.getElementById("test-connection-result");
const btn = document.getElementById("btn-testar-conexao");
if (resEl) {
resEl.style.display = "block";
resEl.style.background = "rgba(16, 32, 58, 0.8)";
resEl.style.color = "#cbd5e1";
resEl.textContent = "Testando conexão com a API Cakto...";
}
if (btn) btn.disabled = true;
try {
const token = await obterTokenCakto(true);
if (resEl) {
resEl.style.background = "rgba(34, 197, 94, 0.15)";
resEl.style.border = "1px solid #22c55e";
resEl.style.color = "#4ade80";
resEl.innerHTML = "✓ Conectado à Cakto com sucesso! Token OAuth2 gerado e validado.";
}
mostrarToast("Conectado à Cakto ✓");
} catch (err) {
console.error("Erro ao testar conexão Cakto:", err);
if (resEl) {
resEl.style.background = "rgba(239, 68, 68, 0.15)";
resEl.style.border = "1px solid #ef4444";
resEl.style.color = "#f87171";
resEl.innerHTML = `✗ Erro na conexão Cakto: ${err.message}`;
}
mostrarToast("Falha ao conectar com Cakto.");
} finally {
if (btn) btn.disabled = false;
}
}
=======
ETAPA 2: MOTOR DE COBRANÇA PIX CAKTO & DEVOLUTIVA REAL-TIME
======================================================== */
function prepararEtapaPagamento() {
const cfg = obterConfiguracaoCakto();
const alertOffer = document.getElementById("cakto-missing-offer-alert");
const btnGerar = document.getElementById("btn-gerar-cobranca-pix");
document.getElementById("pay-client-name").textContent = clienteAtivo.nome || "Cliente";
document.getElementById("pay-gateway-name").textContent = "Cakto";
// Verifica se o ID da oferta foi configurado
if (!cfg.offerId || cfg.offerId.trim() === "") {
if (alertOffer) alertOffer.style.display = "block";
if (btnGerar) btnGerar.disabled = true;
} else {
if (alertOffer) alertOffer.style.display = "none";
if (btnGerar) btnGerar.disabled = false;
}
// Recupera pedido da sessão se houver
const pedidoSalvo = obterPedidoSalvoLocal(clienteAtivo.id);
if (pedidoSalvo && pedidoSalvo.id) {
pedidoAtivoCakto = pedidoSalvo;
exibirDetalhesPedidoCakto(pedidoSalvo);
// Se já confirmado
if (verificarStatusPagamentoArmazenado(pedidoSalvo.id)) {
desbloquearPagamentoSucessoEmTempoReal();
return;
}
// Continua polling do pedido salvo
bloquearBotaoContinuar();
iniciarPollingCakto(pedidoSalvo.id);
return;
}
// Se já estava com flag pago para este cliente
if (clienteAtivo.id && verificarStatusPagamentoArmazenado(clienteAtivo.id)) {
desbloquearPagamentoSucessoEmTempoReal();
return;
}
// Visual padrão aguardando o cliente clicar em gerar QR Code PIX
bloquearBotaoContinuar();
document.getElementById("pay-order-ref").textContent = "-";
document.getElementById("cakto-qr-display-section").style.display = "none";
document.getElementById("cakto-btn-gerar-section").style.display = "block";
atualizarTextoPolling("Aguardando geração do QR Code PIX...");
}
// Endpoint 2: CRIAR COBRANÇA PIX NA CAKTO
async function gerarCobrancaPixCakto() {
const cfg = obterConfiguracaoCakto();
if (!cfg.offerId || cfg.offerId.trim() === "") {
mostrarToast("Configure o ID da Oferta no painel administrativo.");
abrirPainelAdminComAba("gateway");
return;
}
if (!clienteAtivo.nome || !clienteAtivo.email || !clienteAtivo.cpf) {
mostrarToast("Preencha seus dados de cadastro antes de pagar.");
mostrarEtapa("etapa-1");
return;
}
const btn = document.getElementById("btn-gerar-cobranca-pix");
const btnText = document.getElementById("btn-gerar-pix-text");
const failedBox = document.getElementById("pay-status-failed");
if (failedBox) failedBox.style.display = "none";
if (btn) btn.disabled = true;
if (btnText) btnText.textContent = "Gerando cobrança PIX na Cakto...";
atualizarTextoPolling("Conectando à Cakto e gerando PIX...");
try {
const token = await obterTokenCakto();
// Limpeza de telefone (somente dígitos)
const apenasDigitosTel = (clienteAtivo.celular || "").replace(/\D/g, "");
const phoneFormatado = apenasDigitosTel.startsWith("55") ? apenasDigitosTel : `55${apenasDigitosTel}`;
// CPF apenas dígitos
const apenasDigitosCpf = (clienteAtivo.cpf || "").replace(/\D/g, "");
// Fingerprint persistido do navegador
const fingerprint = localStorage.getItem("rs_browser_fingerprint") || ("fp_" + Date.now());
// Idempotency-Key única por intenção de compra
if (!idempotencyKeyAtual) {
idempotencyKeyAtual = (crypto && crypto.randomUUID) ? crypto.randomUUID() : ("ik_" + Date.now() + "_" + Math.random().toString(36).substring(2, 9));
}
const payload = {
paymentMethod: "pix",
customer: {
name: clienteAtivo.nome,
email: clienteAtivo.email,
phone: phoneFormatado,
fingerprint: fingerprint,
docType: "cpf",
docNumber: apenasDigitosCpf
},
items: [
{
offerId: cfg.offerId.trim(),
quantity: 1,
offerType: "main"
}
],
pixExpiresIn: 3600
};
const resp = await fetch(`${CAKTO_BASE_URL}/public_api/payments/`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
"X-Idempotency-Key": idempotencyKeyAtual
},
body: JSON.stringify(payload)
});
if (resp.status === 401) {
// Token expirado, renova e tenta mais uma vez
const novoToken = await obterTokenCakto(true);
const retryResp = await fetch(`${CAKTO_BASE_URL}/public_api/payments/`, {
method: "POST",
headers: {
"Authorization": `Bearer ${novoToken}`,
"Content-Type": "application/json",
"X-Idempotency-Key": idempotencyKeyAtual
},
body: JSON.stringify(payload)
});
if (!retryResp.ok) {
const errJson = await retryResp.json().catch(() => ({}));
throw new Error(errJson.message || errJson.error || `HTTP ${retryResp.status}`);
}
const retryData = await retryResp.json();
processarRespostaCobrancaCakto(retryData);
return;
}
if (!resp.ok) {
let errDetail = `Erro HTTP ${resp.status}`;
try {
const errJson = await resp.json();
errDetail = errJson.message || errJson.error || JSON.stringify(errJson);
} catch (_) {}
throw new Error(errDetail);
}
const data = await resp.json();
processarRespostaCobrancaCakto(data);
} catch (err) {
console.warn("Falha na chamada da API Cakto:", err);
// REQUISITO: Falha de CORS ou rede NÃO desbloqueia; mantém "Pagamento pendente"
atualizarTextoPolling("Aguardando confirmação do pagamento em tempo real...");
mostrarToast(`Aviso: ${err.message || "Aguardando confirmação do gateway"}`);
// Fallback visual com dados mock da intenção para permitir exibição do QR de contingência e liberação pelo admin
contingenciaCobrancaCakto();
} finally {
if (btn) btn.disabled = false;
if (btnText) btnText.textContent = "Gerar QR Code PIX (Cakto)";
}
}
=======
=======
* {
=======
1
Cadastro
2
Pagamento
3
Atendimento Jurídico
4
Conclusão
Inicie sua Consulta Jurídica
Entenda seus direitos com quem possui muita experiência na atuação jurídica combativa.
ℹ️ Como funciona:
Pague no checkout da Cakto (PIX ou cartão).
A confirmação chega automaticamente em tempo real.
Só então a conversa com o Advogado é liberada.
Pedido / Ref: -
Gateway: Cakto Pagamentos
Cliente: -
Pagar agora
Abre o checkout hospedado oficial da Cakto em nova aba (PIX, Cartão de Crédito e Débito).
↗️ Pagamento aberto em nova aba. Conclua no checkout da Cakto (PIX ou cartão).
Conclua seu pagamento na Cakto. Nosso sistema consulta a compensação em tempo real e libera a conversa automaticamente.
🔗 Clique aqui se a página não abriu
Aguardando confirmação do pagamento em tempo real...
Assim que seu pagamento for compensado, o acesso ao Dr. Rafael será desbloqueado sem você precisar recarregar.
Pagamento confirmado em tempo real!
A conversa com o Advogado Rafael foi liberada. Entrando no atendimento...
Pagamento pendente — aguardando confirmação em tempo real...
* O cliente só pode conversar com o Advogado Rafael após confirmação real do pagamento.
🔄 Verificar pagamento novamente
← Voltar e alterar cadastro
🔒
Atendimento Bloqueado
Para conversar e interagir com o Dr. Rafael, é necessária a confirmação em tempo real do pagamento da consulta.
Ir para a tela de pagamento pendente
🔄 Verificar status do pagamento agora
Enviar
Vamos avançar agora para o próximo passo
Documentos Jurídicos Prontos para Assinatura
Baixe os três documentos abaixo, assine e envie através da sua Área do Cliente.
📥 Word (.doc)
🖨️ PDF / Imprimir
📥 Word (.doc)
🖨️ PDF / Imprimir
📥 Word (.doc)
🖨️ PDF / Imprimir
Concluir e ver dados de acesso
Consulta Concluída com Sucesso!
Seu atendimento foi finalizado pelo Dr. Rafael e seus documentos já estão disponíveis.
🔐 Suas Credenciais da Área do Cliente
📋 Copiar credenciais
Login: cliente@exemplo.com
Senha: rs123456
Guarde seu login e senha. Você receberá atualizações do processo e poderá anexar seus documentos assinados.
Painel Administrativo
Gestão de consultas, vendas do Mercado Pago e petições iniciais geradas.
← Voltar ao Atendimento
💰 Consultas Vendidas
⚙️ Configurar Pagamento (Gateway)
👥 Clientes & Petições Iniciais
⚙️ Configurar Pagamento Cakto
Credenciais da API e link do checkout hospedado da Cakto.
✓ Salvo
🔌 Testar conexão
💾 Salvar Configurações
💡 Instrução ao Administrador: Se o cliente pagou no checkout da Cakto e não liberou automaticamente, clique em Confirmar pagamento para liberar a conversa. A aba do cliente desbloqueará instantaneamente sem precisar recarregar.
Total de pagamentos confirmados: 0
Nome do Cliente
✏️ Editar Dados
🗑️ Excluir Cliente
Petição Inicial com Memória de Cálculo
* Documento restrito ao painel administrativo. Não disponível para o cliente.
📥 Baixar Word (.doc)
🖨️ Imprimir / PDF
Nenhum cliente cadastrado no momento.
Falar com o Advogado
Documento gerado com sucesso.