/* ============================================================ 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 (
Recebemos seu pagamento. Em instantes você recebe no e-mail e no WhatsApp todos os próximos passos do seu projeto.