displacement-overlay
Granado B2B · integração

Chat B2B no portal: como embutir

O chat roda num iframe servido por https://chatb2b.granado.com.br. O portal cria o iframe, entrega a sessão do cliente e reage aos eventos que o chat manda. Esta página traz o snippet completo e o protocolo.

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 chatb2b na 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-hide e 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>
Os atributos do iframe são necessários. 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

EventodataQuando
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

EventodataO 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.