michangarrito/apps/backend/dist/modules/billing/billing.service.js
rckrdmrd 48dea7a5d0 feat: Initial commit - michangarrito
Marketplace móvil para negocios locales mexicanos.

Estructura inicial:
- apps/backend (NestJS API)
- apps/frontend (React Web)
- apps/mobile (Expo/React Native)
- apps/mcp-server (Claude MCP Server)
- apps/whatsapp-service (WhatsApp Business API)
- database/ (PostgreSQL DDL)
- docs/ (Documentación)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:41:02 -06:00

220 lines
9.8 KiB
JavaScript

"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