diff --git a/Dockerfile b/Dockerfile index 12292e2..065ce06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,4 +24,4 @@ COPY --from=builder /app/dist ./dist EXPOSE 3000 -CMD ["node", "dist/server.cjs"] +CMD ["sh", "-c", "npx prisma db push && node dist/server.cjs"] diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8f2e96b..d262b8a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -256,6 +256,8 @@ model SystemSettings { asaasWebhookSecret String @default("") @map("asaas_webhook_secret") helpText String @db.Text @default("# Central de Ajuda") @map("help_text") enableBoleto Boolean @default(true) @map("enable_boleto") + enableInstallmentBoleto Boolean @default(false) @map("enable_installment_boleto") + maxBoletoInstallments Int @default(3) @map("max_boleto_installments") evolutionApiUrl String @default("https://evolution.microtecinformaticacurso.com.br") @map("evolution_api_url") evolutionApiKey String @default("") @map("evolution_api_key") diff --git a/server.ts b/server.ts index 049ebea..6c998ba 100644 --- a/server.ts +++ b/server.ts @@ -1218,6 +1218,8 @@ app.get('/api/settings', authenticateToken, async (req: AuthenticatedRequest, re trialDurationDays: settings?.trialDurationDays || 7, helpText: settings?.helpText || '', enableBoleto: settings?.enableBoleto !== false, + enableInstallmentBoleto: settings?.enableInstallmentBoleto === true, + maxBoletoInstallments: settings?.maxBoletoInstallments || 3, brandName: settings?.brandName || 'MICROTEC', brandSlogan: settings?.brandSlogan || 'Acelere seus estudos em informática e pacote Office no estilo Netflix.', cnpj: settings?.cnpj || '', @@ -2214,12 +2216,14 @@ async function startServer() { console.log(`DevFlix Server running on port ${PORT}`); }); - // Background job to clean up expired PIX payments (older than 5 minutes) + // Background job to clean up expired PIX payments (older than 5 minutes) and Boleto payments (older than 2 days) setInterval(async () => { try { const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); + const twoDaysAgo = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000); - const expiredPayments = await prisma.payment.findMany({ + // Clean PIX + const expiredPix = await prisma.payment.findMany({ where: { billingType: 'PIX', status: 'PENDING', @@ -2227,21 +2231,40 @@ async function startServer() { } }); - for (const payment of expiredPayments) { - console.log(`[PIX Expire Job] Expirando e deletando pagamento ${payment.id}...`); - + for (const payment of expiredPix) { + console.log(`[PIX Expire Job] Expirando e deletando PIX ${payment.id}...`); const deletedInAsaas = await AsaasService.deletePayment(payment.id); - if (deletedInAsaas) { await prisma.payment.update({ where: { id: payment.id }, data: { status: 'CANCELED' } }); - console.log(`[PIX Expire Job] Pagamento ${payment.id} cancelado com sucesso.`); + console.log(`[PIX Expire Job] PIX ${payment.id} cancelado com sucesso.`); + } + } + + // Clean BOLETO + const expiredBoleto = await prisma.payment.findMany({ + where: { + billingType: 'BOLETO', + status: 'PENDING', + createdAt: { lt: twoDaysAgo } + } + }); + + for (const payment of expiredBoleto) { + console.log(`[Boleto Expire Job] Expirando e deletando Boleto ${payment.id}...`); + const deletedInAsaas = await AsaasService.deletePayment(payment.id); + if (deletedInAsaas) { + await prisma.payment.update({ + where: { id: payment.id }, + data: { status: 'CANCELED' } + }); + console.log(`[Boleto Expire Job] Boleto ${payment.id} cancelado com sucesso.`); } } } catch (err: any) { - console.error('[PIX Expire Job] Erro ao limpar pagamentos expirados:', err.message); + console.error('[Expire Job] Erro ao limpar pagamentos expirados:', err.message); } }, 60 * 1000); // Check every 60 seconds (1 minute) } diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index 45d5d9b..1289409 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -980,10 +980,40 @@ export default function AdminDashboard({ token, adminUser, onLogout }: AdminDash />
Habilitar Boleto Bancário - Permite pagamentos via boleto. + Permite pagamentos à vista via boleto bancário.
+ +
+ +
+ + {settings.enableInstallmentBoleto && ( +
+ + setSettings({ ...settings, maxBoletoInstallments: parseInt(e.target.value) || 3 })} + className="w-full bg-black/40 border border-white/10 rounded-lg p-3 text-sm text-white focus:outline-none focus:border-[#E50914]" + placeholder="Ex: 3" + /> +
+ )} diff --git a/src/components/CourseUnlockModal.tsx b/src/components/CourseUnlockModal.tsx index 77fcfd6..77f4d5b 100644 --- a/src/components/CourseUnlockModal.tsx +++ b/src/components/CourseUnlockModal.tsx @@ -88,6 +88,8 @@ export default function CourseUnlockModal({ course, user, onClose, onSuccess, to }; const [enableBoleto, setEnableBoleto] = useState(true); + const [enableInstallmentBoleto, setEnableInstallmentBoleto] = useState(false); + const [maxBoletoInstallments, setMaxBoletoInstallments] = useState(3); useEffect(() => { if (token) { @@ -96,8 +98,16 @@ export default function CourseUnlockModal({ course, user, onClose, onSuccess, to }) .then(res => res.json()) .then(data => { - if (data && data.enableBoleto === false) { - setEnableBoleto(false); + if (data) { + if (data.enableBoleto === false) { + setEnableBoleto(false); + } + if (data.enableInstallmentBoleto === true) { + setEnableInstallmentBoleto(true); + } + if (data.maxBoletoInstallments) { + setMaxBoletoInstallments(data.maxBoletoInstallments); + } } }) .catch(() => {}); @@ -160,7 +170,7 @@ export default function CourseUnlockModal({ course, user, onClose, onSuccess, to bodyData.planId = selectedPlanId; } else { endpoint = `/api/courses/${course.id}/unlock`; - bodyData.installmentCount = paymentMethod === 'CREDIT_CARD' ? installments : 1; + bodyData.installmentCount = (paymentMethod === 'CREDIT_CARD' || (paymentMethod === 'BOLETO' && enableInstallmentBoleto)) ? installments : 1; } const res = await fetch(endpoint, { @@ -304,7 +314,7 @@ export default function CourseUnlockModal({ course, user, onClose, onSuccess, to

Boleto Bancário

-

- O boleto será gerado no valor de R$ {currentPrice.toFixed(2)}. O prazo de compensação é de 1 a 2 dias úteis. -

+ + {!paymentData ? ( +

+ O boleto será gerado no valor de R$ {currentPrice.toFixed(2)}. O prazo de compensação é de 1 a 2 dias úteis. +

+ ) : ( +

+ Boleto gerado com sucesso no valor de R$ {currentPrice.toFixed(2)} {paymentData.payment?.installmentCount > 1 ? `parcelado em ${paymentData.payment.installmentCount}x` : ''}. +

+ )} + + {!paymentData && enableInstallmentBoleto && (!selectedPlanObj || selectedPlanObj.cycle === 'LIFETIME') && ( +
+ + +
+ )} +
{paymentData?.payment?.invoiceUrl ? (