Files
the_order/services/finance/src/index.ts
defiQUG 92cc41d26d Add Legal Office seal and complete Azure CDN deployment
- Add Legal Office of the Master seal (SVG design with Maltese Cross, scales of justice, legal scroll)
- Create legal-office-manifest-template.json for Legal Office credentials
- Update SEAL_MAPPING.md and DESIGN_GUIDE.md with Legal Office seal documentation
- Complete Azure CDN infrastructure deployment:
  - Resource group, storage account, and container created
  - 17 PNG seal files uploaded to Azure Blob Storage
  - All manifest templates updated with Azure URLs
  - Configuration files generated (azure-cdn-config.env)
- Add comprehensive Azure CDN setup scripts and documentation
- Fix manifest URL generation to prevent double slashes
- Verify all seals accessible via HTTPS
2025-11-12 22:03:42 -08:00

246 lines
6.2 KiB
TypeScript

/**
* Finance Service
* Handles payments, ledgers, rate models, and invoicing
*/
import Fastify, { type FastifyRequest, type FastifyReply } 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';
const logger = createLogger('finance-service');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const server: any = Fastify({
logger: logger as any,
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<void> {
// 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 as any);
addCorrelationId(server as any);
addRequestLogging(server as any);
server.setErrorHandler(errorHandler as any);
}
// 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',
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
preHandler: [authenticateJWT as any, requireRole('admin', 'accountant', 'finance') as any],
schema: {
...createBodySchema(CreateLedgerEntrySchema),
description: 'Create a ledger entry',
tags: ['ledger'],
response: {
201: {
type: 'object',
properties: {
entry: {
type: 'object',
},
},
},
},
},
},
async (request: FastifyRequest, reply: FastifyReply) => {
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',
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
preHandler: [authenticateJWT as any],
schema: {
...createBodySchema(CreatePaymentSchema),
description: 'Process a payment',
tags: ['payments'],
response: {
201: {
type: 'object',
properties: {
payment: {
type: 'object',
},
},
},
},
},
},
async (request: FastifyRequest, reply: FastifyReply) => {
const body = request.body as {
amount: number;
currency: string;
paymentMethod: string;
metadata?: Record<string, string>;
};
// 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();