Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions api/checkout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const Stripe = require('stripe');

module.exports = async function handler(req, res) {
if (req.method !== 'POST') {
res.setHeader('Allow', 'POST');
return res.status(405).json({ error: 'Method not allowed' });
}

try {
const secretKey = process.env.STRIPE_SECRET_KEY;
const appUrl = process.env.APP_URL;
if (!secretKey || !appUrl) {
return res.status(500).json({ error: 'Missing Stripe configuration on server.' });
}

const stripe = new Stripe(secretKey);
const items = Array.isArray(req.body?.items) ? req.body.items : [];

if (!items.length) {
return res.status(400).json({ error: 'Cart is empty.' });
}

const line_items = items.map((item) => ({
price_data: {
currency: 'usd',
product_data: { name: String(item.name || 'Producto FORMA') },
unit_amount: Math.max(1, Math.round(Number(item.price || 0) * 100)),
},
quantity: Math.max(1, Number(item.quantity || 1)),
}));

const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items,
success_url: `${appUrl}/?paid=1`,
cancel_url: `${appUrl}/?paid=0`,
});

return res.status(200).json({ url: session.url });
} catch (error) {
return res.status(500).json({ error: error.message || 'Checkout failed.' });
}
};
43 changes: 43 additions & 0 deletions api/stripe-webhook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const Stripe = require('stripe');

module.exports.config = {
api: {
bodyParser: false,
},
};

async function readRawBody(req) {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
return Buffer.concat(chunks);
}

module.exports = async function handler(req, res) {
if (req.method !== 'POST') {
res.setHeader('Allow', 'POST');
return res.status(405).end();
}

try {
const secretKey = process.env.STRIPE_SECRET_KEY;
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
if (!secretKey || !webhookSecret) {
return res.status(500).send('Missing Stripe webhook configuration.');
}

const stripe = new Stripe(secretKey);
const signature = req.headers['stripe-signature'];
const rawBody = await readRawBody(req);

const event = stripe.webhooks.constructEvent(rawBody, signature, webhookSecret);

if (event.type === 'checkout.session.completed') {
const session = event.data.object;
console.log('[FORMA] checkout.session.completed', session.id);
}

return res.status(200).json({ received: true });
} catch (error) {
return res.status(400).send(`Webhook Error: ${error.message}`);
}
};
46 changes: 44 additions & 2 deletions assets/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,14 @@ document.addEventListener('click', (event) => {

if (event.target.closest('[data-open-cart]')) openDrawer(cartDrawer);
if (event.target.closest('[data-close-cart]')) closeDrawer(cartDrawer);

if (event.target.closest('.cart-footer .btn.btn-dark')) {
event.preventDefault();
goToStripeCheckout().catch((error) => {
console.error('[FORMA] Stripe checkout error:', error);
document.querySelector('[data-newsletter-message]').textContent = 'No pudimos iniciar el pago. Intenta de nuevo.';
});
}
if (event.target.closest('[data-open-wishlist]')) openDrawer(wishlistDrawer);
if (event.target.closest('[data-close-wishlist]')) closeDrawer(wishlistDrawer);
if (event.target.closest('[data-close-modal]')) closeModal();
Expand All @@ -465,12 +473,46 @@ document.querySelector('[data-view-toggle]').addEventListener('click', (event) =
renderProducts();
});


async function goToStripeCheckout() {
const items = store.state.cart.map((item) => ({
name: item.name,
price: item.price,
quantity: item.quantity,
}));

if (!items.length) {
document.querySelector('[data-newsletter-message]').textContent = 'Tu carrito está vacío. Agrega productos antes de pagar.';
return;
}

const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items }),
});

const data = await response.json();
if (!response.ok || !data.url) throw new Error(data.error || 'No se pudo iniciar el checkout.');
window.location.href = data.url;
}

