Actualizar server.js
This commit is contained in:
468
server.js
468
server.js
@@ -33,6 +33,411 @@ app.use(cors(corsOptions));
|
|||||||
// Habilitar pre-flight para todas las rutas
|
// Habilitar pre-flight para todas las rutas
|
||||||
app.options('*', cors(corsOptions));
|
app.options('*', cors(corsOptions));
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 💳 WEBHOOK STRIPE (DEBE IR ANTES DE express.json)
|
||||||
|
// ==========================================
|
||||||
|
app.post("/webhook/stripe", express.raw({ type: "application/json" }), async (req, res) => {
|
||||||
|
let event;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!STRIPE_WEBHOOK_SECRET) {
|
||||||
|
console.error("❌ STRIPE_WEBHOOK_SECRET no configurado.");
|
||||||
|
return res.status(500).send("Webhook secret no configurado");
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = req.headers["stripe-signature"];
|
||||||
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "sk_test_dummy", { apiVersion: "2023-10-16" });
|
||||||
|
|
||||||
|
event = stripe.webhooks.constructEvent(req.body, signature, STRIPE_WEBHOOK_SECRET);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Firma webhook Stripe inválida:", err.message);
|
||||||
|
return res.status(400).send(`Webhook Error: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertarEventoPago = async ({
|
||||||
|
subscriptionId = null,
|
||||||
|
companyId = null,
|
||||||
|
stripeInvoiceId = null,
|
||||||
|
stripePaymentIntentId = null,
|
||||||
|
stripeCheckoutSessionId = null,
|
||||||
|
stripeEventId = null,
|
||||||
|
amount = 0,
|
||||||
|
currency = "eur",
|
||||||
|
status = "pendiente",
|
||||||
|
eventType = null,
|
||||||
|
paidAt = null
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO protection_payment_events (
|
||||||
|
subscription_id,
|
||||||
|
company_id,
|
||||||
|
stripe_invoice_id,
|
||||||
|
stripe_payment_intent_id,
|
||||||
|
stripe_checkout_session_id,
|
||||||
|
stripe_event_id,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
status,
|
||||||
|
event_type,
|
||||||
|
paid_at
|
||||||
|
)
|
||||||
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
|
||||||
|
`, [
|
||||||
|
subscriptionId,
|
||||||
|
companyId,
|
||||||
|
stripeInvoiceId,
|
||||||
|
stripePaymentIntentId,
|
||||||
|
stripeCheckoutSessionId,
|
||||||
|
stripeEventId,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
status,
|
||||||
|
eventType,
|
||||||
|
paidAt
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("⚠️ Error insertando protection_payment_events:", e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (event.type) {
|
||||||
|
// =====================================================
|
||||||
|
// 1. CHECKOUT COMPLETADO
|
||||||
|
// =====================================================
|
||||||
|
case "checkout.session.completed": {
|
||||||
|
const session = event.data.object;
|
||||||
|
const metadata = session.metadata || {};
|
||||||
|
const paymentType = metadata.type;
|
||||||
|
|
||||||
|
// 🟢 PLAN DE PROTECCIÓN
|
||||||
|
if (paymentType === "protection_plan") {
|
||||||
|
const subscriptionId = parseInt(metadata.subscription_id, 10);
|
||||||
|
const ownerId = parseInt(metadata.owner_id, 10);
|
||||||
|
|
||||||
|
if (!subscriptionId || !ownerId) {
|
||||||
|
console.warn("⚠️ checkout.session.completed sin metadata válida para protection_plan");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
UPDATE protection_subscriptions
|
||||||
|
SET
|
||||||
|
payment_status = 'pagado',
|
||||||
|
status = 'activo',
|
||||||
|
stripe_customer_id = COALESCE($1, stripe_customer_id),
|
||||||
|
stripe_subscription_id = COALESCE($2, stripe_subscription_id),
|
||||||
|
started_at = COALESCE(started_at, NOW()),
|
||||||
|
last_payment_at = NOW(),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = $3 AND company_id = $4
|
||||||
|
`, [
|
||||||
|
session.customer ? String(session.customer) : null,
|
||||||
|
session.subscription ? String(session.subscription) : null,
|
||||||
|
subscriptionId,
|
||||||
|
ownerId
|
||||||
|
]);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO protection_activity (company_id, type, description)
|
||||||
|
VALUES ($1, 'cobro', $2)
|
||||||
|
`, [
|
||||||
|
ownerId,
|
||||||
|
`Alta confirmada por Stripe. Suscripción #${subscriptionId} activada correctamente.`
|
||||||
|
]);
|
||||||
|
|
||||||
|
await insertarEventoPago({
|
||||||
|
subscriptionId,
|
||||||
|
companyId: ownerId,
|
||||||
|
stripeCheckoutSessionId: session.id || null,
|
||||||
|
stripePaymentIntentId: session.payment_intent || null,
|
||||||
|
stripeEventId: event.id,
|
||||||
|
amount: ((session.amount_total || 0) / 100),
|
||||||
|
currency: session.currency || "eur",
|
||||||
|
status: "paid",
|
||||||
|
eventType: event.type,
|
||||||
|
paidAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ [STRIPE] Alta plan activada. Sub ID ${subscriptionId}`);
|
||||||
|
}
|
||||||
|
// 🔵 PRESUPUESTO NORMAL
|
||||||
|
else {
|
||||||
|
const budgetId = session.metadata?.budget_id;
|
||||||
|
const ownerId = session.metadata?.owner_id;
|
||||||
|
const amountTotal = ((session.amount_total || 0) / 100).toFixed(2);
|
||||||
|
|
||||||
|
if (!budgetId || !ownerId) {
|
||||||
|
console.warn("⚠️ checkout.session.completed de presupuesto sin metadata suficiente");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.query(
|
||||||
|
"UPDATE budgets SET status = 'paid' WHERE id = $1 AND owner_id = $2",
|
||||||
|
[budgetId, ownerId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const sq = await pool.query(`
|
||||||
|
SELECT id, raw_data
|
||||||
|
FROM scraped_services
|
||||||
|
WHERE service_ref = $1 AND owner_id = $2
|
||||||
|
`, [`PRE-${budgetId}`, ownerId]);
|
||||||
|
|
||||||
|
if (sq.rowCount > 0) {
|
||||||
|
const serviceId = sq.rows[0].id;
|
||||||
|
let rawData = sq.rows[0].raw_data || {};
|
||||||
|
rawData.is_paid = true;
|
||||||
|
|
||||||
|
await pool.query(
|
||||||
|
"UPDATE scraped_services SET raw_data = $1 WHERE id = $2",
|
||||||
|
[JSON.stringify(rawData), serviceId]
|
||||||
|
);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO service_financials (scraped_id, amount, payment_method, is_paid)
|
||||||
|
VALUES ($1, $2, 'Tarjeta (Stripe)', true)
|
||||||
|
ON CONFLICT (scraped_id)
|
||||||
|
DO UPDATE SET
|
||||||
|
amount = EXCLUDED.amount,
|
||||||
|
is_paid = true,
|
||||||
|
payment_method = 'Tarjeta (Stripe)',
|
||||||
|
updated_at = NOW()
|
||||||
|
`, [serviceId, amountTotal]);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO scraped_service_logs (scraped_id, user_name, action, details)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
`, [
|
||||||
|
serviceId,
|
||||||
|
"Stripe API",
|
||||||
|
"Pago Confirmado",
|
||||||
|
`El cliente ha abonado ${amountTotal}€ por pasarela segura.`
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerQ = await pool.query("SELECT phone FROM users WHERE id = $1", [ownerId]);
|
||||||
|
if (ownerQ.rowCount > 0) {
|
||||||
|
const msgWa = `💰 *¡PAGO RECIBIDO (STRIPE)!*\n\nSe acaba de confirmar el pago con tarjeta del presupuesto *PRE-${budgetId}* por un importe de *${amountTotal}€*.\n\nEl sistema lo ha marcado como pagado automáticamente.`;
|
||||||
|
sendWhatsAppAuto(ownerQ.rows[0].phone, msgWa, `cliente_${ownerId}`, false).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ [STRIPE] Presupuesto PRE-${budgetId} pagado`);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// 2. FACTURA PAGADA (RENOVACIONES Y COBROS REALES)
|
||||||
|
// =====================================================
|
||||||
|
case "invoice.paid": {
|
||||||
|
const invoice = event.data.object;
|
||||||
|
const stripeSubscriptionId = invoice.subscription ? String(invoice.subscription) : null;
|
||||||
|
if (!stripeSubscriptionId) break;
|
||||||
|
|
||||||
|
const subQ = await pool.query(`
|
||||||
|
SELECT id, company_id
|
||||||
|
FROM protection_subscriptions
|
||||||
|
WHERE stripe_subscription_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
`, [stripeSubscriptionId]);
|
||||||
|
|
||||||
|
if (subQ.rowCount === 0) {
|
||||||
|
console.warn(`⚠️ invoice.paid sin subscription local para ${stripeSubscriptionId}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localSub = subQ.rows[0];
|
||||||
|
|
||||||
|
const periodStartUnix =
|
||||||
|
invoice.lines?.data?.[0]?.period?.start || null;
|
||||||
|
const periodEndUnix =
|
||||||
|
invoice.lines?.data?.[0]?.period?.end || null;
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
UPDATE protection_subscriptions
|
||||||
|
SET
|
||||||
|
payment_status = 'pagado',
|
||||||
|
status = 'activo',
|
||||||
|
stripe_customer_id = COALESCE($1, stripe_customer_id),
|
||||||
|
current_period_start = COALESCE(to_timestamp($2), current_period_start),
|
||||||
|
current_period_end = COALESCE(to_timestamp($3), current_period_end),
|
||||||
|
renewal_date = CASE
|
||||||
|
WHEN $3 IS NOT NULL THEN to_timestamp($3)::date
|
||||||
|
ELSE renewal_date
|
||||||
|
END,
|
||||||
|
last_payment_at = NOW(),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = $4
|
||||||
|
`, [
|
||||||
|
invoice.customer ? String(invoice.customer) : null,
|
||||||
|
periodStartUnix,
|
||||||
|
periodEndUnix,
|
||||||
|
localSub.id
|
||||||
|
]);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO protection_activity (company_id, type, description)
|
||||||
|
VALUES ($1, 'cobro', $2)
|
||||||
|
`, [
|
||||||
|
localSub.company_id,
|
||||||
|
`Cobro mensual confirmado por Stripe para la suscripción #${localSub.id}.`
|
||||||
|
]);
|
||||||
|
|
||||||
|
await insertarEventoPago({
|
||||||
|
subscriptionId: localSub.id,
|
||||||
|
companyId: localSub.company_id,
|
||||||
|
stripeInvoiceId: invoice.id || null,
|
||||||
|
stripePaymentIntentId: invoice.payment_intent || null,
|
||||||
|
stripeEventId: event.id,
|
||||||
|
amount: ((invoice.amount_paid || 0) / 100),
|
||||||
|
currency: invoice.currency || "eur",
|
||||||
|
status: "paid",
|
||||||
|
eventType: event.type,
|
||||||
|
paidAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ [STRIPE] invoice.paid procesado para sub local ${localSub.id}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// 3. FACTURA FALLIDA
|
||||||
|
// =====================================================
|
||||||
|
case "invoice.payment_failed": {
|
||||||
|
const invoice = event.data.object;
|
||||||
|
const stripeSubscriptionId = invoice.subscription ? String(invoice.subscription) : null;
|
||||||
|
if (!stripeSubscriptionId) break;
|
||||||
|
|
||||||
|
const subQ = await pool.query(`
|
||||||
|
SELECT id, company_id
|
||||||
|
FROM protection_subscriptions
|
||||||
|
WHERE stripe_subscription_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
`, [stripeSubscriptionId]);
|
||||||
|
|
||||||
|
if (subQ.rowCount === 0) break;
|
||||||
|
|
||||||
|
const localSub = subQ.rows[0];
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
UPDATE protection_subscriptions
|
||||||
|
SET
|
||||||
|
payment_status = 'impagado',
|
||||||
|
status = 'suspendido',
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = $1
|
||||||
|
`, [localSub.id]);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO protection_activity (company_id, type, description)
|
||||||
|
VALUES ($1, 'cobro', $2)
|
||||||
|
`, [
|
||||||
|
localSub.company_id,
|
||||||
|
`Pago fallido detectado por Stripe en la suscripción #${localSub.id}.`
|
||||||
|
]);
|
||||||
|
|
||||||
|
await insertarEventoPago({
|
||||||
|
subscriptionId: localSub.id,
|
||||||
|
companyId: localSub.company_id,
|
||||||
|
stripeInvoiceId: invoice.id || null,
|
||||||
|
stripePaymentIntentId: invoice.payment_intent || null,
|
||||||
|
stripeEventId: event.id,
|
||||||
|
amount: ((invoice.amount_due || 0) / 100),
|
||||||
|
currency: invoice.currency || "eur",
|
||||||
|
status: "failed",
|
||||||
|
eventType: event.type,
|
||||||
|
paidAt: null
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`⚠️ [STRIPE] invoice.payment_failed para sub local ${localSub.id}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// 4. SUSCRIPCIÓN ACTUALIZADA
|
||||||
|
// =====================================================
|
||||||
|
case "customer.subscription.updated": {
|
||||||
|
const subscription = event.data.object;
|
||||||
|
const stripeSubscriptionId = subscription.id ? String(subscription.id) : null;
|
||||||
|
if (!stripeSubscriptionId) break;
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
UPDATE protection_subscriptions
|
||||||
|
SET
|
||||||
|
stripe_customer_id = COALESCE($1, stripe_customer_id),
|
||||||
|
current_period_start = COALESCE(to_timestamp($2), current_period_start),
|
||||||
|
current_period_end = COALESCE(to_timestamp($3), current_period_end),
|
||||||
|
renewal_date = CASE
|
||||||
|
WHEN $3 IS NOT NULL THEN to_timestamp($3)::date
|
||||||
|
ELSE renewal_date
|
||||||
|
END,
|
||||||
|
cancel_at_period_end = COALESCE($4, cancel_at_period_end),
|
||||||
|
status = CASE
|
||||||
|
WHEN $5 = 'active' THEN 'activo'
|
||||||
|
WHEN $5 IN ('past_due', 'unpaid') THEN 'suspendido'
|
||||||
|
WHEN $5 IN ('canceled', 'incomplete_expired') THEN 'cancelado'
|
||||||
|
ELSE status
|
||||||
|
END,
|
||||||
|
payment_status = CASE
|
||||||
|
WHEN $5 = 'active' THEN 'pagado'
|
||||||
|
WHEN $5 IN ('past_due', 'unpaid') THEN 'impagado'
|
||||||
|
ELSE payment_status
|
||||||
|
END,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE stripe_subscription_id = $6
|
||||||
|
`, [
|
||||||
|
subscription.customer ? String(subscription.customer) : null,
|
||||||
|
subscription.current_period_start || null,
|
||||||
|
subscription.current_period_end || null,
|
||||||
|
subscription.cancel_at_period_end ?? false,
|
||||||
|
subscription.status || null,
|
||||||
|
stripeSubscriptionId
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log(`🔄 [STRIPE] customer.subscription.updated ${stripeSubscriptionId}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// 5. SUSCRIPCIÓN CANCELADA / TERMINADA
|
||||||
|
// =====================================================
|
||||||
|
case "customer.subscription.deleted": {
|
||||||
|
const subscription = event.data.object;
|
||||||
|
const stripeSubscriptionId = subscription.id ? String(subscription.id) : null;
|
||||||
|
if (!stripeSubscriptionId) break;
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
UPDATE protection_subscriptions
|
||||||
|
SET
|
||||||
|
status = 'cancelado',
|
||||||
|
cancel_at_period_end = false,
|
||||||
|
cancelled_at = NOW(),
|
||||||
|
ended_at = NOW(),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE stripe_subscription_id = $1
|
||||||
|
`, [stripeSubscriptionId]);
|
||||||
|
|
||||||
|
console.log(`🛑 [STRIPE] customer.subscription.deleted ${stripeSubscriptionId}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log(`ℹ️ Evento Stripe ignorado: ${event.type}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({ received: true });
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error("❌ Error grave procesando Webhook de Stripe:", e.message);
|
||||||
|
return res.status(500).send(`Webhook Error: ${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Límites de subida para logotipos en Base64
|
// Límites de subida para logotipos en Base64
|
||||||
app.use(express.json({ limit: '10mb' }));
|
app.use(express.json({ limit: '10mb' }));
|
||||||
app.use(express.urlencoded({ limit: '10mb', extended: true }));
|
app.use(express.urlencoded({ limit: '10mb', extended: true }));
|
||||||
@@ -47,6 +452,8 @@ const {
|
|||||||
EVOLUTION_INSTANCE,
|
EVOLUTION_INSTANCE,
|
||||||
OPENAI_API_KEY, // 🔔 LEER LLAVE
|
OPENAI_API_KEY, // 🔔 LEER LLAVE
|
||||||
OPENAI_MODEL // 🔔 LEER MODELO
|
OPENAI_MODEL // 🔔 LEER MODELO
|
||||||
|
STRIPE_WEBHOOK_SECRET
|
||||||
|
|
||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
// --- 2. INICIALIZACIÓN GLOBAL DEL MOTOR IA (ESTO ES LO QUE TE FALTABA) ---
|
// --- 2. INICIALIZACIÓN GLOBAL DEL MOTOR IA (ESTO ES LO QUE TE FALTABA) ---
|
||||||
@@ -59,6 +466,12 @@ console.log("------------------------------------------------");
|
|||||||
console.log("🚀 VERSIÓN COMPLETA - INTEGRA REPARA SAAS");
|
console.log("🚀 VERSIÓN COMPLETA - INTEGRA REPARA SAAS");
|
||||||
console.log("------------------------------------------------");
|
console.log("------------------------------------------------");
|
||||||
|
|
||||||
|
if (!STRIPE_WEBHOOK_SECRET) {
|
||||||
|
console.error("⚠️ AVISO: Falta STRIPE_WEBHOOK_SECRET en variables de entorno.");
|
||||||
|
} else {
|
||||||
|
console.log("✅ Stripe Webhook Secret detectado.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!OPENAI_API_KEY) {
|
if (!OPENAI_API_KEY) {
|
||||||
console.error("⚠️ AVISO: Falta OPENAI_API_KEY en variables de entorno.");
|
console.error("⚠️ AVISO: Falta OPENAI_API_KEY en variables de entorno.");
|
||||||
} else {
|
} else {
|
||||||
@@ -4856,6 +5269,61 @@ app.post("/public/portal/:token/budget/:id/checkout", async (req, res) => {
|
|||||||
|
|
||||||
// B) WEBHOOK DE STRIPE (El chivatazo invisible que avisa cuando el cliente YA ha pagado)
|
// B) WEBHOOK DE STRIPE (El chivatazo invisible que avisa cuando el cliente YA ha pagado)
|
||||||
app.post("/webhook/stripe", async (req, res) => {
|
app.post("/webhook/stripe", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const event = req.body;
|
||||||
|
|
||||||
|
if (event.type === 'checkout.session.completed') {
|
||||||
|
const session = event.data.object;
|
||||||
|
const ownerId = session.metadata.owner_id;
|
||||||
|
const amountTotal = (session.amount_total / 100).toFixed(2);
|
||||||
|
const paymentType = session.metadata.type;
|
||||||
|
|
||||||
|
if (paymentType === 'protection_plan') {
|
||||||
|
const subId = session.metadata.subscription_id;
|
||||||
|
console.log(`💰 [STRIPE WEBHOOK] Pago de Seguro PREM-${subId} por ${amountTotal}€`);
|
||||||
|
|
||||||
|
await pool.query("UPDATE protection_subscriptions SET payment_status = 'pagado', status = 'activo' WHERE id = $1 AND company_id = $2", [subId, ownerId]);
|
||||||
|
await pool.query("INSERT INTO protection_activity (company_id, type, description) VALUES ($1, 'cobro', $2)", [ownerId, `Pago de suscripción inicial confirmado (${amountTotal}€)`]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const budgetId = session.metadata.budget_id;
|
||||||
|
console.log(`💰 [STRIPE WEBHOOK] ¡PAGO RECIBIDO! Presupuesto PRE-${budgetId} por ${amountTotal}€`);
|
||||||
|
|
||||||
|
await pool.query("UPDATE budgets SET status = 'paid' WHERE id = $1 AND owner_id = $2", [budgetId, ownerId]);
|
||||||
|
|
||||||
|
const sq = await pool.query("SELECT id, raw_data FROM scraped_services WHERE service_ref = $1 AND owner_id = $2", [`PRE-${budgetId}`, ownerId]);
|
||||||
|
if (sq.rowCount > 0) {
|
||||||
|
const serviceId = sq.rows[0].id;
|
||||||
|
let rawData = sq.rows[0].raw_data || {};
|
||||||
|
rawData.is_paid = true;
|
||||||
|
|
||||||
|
await pool.query("UPDATE scraped_services SET raw_data = $1 WHERE id = $2", [JSON.stringify(rawData), serviceId]);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
INSERT INTO service_financials (scraped_id, amount, payment_method, is_paid)
|
||||||
|
VALUES ($1, $2, 'Tarjeta (Stripe)', true)
|
||||||
|
ON CONFLICT (scraped_id) DO UPDATE SET is_paid = true, payment_method = 'Tarjeta (Stripe)'
|
||||||
|
`, [serviceId, amountTotal]);
|
||||||
|
|
||||||
|
await pool.query("INSERT INTO scraped_service_logs (scraped_id, user_name, action, details) VALUES ($1, $2, $3, $4)",
|
||||||
|
[serviceId, "Stripe API", "Pago Confirmado", `El cliente ha abonado ${amountTotal}€ por pasarela segura.`]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownerQ = await pool.query("SELECT phone FROM users WHERE id = $1", [ownerId]);
|
||||||
|
if (ownerQ.rowCount > 0) {
|
||||||
|
const msgWa = `💰 *¡PAGO RECIBIDO (STRIPE)!*\n\nSe acaba de confirmar el pago con tarjeta del presupuesto *PRE-${budgetId}* por un importe de *${amountTotal}€*.\n\nEl sistema lo ha marcado como pagado automáticamente.`;
|
||||||
|
sendWhatsAppAuto(ownerQ.rows[0].phone, msgWa, `cliente_${ownerId}`, false).catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ received: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("❌ Error grave procesando Webhook de Stripe:", e.message);
|
||||||
|
res.status(400).send(`Webhook Error: ${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const event = req.body;
|
const event = req.body;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user