1. Quando o chat aparece
Por enquanto o chat só aparece quando a URL da página tem o parâmetro chatb2b, por exemplo https://b2b.granado.com.br/?chatb2b. O valor não importa; ?chatb2b, ?chatb2b=1 e ?utm_source=x&chatb2b funcionam.
Do lado do portal, o trabalho é repassar a query string inteira da página no src do iframe (WIDGET + '/' + location.search). Quem decide é o widget:
- com
chatb2bna URL, o chat sobe e fica valendo até o cliente fechar a aba, mesmo navegando por páginas sem o parâmetro; - sem o parâmetro, o widget manda o evento
chatb2b-hidee o portal esconde o contêiner (display: none).
Quando o chat for liberado pra todos, a regra sai do nosso lado. O snippet não muda.
2. Snippet
Colar no template das páginas do portal em que o chat deve existir, antes de </body>. A única parte a completar é a função obterSessao() (seção 3).
<div id="chatb2b" class="min"></div>
<style>
#chatb2b { position: fixed; right: 20px; bottom: 20px; width: 380px; height: 620px; z-index: 9999;
border-radius: 16px; overflow: hidden; box-shadow: 0 12px 32px rgba(0,0,0,.18);
transition: width .2s, height .2s, border-radius .2s; }
#chatb2b.min { width: 64px; height: 64px; border-radius: 50%; box-shadow: none; }
#chatb2b.full { right: 0; bottom: 0; width: 100vw; height: 100vh; border-radius: 0; box-shadow: none; }
@media (max-width: 600px) { #chatb2b:not(.min) { inset: 0; width: 100vw; height: 100vh; border-radius: 0; } }
</style>
<script>
(function () {
var WIDGET = 'https://chatb2b.granado.com.br';
var box = document.getElementById('chatb2b');
var iframe = document.createElement('iframe');
// a query string da página vai inteira pro widget: é ele que decide se aparece (?chatb2b)
iframe.src = WIDGET + '/' + location.search;
iframe.title = 'Atendimento B2B';
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-presentation allow-popups');
iframe.setAttribute('allow', 'microphone; autoplay');
iframe.style.cssText = 'width:100%;height:100%;border:0;margin:0;padding:0';
box.appendChild(iframe);
function enviar(eventName, data) {
iframe.contentWindow.postMessage({ eventName: eventName, data: data || {} }, WIDGET);
}
// devolve a sessão do cliente; ver "Sessão do cliente" abaixo
function obterSessao() {
return Promise.resolve({ status: 'user_not_logged_in' });
}
function enviarSessao() {
obterSessao().then(function (s) { enviar('chatb2b-session', s); });
}
window.addEventListener('message', function (e) {
if (e.origin !== WIDGET) return;
if (e.data === 'handshake') return enviarSessao();
var m = e.data;
if (!m || typeof m.eventName !== 'string') return;
var d = m.data || {};
switch (m.eventName) {
case 'chatb2b-hide': box.style.display = 'none'; break;
case 'chatb2b-request-session': enviarSessao(); break;
case 'chatb2b-expand': box.classList.remove('min'); break;
case 'chatb2b-minimize': box.classList.add('min'); box.classList.remove('full'); break;
case 'chatb2b-fullscreen': box.classList.toggle('full', !!d.on); break;
case 'chatb2b-login': location.href = d.url || '/b2b/customer/account/login/'; break;
case 'chatb2b-open-url': if (d.url) window.open(d.url, '_blank', 'noopener'); break;
case 'chatb2b-cart-updated': /* atualizar o minicart: d.itemsQty */ break;
}
});
// opcional: abrir o chat já no contexto de um pedido ou produto
window.chatB2bAbrir = function (contexto) { enviar('chatb2b-open', { context: contexto }); };
})();
</script>
allow="microphone; autoplay" libera a conversa por áudio; sem ele o navegador bloqueia o microfone dentro do iframe. O sandbox com allow-popups deixa o chat abrir o checkout e os links em outra aba.3. Sessão do cliente
O chat não tem login próprio. Ele usa a sessão do portal pra consultar pedidos, crédito e carrinho do cliente. Logo que o iframe carrega, o widget manda handshake, e o portal responde com chatb2b-session:
// cliente logado
{ status: 'logged_in', sessionId: '<PHPSESSID do Magento>', customerName: '<razão social>' }
// sem login
{ status: 'user_not_logged_in' }
O PHPSESSID é o cookie de sessão do Magento. Como ele costuma ser HttpOnly, o JavaScript da página não consegue lê-lo: obterSessao() precisa buscá-lo num endpoint do próprio portal que só responde ao cliente logado. Se a sessão vencer durante a conversa, o widget manda chatb2b-request-session e o portal responde de novo com chatb2b-session.
4. Eventos
Todas as mensagens vão por postMessage no formato { eventName, data }. O portal só deve aceitar mensagens com origin igual a https://chatb2b.granado.com.br. O widget só aceita mensagens dos domínios do portal cadastrados com a gente (seção 5).
Portal → chat
| Evento | data | Quando |
|---|---|---|
chatb2b-session | { status, sessionId?, customerName? } | Resposta ao handshake e ao chatb2b-request-session. |
chatb2b-open | { context: { orderNumber } } ou { context: { sku } } | Opcional. Abre o chat já com "Quero falar sobre o pedido X" ou "sobre o produto Y" no campo de mensagem, por exemplo num botão "Falar sobre este pedido". |
Chat → portal
| Evento | data | O que o portal faz |
|---|---|---|
handshake | (string, sem objeto) | Envia chatb2b-session. |
chatb2b-hide | — | Esconde o contêiner. Vem quando a página não tem ?chatb2b. |
chatb2b-request-session | — | Envia chatb2b-session de novo (a sessão venceu ou não chegou). |
chatb2b-expand | — | Tira a classe min: o botão redondo vira a janela do chat. |
chatb2b-minimize | — | Volta pra min e sai da tela cheia. |
chatb2b-fullscreen | { on: true | false } | Liga ou desliga a classe full. |
chatb2b-login | { url } | Leva o cliente pra tela de login do portal. Depois do login, a página recarrega e o novo handshake recebe a sessão. |
chatb2b-cart-updated | { itemsQty } | O chat mexeu no carrinho do cliente. Atualizar o minicart do portal. |
chatb2b-open-url | { url } | Abre a URL em outra aba. |
5. Domínios e teste
O widget aceita mensagens de https://b2b.granado.com.br. Pra embutir em outro domínio (homologação, por exemplo), passem o endereço pra gente incluir.
O portal de teste embute o widget com este mesmo snippet, com um formulário no lugar do endpoint de sessão e um log dos eventos nos dois sentidos: chatb2b-teste.granado-app.next.data2.app/?chatb2b. Abra sem o ?chatb2b numa aba nova pra ver o chat escondido.