/* ============================================================ payment.jsx — Customer-facing checkout + Thank You page ============================================================ */ function CustomerPay(){ const { bumps, setRoute, pushToast, stripeEnabled, pixel, webhooks } = useApp(); // Helper — fires every active custom webhook for a given event name const fireHooks = (event, payload) => { if (!window.__pagblackx) return; webhooks.filter(w => w.active && !w.system && w.event === event) .forEach(w => window.__pagblackx.fireWebhook(w.url, { event, ...payload })); }; // Mocked order coming from the checkout creator const order = { title: "Identidade Visual — Pacote Prime", desc: "Logotipo, paleta, tipografia, papelaria e brand book", price: 5990, }; const availableBumps = bumps.filter(b => b.active).slice(0, 3); const [method, setMethod] = React.useState("pix"); // pix | card const [installments, setInstallments] = React.useState(1); const [bumpsOn, setBumpsOn] = React.useState([]); const [coupon, setCoupon] = React.useState(""); const [couponApplied, setCouponApplied] = React.useState(null); const [form, setForm] = React.useState({ name: "", email: "", phone: "", cpf: "", cardNumber: "", cardName: "", cardExp: "", cardCvv: "", }); const set = (k) => (e) => setForm({ ...form, [k]: e.target.value }); const [paying, setPaying] = React.useState(false); const [view, setView] = React.useState("form"); // form | pix-wait | done const [error, setError] = React.useState(null); const [pixExpired, setPixExpired] = React.useState(false); const [pixCountdown, setPixCountdown] = React.useState(900); // 15 min // If admin disables Stripe mid-flow, fall back to Pix. React.useEffect(() => { if (!stripeEnabled && method === "stripe") setMethod("pix"); }, [stripeEnabled, method]); // Fire Pixel InitiateCheckout + checkout.criado webhook once when the page mounts. React.useEffect(() => { if (pixel?.enabled && pixel?.fbPixelId && pixel?.trackInitiate && window.__pagblackx){ window.__pagblackx.trackPixel("InitiateCheckout", { value: order.price, currency: "BRL", content_name: order.title, }); } fireHooks("checkout.criado", { product: order.title, value: order.price, currency: "BRL", }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); React.useEffect(() => { if (view !== "pix-wait" || pixExpired) return; const t = setInterval(() => { setPixCountdown(c => { if (c <= 1) { setPixExpired(true); return 0; } return c - 1; }); }, 1000); return () => clearInterval(t); }, [view, pixExpired]); const subtotal = order.price + bumps.filter(b => bumpsOn.includes(b.id)).reduce((a, b) => a + b.price, 0); const discount = couponApplied ? Math.round(subtotal * couponApplied.pct) : 0; const total = subtotal - discount; const applyCoupon = () => { const code = coupon.trim().toUpperCase(); if (!code) return; if (code === "BLACKX10") setCouponApplied({ code, pct: 0.10 }); else if (code === "STUDIO5") setCouponApplied({ code, pct: 0.05 }); else { setError({ type: "coupon", msg: "Cupom inválido ou expirado." }); return; } setError(null); pushToast("Cupom aplicado."); }; const removeCoupon = () => { setCouponApplied(null); setCoupon(""); }; const validateForm = () => { if (!form.name.trim()) return "Informe seu nome completo."; if (!form.email.includes("@")) return "E-mail inválido."; if (!form.cpf.replace(/\D/g, "").length) return "Informe seu CPF."; if (method === "card" || method === "stripe"){ if (form.cardNumber.replace(/\s/g, "").length < 12) return "Número de cartão inválido."; if (!form.cardName) return "Informe o nome no cartão."; if (!form.cardExp.match(/^\d{2}\/\d{2}$/)) return "Validade no formato MM/AA."; if (form.cardCvv.length < 3) return "CVV inválido."; } return null; }; // Centraliza disparos de tracking + redirecionamento ao aprovar/recusar. const onApproved = (extra = {}) => { if (pixel?.enabled && pixel?.fbPixelId && pixel?.trackPurchase && window.__pagblackx){ window.__pagblackx.trackPixel("Purchase", { value: total, currency: "BRL", content_name: order.title, ...extra }); } fireHooks("pagamento.aprovado", { method, value: total, currency: "BRL", product: order.title, ...extra }); setView("done"); pushToast("Pagamento aprovado."); setTimeout(() => setRoute("/obrigado"), 700); }; const onDeclined = (reason) => { fireHooks("pagamento.recusado", { method, value: total, reason }); }; const pay = () => { setError(null); const v = validateForm(); if (v){ setError({ type: "form", msg: v }); return; } setPaying(true); setTimeout(() => { setPaying(false); if (method === "pix"){ setView("pix-wait"); return; } // Card / Stripe: simulate occasional decline if number ends in 0 if (form.cardNumber.replace(/\s/g, "").endsWith("0")){ const msg = method === "stripe" ? "Cartão recusado pela Stripe. Tente outro cartão ou pague com Pix." : "Cartão recusado pelo emissor. Tente outro cartão ou pague com Pix."; setError({ type: "decline", msg }); onDeclined(msg); return; } onApproved({ installments }); }, 1400); }; const fakePixApprove = () => onApproved({ method: "pix" }); // When the Pix expires, notify webhooks. React.useEffect(() => { if (pixExpired) fireHooks("pix.expirado", { value: total }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [pixExpired]); const renewPix = () => { setPixExpired(false); setPixCountdown(900); }; const mm = String(Math.floor(pixCountdown / 60)).padStart(2, "0"); const ss = String(pixCountdown % 60).padStart(2, "0"); return (
Ambiente seguro · pagamento criptografado
{/* LEFT — form */}
{view === "form" && <>

Finalizar pagamento

Ambiente seguro
{/* Method picker */}
setMethod("pix")}>
Pix
Aprovado em segundos
5% OFF
setMethod("card")}>
Cartão de crédito
Até 12x
{stripeEnabled && (
setMethod("stripe")}>
Internacional
USD / EUR · Apple Pay
)}
{/* Personal data */}

