import { Request, Response, NextFunction } from 'express'; import { prisma } from '../../config/database'; import type { PaymentSettings } from '@prisma/client'; import type { UpdatePaymentSettingsInput } from './payments.schemas'; import { encrypt, decrypt } from '../../utils/crypto'; import { resetStripeClient } from '../../services/stripe.client'; const ENCRYPTED_FIELDS = ['stripeSecretKey', 'stripeWebhookSecret'] as const; const SENSITIVE_FIELDS = ['stripeSecretKey', 'stripeWebhookSecret'] as const; function decryptSettings(settings: PaymentSettings): PaymentSettings { for (const field of ENCRYPTED_FIELDS) { const value = settings[field]; if (typeof value === 'string' && value) { (settings as Record)[field] = decrypt(value); } } return settings; } export const paymentSettingsService = { /** Full settings with decrypted secrets (admin use) */ async get(): Promise { let settings = await prisma.paymentSettings.findFirst(); if (!settings) { settings = await prisma.paymentSettings.create({ data: {} }); } return decryptSettings(settings); }, /** Public-safe settings (strips secret keys) */ async getPublic() { const settings = await this.get(); const result = { ...settings } as Record; for (const field of SENSITIVE_FIELDS) { delete result[field]; } return result; }, async update(data: UpdatePaymentSettingsInput): Promise { const toWrite = { ...data } as Record; // Encrypt sensitive fields, skipping masked sentinel values from the admin UI for (const field of ENCRYPTED_FIELDS) { if (field in toWrite && typeof toWrite[field] === 'string') { const val = toWrite[field] as string; if (!val || val.startsWith('••••')) { // Empty or mask string submitted — preserve existing encrypted value delete toWrite[field]; continue; } toWrite[field] = encrypt(val); } } // Handle donationSuggestedAmounts as JSON if (data.donationSuggestedAmounts) { toWrite.donationSuggestedAmounts = JSON.stringify(data.donationSuggestedAmounts); } const existing = await prisma.paymentSettings.findFirst(); let settings: PaymentSettings; if (existing) { settings = await prisma.paymentSettings.update({ where: { id: existing.id }, data: toWrite, }); } else { settings = await prisma.paymentSettings.create({ data: toWrite }); } // Reset Stripe client so it picks up new keys resetStripeClient(); return decryptSettings(settings); }, }; /** Middleware: reject requests when payments are disabled in site settings */ export async function requirePaymentsEnabled(_req: Request, res: Response, next: NextFunction) { try { const settings = await prisma.siteSettings.findFirst({ select: { enablePayments: true } }); if (!settings?.enablePayments) { res.status(403).json({ error: { message: 'Payments are not enabled', code: 'PAYMENTS_DISABLED' } }); return; } next(); } catch (err) { next(err); } }