diff --git a/app/api/health.py b/app/api/health.py
index 134d586..ca0527b 100644
--- a/app/api/health.py
+++ b/app/api/health.py
@@ -1,6 +1,8 @@
-"""Health, session bootstrap and freight quoting."""
+"""Health, session bootstrap, freight quoting and the address of a CEP."""
import os
+import re
+import httpx
from fastapi import APIRouter, HTTPException, Request, Response
from ..core import db
@@ -48,3 +50,23 @@ def quote_freight(body: FreightEstimate):
return freight.quote(body.service, body.postal_code, body.metres, body.declared_cents)
except ValueError as exc:
raise HTTPException(422, str(exc))
+
+# The address of a CEP, so the cart fills it in. Looked up here rather than in
+# the browser, which keeps the Site's CSP to its own origin.
+VIACEP = 'https://viacep.com.br/ws/{}/json/'
+CEP_LIMIT = 120
+
+@router.get('/api/cep/{cep}')
+def cep_address(cep: str, request: Request):
+ if not re.fullmatch(r'[0-9]{8}', cep):
+ raise HTTPException(422, 'CEP must have eight digits')
+ rate_limit('cep-lookup', client_ip(request), CEP_LIMIT, 900)
+ try:
+ response = httpx.get(VIACEP.format(cep), timeout=5)
+ data = response.json() if response.status_code == 200 else {}
+ except (httpx.HTTPError, ValueError):
+ raise HTTPException(503, 'Consulta de CEP indisponível')
+ if not data or data.get('erro'):
+ raise HTTPException(404, 'CEP não encontrado')
+ return {'street': data.get('logradouro') or '', 'district': data.get('bairro') or '',
+ 'city': data.get('localidade') or '', 'state': data.get('uf') or ''}
diff --git a/web/cart.js b/web/cart.js
index 8cee1da..0409a7c 100644
--- a/web/cart.js
+++ b/web/cart.js
@@ -34,7 +34,7 @@
// Freight must be quoted again; restored browser values are never final.
if(entrega.tipo==='frete'){entrega.cotado=false;entrega.valor=0;}
for(const [id,key] of [['fCnpj','cnpj'],['fZap','zap'],['fMail','mail']])$(id).value=cliente[key];
- $('cepIn').value=entrega.cep;
+ $('cepIn').value=fmtCep(entrega.cep);
entrega.end=entrega.end||{};
for(const [id,key] of [['eNome','nome'],['eRua','rua'],['eNum','num'],['eComp','comp'],
['eBairro','bairro'],['eCidade','cidade'],['eUf','uf']])$(id).value=entrega.end[key]||'';
diff --git a/web/checkout.js b/web/checkout.js
index a77772e..b017735 100644
--- a/web/checkout.js
+++ b/web/checkout.js
@@ -118,10 +118,10 @@
const result = await api('/freight',{service,postal_code:cep,metres:metres.toFixed(2),declared_cents:cents});
if (entrega.cep !== cep || entrega.tipo !== 'frete') return;
entrega.valor = result.total_cents/100;
+ entrega.dias = result.days || null;
entrega.cotado = true;
freightQuoted = JSON.stringify(pacote());
- $('cepMsg').textContent = 'Frete: '+rs(entrega.valor)+
- (result.days ? ' · entrega em até '+result.days+' dias úteis.' : '.');
+ $('cepMsg').textContent = '';
pintaEntrega();
} catch(error) { $('cepMsg').textContent = error.message; }
};
diff --git a/web/index.html b/web/index.html
index 20ed7c4..683c054 100644
--- a/web/index.html
+++ b/web/index.html
@@ -644,6 +644,7 @@ h1 em{font-style:normal;color:var(--laranja)}
font-family:"Inter",sans-serif;font-weight:600;font-size:12.5px}
.cepL button:hover{border-color:var(--laranja);color:var(--laranja)}
.cep p{font-size:11.5px;color:var(--fraco);margin-top:9px;line-height:1.5}
+.cep p:empty{display:none}
.cep .endereco{margin-top:12px}
.cep .err{color:var(--vermelho)}
.avisoE:empty{display:none}
@@ -1162,7 +1163,7 @@ footer a:hover{color:var(--laranja2)}
Receber em casa
- Cotamos com a transportadora pelo seu CEP
+ Entrega pela Jadlog em todo o Brasil. Informe o CEP para ver o valor e o prazo.
—
@@ -1189,7 +1190,7 @@ footer a:hover{color:var(--laranja2)}
-
+
diff --git a/web/site-cart.js b/web/site-cart.js
index 9297e79..accb66a 100644
--- a/web/site-cart.js
+++ b/web/site-cart.js
@@ -49,7 +49,8 @@ function pintaPedido(){
''+n+(n===1?' item':' itens')+''+rs(itens+eco)+'
'+
(eco>0.01? 'Desconto pela nota'+
'− '+rs(eco)+'
' : '')+
- 'Frete'+
+ 'Frete'+(entrega.tipo==='frete' && entrega.cotado && entrega.dias
+ ? ' · até '+entrega.dias+' dias úteis' : '')+''+
(entrega.tipo==='retira' ? 'Grátis'
: entrega.cotado ? rs(frete) : 'a cotar')+'
'+
'Revisão e reencaixe'+
diff --git a/web/site-flow.js b/web/site-flow.js
index c31e34f..911e2e3 100644
--- a/web/site-flow.js
+++ b/web/site-flow.js
@@ -106,7 +106,7 @@ function pintaEntrega(){
: entrega.cotado ? rs(entrega.valor) : 'a cotar';
$('bPagar').disabled = !entrega.cotado || !clienteOk() || !enderecoOk() || !cartPodeEnviar();
$('eEnd').textContent = entrega.tipo==='frete' && entrega.cotado && !enderecoOk()
- ? 'Preencha o endereço de entrega para seguir.' : '';
+ ? 'Complete o endereço de entrega para ir ao pagamento.' : '';
pintaPedido();
}
@@ -158,10 +158,31 @@ document.querySelectorAll('.opE').forEach(b=>b.addEventListener('click',()=>{
if(entrega.tipo==='frete') $('cepMsg').innerHTML='';
pintaEntrega();
}));
+function fmtCep(v){ v=String(v||'').replace(/\D/g,'').slice(0,8); return v.length>5 ? v.slice(0,5)+'-'+v.slice(5) : v; }
+// The address of a complete CEP fills the form; only the number is left to type.
+let cepBuscado='';
+async function buscaEndereco(cep){
+ if(cep===cepBuscado || !window.dtfApi) return;
+ cepBuscado=cep;
+ try{
+ const a=await window.dtfApi('/cep/'+cep);
+ if(entrega.cep!==cep) return;
+ for(const [id,chave,valor] of [['eRua','rua',a.street],['eBairro','bairro',a.district],
+ ['eCidade','cidade',a.city],['eUf','uf',a.state]]){
+ if(!valor) continue;
+ $(id).value=valor; entrega.end={...(entrega.end||{}),[chave]:valor};
+ }
+ pintaEntrega();
+ const vazio=['eNome','eRua','eNum'].map($).find(el=>!el.value.trim());
+ if(vazio && document.activeElement===$('cepIn')) vazio.focus();
+ }catch(e){ /* sem consulta: o cliente preenche o endereço */ }
+}
$('cepIn').addEventListener('input',e=>{
const v=e.target.value.replace(/\D/g,'').slice(0,8);
- e.target.value = v.length>5 ? v.slice(0,5)+'-'+v.slice(5) : v;
- entrega.cep=v; entrega.cotado=false; entrega.valor=0; pintaEntrega();
+ e.target.value = fmtCep(v);
+ if(v===entrega.cep) return;
+ entrega.cep=v; entrega.cotado=false; entrega.valor=0; entrega.dias=null; pintaEntrega();
+ if(v.length===8){ buscaEndereco(v); if(window.dtfFreight) window.dtfFreight(); }
});
for(const [id,chave] of [['eNome','nome'],['eRua','rua'],['eNum','num'],['eComp','comp'],
['eBairro','bairro'],['eCidade','cidade'],['eUf','uf']]){
@@ -173,6 +194,7 @@ for(const [id,chave] of [['eNome','nome'],['eRua','rua'],['eNum','num'],['eComp'
}
$('bCep').addEventListener('click',async()=>{
if(entrega.cep.length!==8){ $('cepMsg').innerHTML='CEP incompleto.'; return; }
+ buscaEndereco(entrega.cep);
if(window.dtfFreight){ await window.dtfFreight(); return; }
// A cotação vem da transportadora. Enquanto a integração não existir, o pedido não
// fecha por frete: o valor precisa estar fechado antes do Mercado Pago.
diff --git a/web/site-v2.css b/web/site-v2.css
index 36615a0..767e797 100644
--- a/web/site-v2.css
+++ b/web/site-v2.css
@@ -228,6 +228,8 @@ section+section{border-top:0}
/* The payment pages: paying on the left, the order summary on the right */
html:is([data-rota="pagamento"],[data-rota="pix"]) .checkout{max-width:1040px;margin-top:8px}
html:is([data-rota="pagamento"],[data-rota="pix"]) .pagGrid{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:32px;align-items:start}
+.dicaEnd{font-size:13px;color:var(--texto2);margin-top:12px}
+.dicaEnd:empty{display:none}
.pagCab{display:flex;align-items:baseline;justify-content:space-between;gap:16px;margin-bottom:20px}
.pagCab h3{font-size:26px;font-weight:800;letter-spacing:-.02em}
.pagVolta{font-size:14px;color:var(--texto2)}