Seus dados

{/* Method specific */} {(method === "card" || method === "stripe") && (

Dados do cartão

{method === "stripe" && ( via Stripe )}
{method === "stripe" && (
ou pague com cartão
)}
{method === "card" && (
)} {method === "stripe" && (
Stripe processa em USD/EUR · 3-D Secure ativo
)}
)} {/* Order bumps */} {availableBumps.length > 0 && (

Adicione com desconto

{availableBumps.map((b) => { const on = bumpsOn.includes(b.id); return ( ); })}
)} {/* Coupon */}

Cupom de desconto

{couponApplied ? (
{couponApplied.code} ({Math.round(couponApplied.pct * 100)}% OFF aplicado)
) : (
setCoupon(e.target.value)} placeholder="Digite seu cupom" />
)} {error?.type === "coupon" && (
{error.msg}
)}
Experimente BLACKX10 ou STUDIO5.
{/* Inline errors */} {error && error.type !== "coupon" && (
{error.type === "decline" ? "Pagamento recusado" : "Erro temporário"}
{error.msg}
)}
Ao continuar você concorda com os termos e a política de privacidade.
} {view === "pix-wait" && (

Pague com Pix

Aguardando pagamento
{!pixExpired ? <>
Abra o app do seu banco, escolha pagar com Pix por QR Code e escaneie a imagem abaixo. Ou copie o código.
00020126770014BR.GOV.BCB.PIX2555api.mercadopago.com/instore/o/v2/qr/{uid()}{uid()}5204000053039865406{total.toFixed(2)}5802BR5916BLACKX STUDIO LT6009SAO PAULO62070503***6304B7A2
Expira em {mm}:{ss}
: <>
Pix expirado
Seu código Pix expirou. Gere um novo para continuar com o pagamento ou troque o método.
}
)}
{/* RIGHT — order summary */}

Seu pedido

Premium
BX
{order.title} {order.desc}
{fmtBRL(order.price)}
{bumps.filter(b => bumpsOn.includes(b.id)).map(b => (
+
{b.name} {b.desc}
{fmtBRL(b.price)}
))}
Subtotal{fmtBRL(subtotal)}
{couponApplied &&
Cupom {couponApplied.code}− {fmtBRL(discount)}
}
Método {method === "pix" ? "Pix" : method === "card" ? `Cartão de crédito ${installments > 1 ? `${installments}x` : "à vista"}` : "Cartão internacional"}
Total{fmtBRL(total)}
Ambiente seguro · seus dados são criptografados ponta a ponta. Não armazenamos números de cartão.
); } // ============================================================ // Thank You page // ============================================================ function ThankYou(){ const { setRoute } = useApp(); return (
BlackX
Pagamento confirmado

Bem-vindo à BlackX.

Recebemos seu pagamento. Em instantes você recebe no e-mail e no WhatsApp todos os próximos passos do seu projeto.

Falar no WhatsApp
Um recibo foi enviado para o seu e-mail. Guarde o código do pedido para referência futura.
{ e.preventDefault(); setRoute("/dashboard"); }} style={{ color: "var(--mute)", fontSize: 13, textDecoration: "underline" }}> Voltar ao painel admin
); } function DetailCell({ label, value, border }){ return (
{label}
{value}
); } Object.assign(window, { CustomerPay, ThankYou });