UNPKG

skailan-core

Version:

Servicio de autenticación y multitenancy para Skailan.

308 lines 11.9 kB
import { TrialService } from "../services/TrialService.js"; import { ProductService } from "../../product/domain/services/ProductService.js"; export class PaymentAccessMiddleware { static instance; prisma; trialService; productService; constructor(prisma) { this.prisma = prisma; this.trialService = TrialService.getInstance(prisma); this.productService = ProductService.getInstance(prisma); } static getInstance(prisma) { if (!PaymentAccessMiddleware.instance) { PaymentAccessMiddleware.instance = new PaymentAccessMiddleware(prisma); } return PaymentAccessMiddleware.instance; } /** * Middleware principal para controlar acceso basado en pagos */ checkPaymentAccess(options = {}) { return async (req, res, next) => { try { // Verificar que el usuario esté autenticado if (!req.user) { return res.status(401).json({ error: "Usuario no autenticado", code: "UNAUTHORIZED", }); } // Obtener la organización del usuario const membership = await this.prisma.membership.findFirst({ where: { userId: req.user.id, status: "ACTIVE", }, include: { organization: true, }, }); if (!membership) { return res.status(403).json({ error: "Usuario no pertenece a ninguna organización activa", code: "NO_ORGANIZATION", }); } req.organization = membership.organization; // Verificar si la organización está bloqueada if (membership.organization.isBlocked) { return res.status(403).json({ error: "Organización bloqueada por falta de pago", code: "ORGANIZATION_BLOCKED", details: { reason: "payment_required", trialEnd: membership.organization.trialEnd, }, }); } // Si solo requiere trial o pago activo if (options.requireTrialOrPayment) { const canAccess = await this.trialService.canAccess(membership.organization.id); if (!canAccess) { return res.status(403).json({ error: "Acceso denegado: trial expirado y sin pago activo", code: "ACCESS_DENIED", details: { reason: "trial_expired_no_payment", trialEnd: membership.organization.trialEnd, }, }); } } // Si requiere pago activo específicamente if (options.requireActivePayment) { const hasActiveSubscription = await this.hasActiveSubscription(membership.organization.id); if (!hasActiveSubscription) { if (options.redirectToPayment) { return res.status(402).json({ error: "Pago requerido", code: "PAYMENT_REQUIRED", redirect: "/billing", }); } else { return res.status(403).json({ error: "Pago activo requerido", code: "ACTIVE_PAYMENT_REQUIRED", }); } } } // Verificar características específicas if (options.allowedFeatures && options.allowedFeatures.length > 0) { const hasFeature = await this.checkFeatures(membership.organization.id, options.allowedFeatures); if (!hasFeature) { return res.status(403).json({ error: "Característica no disponible en el plan actual", code: "FEATURE_NOT_AVAILABLE", details: { requiredFeatures: options.allowedFeatures, }, }); } } // Verificar límites de usuarios si es necesario if (options.checkUserLimits) { const userLimitExceeded = await this.checkUserLimits(membership.organization.id); if (userLimitExceeded) { return res.status(403).json({ error: "Límite de usuarios excedido", code: "USER_LIMIT_EXCEEDED", details: { currentUsers: userLimitExceeded.current, maxUsers: userLimitExceeded.max, }, }); } } next(); } catch (error) { console.error("Error en PaymentAccessMiddleware:", error); return res.status(500).json({ error: "Error interno del servidor", code: "INTERNAL_ERROR", }); } }; } /** * Middleware para verificar trial activo */ checkTrialAccess() { return async (req, res, next) => { try { if (!req.organization) { return res.status(400).json({ error: "Organización no encontrada en el request", code: "NO_ORGANIZATION", }); } const trialStatus = await this.trialService.getTrialStatus(req.organization.id); if (!trialStatus.isActive) { return res.status(403).json({ error: "Trial no activo", code: "TRIAL_NOT_ACTIVE", details: { trialStatus, }, }); } // Agregar información del trial al request req.trialStatus = trialStatus; next(); } catch (error) { console.error("Error en checkTrialAccess:", error); return res.status(500).json({ error: "Error interno del servidor", code: "INTERNAL_ERROR", }); } }; } /** * Middleware para verificar suscripción activa */ checkActiveSubscription() { return async (req, res, next) => { try { if (!req.organization) { return res.status(400).json({ error: "Organización no encontrada en el request", code: "NO_ORGANIZATION", }); } const hasActiveSubscription = await this.hasActiveSubscription(req.organization.id); if (!hasActiveSubscription) { return res.status(403).json({ error: "Suscripción activa requerida", code: "ACTIVE_SUBSCRIPTION_REQUIRED", }); } next(); } catch (error) { console.error("Error en checkActiveSubscription:", error); return res.status(500).json({ error: "Error interno del servidor", code: "INTERNAL_ERROR", }); } }; } /** * Middleware para verificar características específicas */ checkFeatureAccess(featureCode) { return async (req, res, next) => { try { if (!req.organization) { return res.status(400).json({ error: "Organización no encontrada en el request", code: "NO_ORGANIZATION", }); } const hasFeature = await this.checkFeatures(req.organization.id, [ featureCode, ]); if (!hasFeature) { return res.status(403).json({ error: `Característica '${featureCode}' no disponible`, code: "FEATURE_NOT_AVAILABLE", details: { requiredFeature: featureCode, }, }); } next(); } catch (error) { console.error("Error en checkFeatureAccess:", error); return res.status(500).json({ error: "Error interno del servidor", code: "INTERNAL_ERROR", }); } }; } /** * Verifica si la organización tiene una suscripción activa */ async hasActiveSubscription(organizationId) { const subscription = await this.prisma.subscription.findFirst({ where: { organizationId, status: "ACTIVE", }, }); return !!subscription; } /** * Verifica si la organización tiene acceso a las características especificadas */ async checkFeatures(organizationId, featureCodes) { // Obtener la suscripción activa const subscription = await this.prisma.subscription.findFirst({ where: { organizationId, status: "ACTIVE", }, include: { plan: { include: { planFeatures: { include: { feature: true, }, }, }, }, }, }); if (!subscription) { return false; } // Verificar cada característica requerida for (const featureCode of featureCodes) { const hasFeature = subscription.plan.planFeatures.some((pf) => pf.feature.code === featureCode && pf.feature.isActive); if (!hasFeature) { return false; } } return true; } /** * Verifica los límites de usuarios */ async checkUserLimits(organizationId) { const subscription = await this.prisma.subscription.findFirst({ where: { organizationId, status: "ACTIVE", }, include: { plan: true, }, }); if (!subscription || !subscription.plan.maxUsers) { return null; // Sin límite } const currentUsers = await this.prisma.membership.count({ where: { organizationId, status: "ACTIVE", }, }); if (currentUsers >= subscription.plan.maxUsers) { return { current: currentUsers, max: subscription.plan.maxUsers, }; } return null; } } //# sourceMappingURL=PaymentAccessMiddleware.js.map