diff --git a/server.ts b/server.ts index ecfaa5b..a10c3af 100644 --- a/server.ts +++ b/server.ts @@ -286,9 +286,10 @@ app.get('/api/courses/:id', authenticateToken, (req: AuthenticatedRequest, res: }); // 5.5 Unlock Course -app.post('/api/courses/:id/unlock', authenticateToken, (req: AuthenticatedRequest, res: Response) => { +app.post('/api/courses/:id/unlock', authenticateToken, async (req: AuthenticatedRequest, res: Response) => { const courseId = req.params.id; const userId = req.user!.id; + const { billingType, creditCardInfo } = req.body; const db = loadDb(); const user = db.users.find(u => u.id === userId); @@ -303,29 +304,45 @@ app.post('/api/courses/:id/unlock', authenticateToken, (req: AuthenticatedReques return; } - if (!user.unlockedCourses) { - user.unlockedCourses = []; - } - if (!user.unlockedCourses.includes(courseId)) { - user.unlockedCourses.push(courseId); - } + try { + let customerId = user.asaasCustomerId; + if (!customerId) { + customerId = await AsaasService.createCustomer(user.name, user.email); + user.asaasCustomerId = customerId; + } - // Record an Asaas invoice/payment for this curso unlock - const newPayment: SubscriptionPayment = { - id: `pay_crs_${Math.random().toString(36).substring(2, 9)}`, - userId, - value: course.price || 49.90, - status: 'CONFIRMED', - billingType: 'PIX', - invoiceUrl: `https://www.asaas.com/i/unlock_${courseId}`, - dueDate: new Date().toISOString().split('T')[0], - paidAt: new Date().toISOString(), - createdAt: new Date().toISOString() - }; - db.payments.push(newPayment); + const asaasPayment = await AsaasService.createPayment( + customerId, + course.price || 49.90, + billingType || 'PIX', + `unlock_course_${courseId}`, + creditCardInfo + ); - saveDb(db); - res.json({ success: true, unlockedCourses: user.unlockedCourses }); + let pixData = null; + if ((billingType || 'PIX') === 'PIX') { + pixData = await AsaasService.getPixQrCode(asaasPayment.id); + } + + const newPayment: SubscriptionPayment = { + id: asaasPayment.id, + userId, + value: course.price || 49.90, + status: 'PENDING', + billingType: billingType || 'PIX', + invoiceUrl: asaasPayment.invoiceUrl, + dueDate: asaasPayment.dueDate || new Date().toISOString().split('T')[0], + paidAt: null, + createdAt: new Date().toISOString() + }; + db.payments.push(newPayment); + saveDb(db); + + res.json({ success: true, payment: newPayment, pixData }); + } catch (err: any) { + console.error('Course unlock error:', err); + res.status(500).json({ error: err.message || 'Erro ao processar pagamento do curso' }); + } }); // 6. Lesson Progress tracking @@ -964,13 +981,27 @@ app.post('/api/webhooks/asaas', (req: Request, res: Response) => { // Handle subscription access control if (event === 'PAYMENT_RECEIVED' || event === 'PAYMENT_CONFIRMED') { - // Liberar / manter acesso - user.subscriptionStatus = 'ACTIVE'; - console.log(`Webhook: Acesso LIBERADO para usuário ${user.email} (${user.name})`); + const description = payment.description || ''; + + if (description.startsWith('unlock_course_')) { + const courseId = description.replace('unlock_course_', ''); + if (!user.unlockedCourses) user.unlockedCourses = []; + if (!user.unlockedCourses.includes(courseId)) { + user.unlockedCourses.push(courseId); + } + console.log(`Webhook: Curso ${courseId} LIBERADO para usuário ${user.email}`); + } else { + // Liberar / manter acesso + user.subscriptionStatus = 'ACTIVE'; + console.log(`Webhook: Acesso LIBERADO para usuário ${user.email} (${user.name})`); + } } else if (event === 'PAYMENT_OVERDUE' || event === 'SUBSCRIPTION_DELETED') { - // Bloquear acesso automaticamente - user.subscriptionStatus = event === 'PAYMENT_OVERDUE' ? 'OVERDUE' : 'CANCELED'; - console.log(`Webhook: Acesso BLOQUEADO para usuário ${user.email} (${user.name}) - Motivo: ${event}`); + // Bloquear acesso automaticamente se for assinatura + const description = payment.description || ''; + if (!description.startsWith('unlock_course_')) { + user.subscriptionStatus = event === 'PAYMENT_OVERDUE' ? 'OVERDUE' : 'CANCELED'; + console.log(`Webhook: Acesso BLOQUEADO para usuário ${user.email} (${user.name}) - Motivo: ${event}`); + } } } else { console.warn(`Webhook: Cliente Asaas ${customerId} não encontrado no banco local.`); diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx index 21a64de..40771d8 100644 --- a/src/components/AdminDashboard.tsx +++ b/src/components/AdminDashboard.tsx @@ -773,7 +773,7 @@ export default function AdminDashboard({ token }: AdminDashboardProps) { className="w-full bg-black/40 border border-white/10 rounded-lg p-3 text-sm text-white focus:outline-none focus:border-primary transition-colors min-h-[120px]" placeholder="Escreva aqui as instruções de ajuda para os alunos..." /> -
O texto que aparece para o aluno em Meus Dados -> Ajuda.
+O texto que aparece para o aluno em Meus Dados -> Ajuda.
diff --git a/src/components/CourseUnlockModal.tsx b/src/components/CourseUnlockModal.tsx index 2987bd4..a84e40d 100644 --- a/src/components/CourseUnlockModal.tsx +++ b/src/components/CourseUnlockModal.tsx @@ -16,6 +16,8 @@ export default function CourseUnlockModal({ course, onClose, onSuccess, token }: const [boletoCopied, setBoletoCopied] = useState(false); const [unlocked, setUnlocked] = useState(false); const [error, setError] = useState