/** * Finance Service * Handles payments, ledgers, rate models, and invoicing */ import Fastify from 'fastify'; import fastifySwagger from '@fastify/swagger'; import fastifySwaggerUI from '@fastify/swagger-ui'; import { errorHandler, createLogger, registerSecurityPlugins, addCorrelationId, addRequestLogging, getEnv, createBodySchema, authenticateJWT, requireRole, } from '@the-order/shared'; import { CreateLedgerEntrySchema, CreatePaymentSchema } from '@the-order/schemas'; import { healthCheck as dbHealthCheck, getPool, createLedgerEntry, createPayment, updatePaymentStatus } from '@the-order/database'; import { StripePaymentGateway } from '@the-order/payment-gateway'; import { randomUUID } from 'crypto'; const logger = createLogger('finance-service'); const server = Fastify({ logger, requestIdLogLabel: 'requestId', disableRequestLogging: false, }); // Initialize database pool const env = getEnv(); if (env.DATABASE_URL) { getPool({ connectionString: env.DATABASE_URL }); } // Initialize payment gateway let paymentGateway: StripePaymentGateway | null = null; try { if (env.PAYMENT_GATEWAY_API_KEY) { paymentGateway = new StripePaymentGateway(); } } catch (error) { logger.warn({ err: error }, 'Payment gateway not configured'); } // Initialize server async function initializeServer(): Promise { // Register Swagger const swaggerUrl = env.SWAGGER_SERVER_URL || (env.NODE_ENV === 'development' ? 'http://localhost:4003' : undefined); if (!swaggerUrl) { logger.warn('SWAGGER_SERVER_URL not set, Swagger documentation will not be available'); } else { await server.register(fastifySwagger, { openapi: { info: { title: 'Finance Service API', description: 'Payments, ledgers, rate models, and invoicing', version: '1.0.0', }, servers: [ { url: swaggerUrl, description: env.NODE_ENV || 'Development server', }, ], }, }); await server.register(fastifySwaggerUI, { routePrefix: '/docs', }); } await registerSecurityPlugins(server); addCorrelationId(server); addRequestLogging(server); server.setErrorHandler(errorHandler); } // Health check server.get( '/health', { schema: { description: 'Health check endpoint', tags: ['health'], response: { 200: { type: 'object', properties: { status: { type: 'string' }, service: { type: 'string' }, database: { type: 'string' }, }, }, }, }, }, async () => { const dbHealthy = await dbHealthCheck(); return { status: dbHealthy ? 'ok' : 'degraded', service: 'finance', database: dbHealthy ? 'connected' : 'disconnected', }; } ); // Ledger operations server.post( '/ledger/entry', { preHandler: [authenticateJWT, requireRole('admin', 'accountant', 'finance')], schema: { ...createBodySchema(CreateLedgerEntrySchema), description: 'Create a ledger entry', tags: ['ledger'], response: { 201: { type: 'object', properties: { entry: { type: 'object', }, }, }, }, }, }, async (request, reply) => { const body = request.body as { accountId: string; type: 'debit' | 'credit'; amount: number; currency: string; description?: string; reference?: string; }; // Save to database const entry = await createLedgerEntry({ account_id: body.accountId, type: body.type, amount: body.amount, currency: body.currency, description: body.description, reference: body.reference, }); return reply.status(201).send({ entry }); } ); // Payment processing server.post( '/payments', { preHandler: [authenticateJWT], schema: { ...createBodySchema(CreatePaymentSchema), description: 'Process a payment', tags: ['payments'], response: { 201: { type: 'object', properties: { payment: { type: 'object', }, }, }, }, }, }, async (request, reply) => { const body = request.body as { amount: number; currency: string; paymentMethod: string; metadata?: Record; }; // Create payment record const payment = await createPayment({ amount: body.amount, currency: body.currency, status: 'pending', payment_method: body.paymentMethod, }); // Process payment through gateway if available if (paymentGateway) { try { const result = await paymentGateway.processPayment( body.amount, body.currency, body.paymentMethod, { payment_id: payment.id, ...body.metadata, } ); // Update payment status const updatedPayment = await updatePaymentStatus( payment.id, result.status, result.transactionId, result.gatewayResponse ); return reply.status(201).send({ payment: updatedPayment }); } catch (error) { logger.error({ err: error, paymentId: payment.id }, 'Payment processing failed'); await updatePaymentStatus(payment.id, 'failed', undefined, { error: String(error) }); throw error; } } else { // No payment gateway configured - return pending status return reply.status(201).send({ payment }); } } ); // Start server const start = async () => { try { await initializeServer(); const env = getEnv(); const port = env.PORT || 4003; await server.listen({ port, host: '0.0.0.0' }); logger.info({ port }, 'Finance service listening'); } catch (err) { logger.error({ err }, 'Failed to start server'); process.exit(1); } }; start();