"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var BillingService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.BillingService = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("@nestjs/typeorm"); const typeorm_2 = require("typeorm"); const stripe_service_1 = require("./stripe.service"); const subscription_entity_1 = require("../subscriptions/entities/subscription.entity"); const plan_entity_1 = require("../subscriptions/entities/plan.entity"); const token_balance_entity_1 = require("../subscriptions/entities/token-balance.entity"); const token_usage_entity_1 = require("../subscriptions/entities/token-usage.entity"); let BillingService = BillingService_1 = class BillingService { constructor(stripeService, subscriptionRepo, planRepo, tokenBalanceRepo, tokenUsageRepo) { this.stripeService = stripeService; this.subscriptionRepo = subscriptionRepo; this.planRepo = planRepo; this.tokenBalanceRepo = tokenBalanceRepo; this.tokenUsageRepo = tokenUsageRepo; this.logger = new common_1.Logger(BillingService_1.name); this.tokenPackages = [ { code: 'tokens_1000', name: '1,000 Tokens', tokens: 1000, priceMxn: 29 }, { code: 'tokens_3000', name: '3,000 Tokens', tokens: 3000, priceMxn: 69 }, { code: 'tokens_8000', name: '8,000 Tokens', tokens: 8000, priceMxn: 149 }, { code: 'tokens_20000', name: '20,000 Tokens', tokens: 20000, priceMxn: 299 }, ]; } async getPlans() { return this.planRepo.find({ where: { status: 'active' }, order: { priceMonthly: 'ASC' }, }); } getTokenPackages() { return this.tokenPackages; } async createSubscriptionCheckout(tenantId, planCode, successUrl, cancelUrl) { const plan = await this.planRepo.findOne({ where: { code: planCode, status: 'active' } }); if (!plan || !plan.stripePriceIdMonthly) { throw new common_1.NotFoundException('Plan no encontrado'); } const subscription = await this.subscriptionRepo.findOne({ where: { tenantId } }); if (!subscription?.stripeCustomerId) { throw new common_1.BadRequestException('Cliente de Stripe no configurado'); } const session = await this.stripeService.createCheckoutSession({ customerId: subscription.stripeCustomerId, priceId: plan.stripePriceIdMonthly, mode: 'subscription', successUrl, cancelUrl, metadata: { tenantId, planCode }, }); return { checkoutUrl: session.url }; } async createTokenPurchaseCheckout(tenantId, packageCode, successUrl, cancelUrl) { const tokenPackage = this.tokenPackages.find((p) => p.code === packageCode); if (!tokenPackage) { throw new common_1.NotFoundException('Paquete de tokens no encontrado'); } const subscription = await this.subscriptionRepo.findOne({ where: { tenantId } }); if (!subscription?.stripeCustomerId) { throw new common_1.BadRequestException('Cliente de Stripe no configurado'); } const paymentIntent = await this.stripeService.createPaymentIntent({ amount: tokenPackage.priceMxn * 100, customerId: subscription.stripeCustomerId, metadata: { tenantId, packageCode, tokens: tokenPackage.tokens.toString(), }, }); return { checkoutUrl: paymentIntent.client_secret }; } async createPortalSession(tenantId, returnUrl) { const subscription = await this.subscriptionRepo.findOne({ where: { tenantId } }); if (!subscription?.stripeCustomerId) { throw new common_1.BadRequestException('Cliente de Stripe no configurado'); } const session = await this.stripeService.createPortalSession({ customerId: subscription.stripeCustomerId, returnUrl, }); return { portalUrl: session.url }; } async handleSubscriptionCreated(stripeSubscriptionId, stripeCustomerId, stripePriceId) { let plan = await this.planRepo.findOne({ where: { stripePriceIdMonthly: stripePriceId } }); if (!plan) { plan = await this.planRepo.findOne({ where: { stripePriceIdYearly: stripePriceId } }); } if (!plan) { this.logger.warn(`Plan not found for price: ${stripePriceId}`); return; } const subscription = await this.subscriptionRepo.findOne({ where: { stripeCustomerId }, }); if (subscription) { subscription.planId = plan.id; subscription.stripeSubscriptionId = stripeSubscriptionId; subscription.status = subscription_entity_1.SubscriptionStatus.ACTIVE; subscription.currentPeriodStart = new Date(); subscription.currentPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); await this.subscriptionRepo.save(subscription); if (plan.includedTokens > 0) { await this.addTokensToBalance(subscription.tenantId, plan.includedTokens, 'subscription'); } this.logger.log(`Subscription activated for tenant: ${subscription.tenantId}`); } } async handleSubscriptionCancelled(stripeSubscriptionId) { const subscription = await this.subscriptionRepo.findOne({ where: { stripeSubscriptionId }, }); if (subscription) { subscription.status = subscription_entity_1.SubscriptionStatus.CANCELLED; subscription.cancelledAt = new Date(); await this.subscriptionRepo.save(subscription); this.logger.log(`Subscription cancelled for tenant: ${subscription.tenantId}`); } } async handleTokenPurchase(tenantId, packageCode, tokens) { await this.addTokensToBalance(tenantId, tokens, 'purchase'); this.logger.log(`Added ${tokens} tokens to tenant: ${tenantId}`); } async getTokenBalance(tenantId) { return this.tokenBalanceRepo.findOne({ where: { tenantId } }); } async addTokensToBalance(tenantId, tokens, source) { let balance = await this.tokenBalanceRepo.findOne({ where: { tenantId } }); if (!balance) { balance = this.tokenBalanceRepo.create({ tenantId, usedTokens: 0, availableTokens: 0, }); } balance.availableTokens += tokens; return this.tokenBalanceRepo.save(balance); } async consumeTokens(tenantId, tokens, action, description) { const balance = await this.tokenBalanceRepo.findOne({ where: { tenantId } }); if (!balance || balance.availableTokens < tokens) { return false; } balance.usedTokens += tokens; balance.availableTokens -= tokens; await this.tokenBalanceRepo.save(balance); const usage = this.tokenUsageRepo.create({ tenantId, tokensUsed: tokens, action, description, }); await this.tokenUsageRepo.save(usage); return true; } async getTokenUsageHistory(tenantId, limit = 50) { return this.tokenUsageRepo.find({ where: { tenantId }, order: { createdAt: 'DESC' }, take: limit, }); } async getBillingSummary(tenantId) { const subscription = await this.subscriptionRepo.findOne({ where: { tenantId }, relations: ['plan'], }); const tokenBalance = await this.getTokenBalance(tenantId); let invoices = []; if (subscription?.stripeCustomerId) { try { invoices = await this.stripeService.listInvoices(subscription.stripeCustomerId, 5); } catch (error) { this.logger.warn('Could not fetch invoices from Stripe'); } } return { subscription, plan: subscription?.plan || null, tokenBalance, invoices: invoices.map((inv) => ({ id: inv.id, amount: inv.amount_paid / 100, status: inv.status, date: new Date(inv.created * 1000), pdfUrl: inv.invoice_pdf, })), }; } }; exports.BillingService = BillingService; exports.BillingService = BillingService = BillingService_1 = __decorate([ (0, common_1.Injectable)(), __param(1, (0, typeorm_1.InjectRepository)(subscription_entity_1.Subscription)), __param(2, (0, typeorm_1.InjectRepository)(plan_entity_1.Plan)), __param(3, (0, typeorm_1.InjectRepository)(token_balance_entity_1.TokenBalance)), __param(4, (0, typeorm_1.InjectRepository)(token_usage_entity_1.TokenUsage)), __metadata("design:paramtypes", [stripe_service_1.StripeService, typeorm_2.Repository, typeorm_2.Repository, typeorm_2.Repository, typeorm_2.Repository]) ], BillingService); //# sourceMappingURL=billing.service.js.map