Payment Terminals (MercadoPago + Clip): - TenantTerminalConfig, TerminalPayment, TerminalWebhookEvent entities - MercadoPagoService: payments, refunds, links, webhooks - ClipService: payments, refunds, links, webhooks - Controllers for authenticated and webhook endpoints - Retry with exponential backoff - Multi-tenant credential management AI Role-Based Access: - ERPRole config: ADMIN, SUPERVISOR, OPERATOR, CUSTOMER - 70+ tools mapped to roles - System prompts per role (admin, supervisor, operator, customer) - RoleBasedAIService with tool filtering - OpenRouter integration - Rate limiting per role Based on: michangarrito INT-004, INT-005, MCH-012, MCH-013 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
166 lines
4.3 KiB
TypeScript
166 lines
4.3 KiB
TypeScript
/**
|
|
* MercadoPago Controller
|
|
*
|
|
* Endpoints para pagos con MercadoPago
|
|
*/
|
|
|
|
import { Router, Request, Response, NextFunction } from 'express';
|
|
import { DataSource } from 'typeorm';
|
|
import { MercadoPagoService } from '../services/mercadopago.service';
|
|
|
|
export class MercadoPagoController {
|
|
public router: Router;
|
|
private mercadoPagoService: MercadoPagoService;
|
|
|
|
constructor(private dataSource: DataSource) {
|
|
this.router = Router();
|
|
this.mercadoPagoService = new MercadoPagoService(dataSource);
|
|
this.initializeRoutes();
|
|
}
|
|
|
|
private initializeRoutes(): void {
|
|
// Pagos
|
|
this.router.post('/payments', this.createPayment.bind(this));
|
|
this.router.get('/payments/:id', this.getPayment.bind(this));
|
|
this.router.post('/payments/:id/refund', this.refundPayment.bind(this));
|
|
|
|
// Links de pago
|
|
this.router.post('/links', this.createPaymentLink.bind(this));
|
|
}
|
|
|
|
/**
|
|
* POST /mercadopago/payments
|
|
* Crear un nuevo pago
|
|
*/
|
|
private async createPayment(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const tenantId = this.getTenantId(req);
|
|
const userId = this.getUserId(req);
|
|
|
|
const payment = await this.mercadoPagoService.createPayment(
|
|
tenantId,
|
|
{
|
|
amount: req.body.amount,
|
|
currency: req.body.currency,
|
|
description: req.body.description,
|
|
paymentMethod: req.body.paymentMethod,
|
|
customerEmail: req.body.customerEmail,
|
|
customerName: req.body.customerName,
|
|
referenceType: req.body.referenceType,
|
|
referenceId: req.body.referenceId,
|
|
metadata: req.body.metadata,
|
|
},
|
|
userId
|
|
);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: this.sanitizePayment(payment),
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /mercadopago/payments/:id
|
|
* Obtener estado de un pago
|
|
*/
|
|
private async getPayment(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const tenantId = this.getTenantId(req);
|
|
const paymentId = req.params.id;
|
|
|
|
const payment = await this.mercadoPagoService.getPayment(tenantId, paymentId);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: this.sanitizePayment(payment),
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /mercadopago/payments/:id/refund
|
|
* Reembolsar un pago
|
|
*/
|
|
private async refundPayment(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const tenantId = this.getTenantId(req);
|
|
const paymentId = req.params.id;
|
|
|
|
const payment = await this.mercadoPagoService.refundPayment(tenantId, {
|
|
paymentId,
|
|
amount: req.body.amount,
|
|
reason: req.body.reason,
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: this.sanitizePayment(payment),
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /mercadopago/links
|
|
* Crear un link de pago
|
|
*/
|
|
private async createPaymentLink(req: Request, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const tenantId = this.getTenantId(req);
|
|
const userId = this.getUserId(req);
|
|
|
|
const link = await this.mercadoPagoService.createPaymentLink(
|
|
tenantId,
|
|
{
|
|
amount: req.body.amount,
|
|
title: req.body.title,
|
|
description: req.body.description,
|
|
expiresAt: req.body.expiresAt ? new Date(req.body.expiresAt) : undefined,
|
|
referenceType: req.body.referenceType,
|
|
referenceId: req.body.referenceId,
|
|
},
|
|
userId
|
|
);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
data: link,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtener tenant ID del request
|
|
*/
|
|
private getTenantId(req: Request): string {
|
|
const tenantId = (req as any).tenantId || (req as any).user?.tenantId;
|
|
if (!tenantId) {
|
|
throw new Error('Tenant ID not found in request');
|
|
}
|
|
return tenantId;
|
|
}
|
|
|
|
/**
|
|
* Obtener user ID del request
|
|
*/
|
|
private getUserId(req: Request): string | undefined {
|
|
return (req as any).userId || (req as any).user?.id;
|
|
}
|
|
|
|
/**
|
|
* Sanitizar pago para respuesta (ocultar datos sensibles)
|
|
*/
|
|
private sanitizePayment(payment: any): any {
|
|
const { providerResponse, ...safe } = payment;
|
|
return safe;
|
|
}
|
|
}
|