document.querySelector('[data-newsletter-form]').addEventListener('submit', async (event) => {
event.preventDefault();
const email = event.currentTarget.querySelector('input[type="email"]').value;
const input = event.currentTarget.querySelector('input[type="email"]');
const email = input.value.trim();
if (!email) return;

await window.FormaIntegrations?.saveNewsletter?.(email);
window.FormaIntegrations?.trackEvent('newsletter_signup', { email });
document.querySelector('[data-newsletter-message]').textContent = 'Listo. Te avisaremos del próximo drop privado.';

const target = 'feispla@hotmail.com';
const subject = encodeURIComponent('Nuevo registro de newsletter FORMA');
const body = encodeURIComponent(`Correo del cliente: ${email}`);
window.location.href = `mailto:${target}?subject=${subject}&body=${body}`;

document.querySelector('[data-newsletter-message]').textContent = 'Listo. Abrimos tu app de correo para enviar el registro.';
event.currentTarget.reset();
});

modal.addEventListener('click', (event) => {
Expand Down
41 changes: 37 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@
<section class="hero" id="inicio">
<div class="hero-copy">
<p class="eyebrow">Nueva campaña · Primavera urbana</p>
<h1>Esenciales con forma, intención y carácter.</h1>
<h1>Esenciales premium para comprar hoy. Soluciones e-commerce para crecer mañana.</h1>
<p>
Compra una selección curada de moda, accesorios y objetos lifestyle con estética editorial, materiales premium y experiencia de compra sin fricción.
Compra una selección curada de moda, accesorios y lifestyle con experiencia premium.
Además, si tienes una marca, en FORMA diseñamos y desarrollamos tu tienda Shopify con enfoque en conversión.
</p>
<div class="hero-actions">
<a class="btn btn-dark" href="#productos">Comprar ahora</a>
<button class="btn btn-light" type="button" data-open-featured>Ver campaña</button>
<a class="btn btn-light" href="https://feispla.myshopify.com/" target="_blank" rel="noopener noreferrer">Ir a tienda oficial</a>
</div>
<dl class="stats" aria-label="Estadísticas de la tienda">
<div><dt>24h</dt><dd>despacho</dd></div>
Expand All @@ -75,6 +76,39 @@ <h1>Esenciales con forma, intención y carácter.</h1>
</div>
</section>

<section class="ops-stack" aria-label="Servicios FORMA para e-commerce">
<div>
<p class="eyebrow">Servicios FORMA</p>
<h2>También construimos tiendas que venden.</h2>
<p>
Diseño + desarrollo + integración Shopify + automatizaciones en un flujo profesional,
manteniendo estética premium y foco total en conversión.
</p>
</div>

<div class="ops-grid">
<article>
<strong>Setup Shopify</strong>
<span>Listo para operar</span>
<p>Catálogo optimizado, navegación clara y estructura comercial sólida.</p>
</article>
<article>
<strong>UX & Performance</strong>
<span>Mobile-first</span>
<p>Checkout más fluido, mejor experiencia de compra y carga rápida.</p>
</article>
<article>
<strong>Integración & Escala</strong>
<span>Operación conectada</span>
<p>Eventos, CRM, automatizaciones y despliegues continuos.</p>
</article>
</div>

<p style="margin-top: 1rem; color: #666;">
Stack validado para operación real: Shopify · Supabase · Vercel · GitHub AI
</p>
</section>

<section class="feature-bar" id="garantias" aria-label="Características de compra">
<article><span>🚚</span><strong>Envío gratis</strong><p>En pedidos desde $100.</p></article>
<article><span>↩</span><strong>Devoluciones fáciles</strong><p>30 días para cambios.</p></article>
Expand Down Expand Up @@ -189,7 +223,6 @@ <h2>Lista para Shopify, Supabase, Chatbase, GitHub AI y Vercel.</h2>
<div>
<a class="brand" href="#inicio"><span class="brand-mark">F</span><span>FORMA</span></a>
<p>Un e-commerce editorial construido para vender productos premium con claridad, confianza y estilo.</p>
<p><strong>Contacto comercial:</strong> FORMA Ventas · <a href="mailto:feispla@hotmail.com">feispla@hotmail.com</a></p>
</div>
<form class="newsletter" data-newsletter-form>
<label for="email-newsletter">Recibe drops y descuentos privados</label>
Expand Down