Fix: preserve gross amount in webhook and reconstruct it in portal to fix double discount on paid items
This commit is contained in:
parent
7bada2a4e7
commit
58182ff53c
|
|
@ -0,0 +1,89 @@
|
|||
import pg from 'pg';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const pool = new pg.Pool({
|
||||
connectionString: process.env.DATABASE_URL || 'postgresql://edumanager:edumanager2024@localhost:5432/edumanager'
|
||||
});
|
||||
|
||||
async function debug() {
|
||||
try {
|
||||
// 1. Buscar todos os alunos
|
||||
const alunos = await pool.query('SELECT id, nome FROM alunos LIMIT 20');
|
||||
console.log('\n=== ALUNOS CADASTRADOS ===');
|
||||
alunos.rows.forEach(a => console.log(` ${a.id} | ${a.nome}`));
|
||||
|
||||
// 2. Buscar TODAS as cobranças do SQL
|
||||
const cobrancas = await pool.query(`
|
||||
SELECT asaas_payment_id, aluno_id, status,
|
||||
TO_CHAR(vencimento, 'YYYY-MM-DD') as vencimento,
|
||||
valor, amount_original, discount, description, type,
|
||||
TO_CHAR(data_pagamento, 'YYYY-MM-DD') as data_pagamento,
|
||||
transaction_receipt_url
|
||||
FROM alunos_cobrancas
|
||||
ORDER BY aluno_id, vencimento ASC
|
||||
`);
|
||||
|
||||
console.log(`\n=== COBRANÇAS NO SQL (${cobrancas.rows.length} total) ===`);
|
||||
const byStudent = {};
|
||||
cobrancas.rows.forEach(c => {
|
||||
if (!byStudent[c.aluno_id]) byStudent[c.aluno_id] = [];
|
||||
byStudent[c.aluno_id].push(c);
|
||||
});
|
||||
|
||||
for (const [alunoId, payments] of Object.entries(byStudent)) {
|
||||
const aluno = alunos.rows.find(a => a.id === alunoId);
|
||||
console.log(`\n 📌 ${aluno?.nome || alunoId} (${payments.length} cobranças):`);
|
||||
payments.forEach(p => {
|
||||
const status = p.status?.toUpperCase();
|
||||
const icon = status === 'PAGO' ? '✅' : status === 'PENDENTE' ? '⏳' : status === 'ATRASADO' ? '🔴' : '❓';
|
||||
console.log(` ${icon} ${p.asaas_payment_id} | ${p.vencimento} | R$${Number(p.valor).toFixed(2)} | Status: ${p.status} | Pago em: ${p.data_pagamento || '-'} | Desc: ${p.description || '-'} | amount_original: ${p.amount_original || '-'}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Buscar do JSON
|
||||
const dataPath = path.join(__dirname, '..', 'data', 'school_data.json');
|
||||
if (fs.existsSync(dataPath)) {
|
||||
const data = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
|
||||
const jsonPayments = data.payments || [];
|
||||
console.log(`\n=== COBRANÇAS NO JSON (${jsonPayments.length} total) ===`);
|
||||
|
||||
const jsonByStudent = {};
|
||||
jsonPayments.forEach(p => {
|
||||
if (!jsonByStudent[p.studentId]) jsonByStudent[p.studentId] = [];
|
||||
jsonByStudent[p.studentId].push(p);
|
||||
});
|
||||
|
||||
for (const [studentId, payments] of Object.entries(jsonByStudent)) {
|
||||
const aluno = alunos.rows.find(a => a.id === studentId);
|
||||
console.log(`\n 📌 ${aluno?.nome || studentId} (${payments.length} cobranças no JSON):`);
|
||||
payments.forEach(p => {
|
||||
const inSql = cobrancas.rows.find(c => c.asaas_payment_id === p.asaasPaymentId);
|
||||
console.log(` ${inSql ? '🔗' : '⚠️'} ${p.asaasPaymentId || 'SEM_ID'} | ${p.dueDate} | R$${p.amount} | Status: ${p.status} | Desc: ${p.description || '-'} | discount: ${p.discount || 0} | ${inSql ? 'Existe no SQL' : 'SÓ NO JSON!'}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Verificar cobranças que existem no SQL mas NÃO no JSON
|
||||
console.log('\n=== COBRANÇAS QUE EXISTEM NO SQL MAS NÃO NO JSON ===');
|
||||
let orphanCount = 0;
|
||||
cobrancas.rows.forEach(c => {
|
||||
const inJson = jsonPayments.find(p => p.asaasPaymentId === c.asaas_payment_id);
|
||||
if (!inJson) {
|
||||
orphanCount++;
|
||||
console.log(` ⚠️ ${c.asaas_payment_id} | Aluno: ${c.aluno_id} | ${c.vencimento} | R$${Number(c.valor).toFixed(2)} | Status: ${c.status}`);
|
||||
}
|
||||
});
|
||||
if (orphanCount === 0) console.log(' ✅ Nenhuma — tudo sincronizado.');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Erro:', err);
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
debug();
|
||||
|
|
@ -834,16 +834,20 @@ app.post('/api/webhook_asaas', async (req, res) => {
|
|||
statusStr === 'atrasado' ? 'overdue' :
|
||||
statusStr === 'cancelado' ? 'cancelled' : 'pending';
|
||||
|
||||
// Se for um evento de atualização de pagamento, atualiza o valor.
|
||||
// Se for só confirmação de recebimento, preserva o 'amount' bruto original para não causar double-discount.
|
||||
const shouldUpdateAmount = payload.event === 'PAYMENT_UPDATED' && updateData.valor;
|
||||
|
||||
appData.payments[pIdx] = {
|
||||
...p,
|
||||
status: newStatus,
|
||||
amount: updateData.valor || p.amount,
|
||||
amount: shouldUpdateAmount ? updateData.valor : p.amount,
|
||||
dueDate: updateData.vencimento || p.dueDate,
|
||||
paidDate: updateData.data_pagamento || p.paidDate
|
||||
};
|
||||
appData.lastUpdated = new Date().toISOString();
|
||||
await saveSchoolData(appData);
|
||||
console.log(`[Webhook:Sync] JSON atualizado para boleto ${asaasPaymentId}`);
|
||||
console.log(`[Webhook:Sync] JSON atualizado para boleto ${asaasPaymentId} (Amount: ${shouldUpdateAmount ? 'Atualizado' : 'Preservado'})`);
|
||||
}
|
||||
} catch (syncErr) {
|
||||
console.error('[Webhook:Sync] Erro ao sincronizar JSON:', syncErr.message);
|
||||
|
|
|
|||
|
|
@ -275,13 +275,21 @@ app.get('/api/portal/financeiro', authMiddleware, async (req, res) => {
|
|||
}
|
||||
}
|
||||
|
||||
let amountOriginal = jsonP.amount || Number(db.valor) || 0;
|
||||
const discount = jsonP.amount ? (jsonP.discount || 0) : 0;
|
||||
|
||||
// [Bugfix]: Recupera o valor bruto corrompido pelo webhook antigo
|
||||
if (amountOriginal === Number(db.valor) && discount > 0) {
|
||||
amountOriginal += discount;
|
||||
}
|
||||
|
||||
finalPayments.push({
|
||||
id: jsonP.id || asaasId,
|
||||
studentId: req.user.studentId,
|
||||
asaasPaymentId: asaasId,
|
||||
asaasPaymentUrl: jsonP.asaasPaymentUrl || null,
|
||||
amount: jsonP.amount || Number(db.valor) || 0,
|
||||
discount: jsonP.amount ? (jsonP.discount || 0) : 0,
|
||||
amount: amountOriginal,
|
||||
discount: discount,
|
||||
dueDate: db.vencimento || jsonP.dueDate,
|
||||
status: normalizedStatus,
|
||||
paidDate: db.data_pagamento || jsonP.paidDate || null,
|
||||
|
|
|
|||
|
|
@ -279,9 +279,16 @@ app.get('/api/portal/financeiro', authMiddleware, async (req, res) => {
|
|||
}
|
||||
|
||||
// amount_original = valor bruto (ex: 170), db.valor = valor líquido Asaas (ex: 150)
|
||||
const amountOriginal = Number(db.amount_original) || jsonP.amount || Number(db.valor) || 0;
|
||||
let amountOriginal = Number(db.amount_original) || jsonP.amount || Number(db.valor) || 0;
|
||||
const discount = Number(db.discount) || (jsonP.amount ? (jsonP.discount || 0) : 0);
|
||||
|
||||
// [Bugfix]: Se o amountOriginal for igual ao valor líquido (db.valor) e houver desconto,
|
||||
// significa que o webhook antigo sobrescreveu o valor bruto pelo líquido no JSON.
|
||||
// Neste caso, o valor bruto real é o líquido + desconto.
|
||||
if (amountOriginal === Number(db.valor) && discount > 0) {
|
||||
amountOriginal += discount;
|
||||
}
|
||||
|
||||
finalPayments.push({
|
||||
id: jsonP.id || asaasId,
|
||||
studentId: req.user.studentId,
|
||||
|
|
|
|||
Loading…
Reference in New Issue