Propagated modules: - payment-terminals: MercadoPago + Clip TPV (PCI-DSS compliant) - ai: Role-based AI access (ADMIN, DOCTOR, RECEPCIONISTA, PACIENTE) - mcp: 18 ERP tools for AI assistants Note: Payment data must NOT be stored in clinical records. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
164 lines
4.8 KiB
TypeScript
164 lines
4.8 KiB
TypeScript
/**
|
|
* Transactions Controller
|
|
*
|
|
* REST API endpoints for payment transactions
|
|
*/
|
|
|
|
import { Router, Request, Response, NextFunction } from 'express';
|
|
import { DataSource } from 'typeorm';
|
|
import { TransactionsService } from '../services';
|
|
import { ProcessPaymentDto, ProcessRefundDto, SendReceiptDto, TransactionFilterDto } from '../dto';
|
|
|
|
// Extend Request to include tenant info
|
|
interface AuthenticatedRequest extends Request {
|
|
tenantId?: string;
|
|
userId?: string;
|
|
}
|
|
|
|
export class TransactionsController {
|
|
public router: Router;
|
|
private service: TransactionsService;
|
|
|
|
constructor(dataSource: DataSource) {
|
|
this.router = Router();
|
|
this.service = new TransactionsService(dataSource);
|
|
this.initializeRoutes();
|
|
}
|
|
|
|
private initializeRoutes(): void {
|
|
// Stats
|
|
this.router.get('/stats', this.getStats.bind(this));
|
|
|
|
// Payment processing
|
|
this.router.post('/charge', this.processPayment.bind(this));
|
|
this.router.post('/refund', this.processRefund.bind(this));
|
|
|
|
// Transaction queries
|
|
this.router.get('/', this.getAll.bind(this));
|
|
this.router.get('/:id', this.getById.bind(this));
|
|
|
|
// Actions
|
|
this.router.post('/:id/receipt', this.sendReceipt.bind(this));
|
|
}
|
|
|
|
/**
|
|
* GET /payment-transactions/stats
|
|
* Get transaction statistics
|
|
*/
|
|
private async getStats(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const filter: TransactionFilterDto = {
|
|
branchId: req.query.branchId as string,
|
|
startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined,
|
|
endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined,
|
|
};
|
|
|
|
const stats = await this.service.getStats(req.tenantId!, filter);
|
|
res.json({ data: stats });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /payment-transactions/charge
|
|
* Process a payment
|
|
*/
|
|
private async processPayment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const dto: ProcessPaymentDto = req.body;
|
|
const result = await this.service.processPayment(req.tenantId!, req.userId!, dto);
|
|
|
|
if (result.success) {
|
|
res.status(201).json({ data: result });
|
|
} else {
|
|
res.status(400).json({ data: result });
|
|
}
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /payment-transactions/refund
|
|
* Process a refund
|
|
*/
|
|
private async processRefund(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const dto: ProcessRefundDto = req.body;
|
|
const result = await this.service.processRefund(req.tenantId!, req.userId!, dto);
|
|
|
|
if (result.success) {
|
|
res.json({ data: result });
|
|
} else {
|
|
res.status(400).json({ data: result });
|
|
}
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /payment-transactions
|
|
* Get transactions with filters
|
|
*/
|
|
private async getAll(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const filter: TransactionFilterDto = {
|
|
branchId: req.query.branchId as string,
|
|
userId: req.query.userId as string,
|
|
status: req.query.status as any,
|
|
sourceType: req.query.sourceType as any,
|
|
terminalProvider: req.query.terminalProvider as string,
|
|
startDate: req.query.startDate ? new Date(req.query.startDate as string) : undefined,
|
|
endDate: req.query.endDate ? new Date(req.query.endDate as string) : undefined,
|
|
limit: req.query.limit ? parseInt(req.query.limit as string) : undefined,
|
|
offset: req.query.offset ? parseInt(req.query.offset as string) : undefined,
|
|
};
|
|
|
|
const result = await this.service.findAll(req.tenantId!, filter);
|
|
res.json(result);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /payment-transactions/:id
|
|
* Get transaction by ID
|
|
*/
|
|
private async getById(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const transaction = await this.service.findById(req.params.id, req.tenantId!);
|
|
|
|
if (!transaction) {
|
|
res.status(404).json({ error: 'Transaction not found' });
|
|
return;
|
|
}
|
|
|
|
res.json({ data: transaction });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /payment-transactions/:id/receipt
|
|
* Send receipt for transaction
|
|
*/
|
|
private async sendReceipt(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const dto: SendReceiptDto = req.body;
|
|
const result = await this.service.sendReceipt(req.params.id, req.tenantId!, dto);
|
|
|
|
if (result.success) {
|
|
res.json({ success: true });
|
|
} else {
|
|
res.status(400).json({ success: false, error: result.error });
|
|
}
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
}
|