// Checkout da loja — dados do cliente, entrega, pagamento via Pix (BR Code real) ou cartão. const { useState: ckState, useEffect: ckEffect, useRef: ckRef } = React; const onlyDigits = (s) => (s||'').replace(/\D/g,''); const maskPhone = (v) => { const d = onlyDigits(v).slice(0,11); return d.length<=10 ? d.replace(/(\d{2})(\d{0,4})(\d{0,4})/, (m,a,b,c)=>[a&&`(${a}) `,b,c&&`-${c}`].filter(Boolean).join('')) : d.replace(/(\d{2})(\d{5})(\d{0,4})/, '($1) $2-$3'); }; const maskCard = (v) => onlyDigits(v).slice(0,16).replace(/(\d{4})(?=\d)/g,'$1 '); const maskExp = (v) => { const d = onlyDigits(v).slice(0,4); return d.length>2 ? d.slice(0,2)+'/'+d.slice(2) : d; }; function luhn(num) { const d = onlyDigits(num); if (d.length < 13) return false; let sum = 0, alt = false; for (let i = d.length-1; i>=0; i--) { let n = +d[i]; if (alt) { n*=2; if (n>9) n-=9; } sum+=n; alt=!alt; } return sum % 10 === 0; } function Checkout({open, onClose, items, total, onDone, showToast}) { const store = useStore(); const cfg = store.settings; const [step, setStep] = ckState('dados'); const [c, setC] = ckState({name:'', phone:'', email:'', cpf:''}); const [ship, setShip] = ckState(cfg.delivery.pickup ? 'retirada' : 'entrega'); const [addr, setAddr] = ckState({street:'', num:'', bairro:'', ref:''}); const [method, setMethod] = ckState(cfg.pix.enabled ? 'pix' : 'cartao'); const [inst, setInst] = ckState(1); const [card, setCard] = ckState({number:'', holder:'', exp:'', cvv:''}); const [order, setOrder] = ckState(null); const [busy, setBusy] = ckState(false); const [err, setErr] = ckState(''); ckEffect(() => { if (open) { setStep('dados'); setOrder(null); setErr(''); } }, [open]); if (!open) return null; const freeShip = total >= (+cfg.delivery.freeAbove || 0); const fee = ship === 'entrega' && !freeShip ? (+cfg.delivery.localFee || 0) : 0; const grand = total + fee; const maxInst = Math.max(1, Math.min(+cfg.card.maxInstallments || 1, Math.floor(grand / (+cfg.card.minInstallment || 30)) || 1)); const validDados = c.name.trim().length > 2 && onlyDigits(c.phone).length >= 10 && (ship === 'retirada' || (addr.street.trim() && addr.num.trim() && addr.bairro.trim())); const createOrder = (status, extra = {}) => { const o = { id: uid('o'), code: orderCode(), createdAt: new Date().toISOString(), customer: {...c, phone: onlyDigits(c.phone)}, items: items.map(({p, qty}) => ({id:p.id, name:p.name, qty, price:p.priceN})), subtotal: total, fee, total: grand, delivery: ship, address: ship === 'entrega' ? addr : null, method, installments: method === 'cartao' ? inst : 1, status, ...extra, }; setStore(s => ({ orders: [o, ...(s.orders||[])], products: s.products.map(p => { const it = o.items.find(i => i.id === p.id); return it ? {...p, stock: Math.max(0, (+p.stock||0) - it.qty)} : p; }), })); setOrder(o); return o; }; const goPix = () => { createOrder('aguardando'); setStep('pix'); }; const payCard = () => { setErr(''); if (!luhn(card.number)) return setErr('Número do cartão inválido.'); if (onlyDigits(card.exp).length !== 4) return setErr('Validade incompleta.'); if (onlyDigits(card.cvv).length < 3) return setErr('CVV incompleto.'); if (card.holder.trim().length < 3) return setErr('Informe o nome impresso no cartão.'); setBusy(true); // Em produção esta etapa vira: tokenizar no SDK do gateway → POST /api/orders → POST /api/payments/card setTimeout(() => { setBusy(false); const paid = !!cfg.card.publicKey; createOrder(paid ? 'pago' : 'aguardando', {cardLast4: onlyDigits(card.number).slice(-4), demo: !paid}); setStep('ok'); onDone(); showToast(paid ? 'Pagamento aprovado' : 'Pedido registrado'); }, 1300); }; const confirmPix = () => { setStore(s => ({orders: s.orders.map(o => o.id === order.id ? {...o, status:'aguardando'} : o)})); setStep('ok'); onDone(); showToast('Pedido registrado — envie o comprovante'); }; const waLink = () => { const num = onlyDigits(cfg.contact.whatsapp); const txt = encodeURIComponent(`Olá! Fiz o pedido ${order?.code} no site (${brlc(grand)}) e estou enviando o comprovante do Pix.`); return `https://wa.me/${num}?text=${txt}`; }; return (
Pedido {order.code} registrado.{' '} {order.method === 'pix' ? 'Assim que o Pix cair, confirmamos por WhatsApp.' : order.status === 'pago' ? 'Pagamento aprovado no cartão.' : 'Vamos confirmar o pagamento e avisar você.'}
Aponte a câmera do seu banco para o QR Code ou use o código copia e cola. Pedido {order.code}.
Recebedor: {cfg.pix.merchant} · {cfg.pix.city}