/** * Public payment-icon serving — Phase 5.x. * * GET /assets/payment-icons/:slug.svg * Returns the idn-finlogos SVG for `slug` with a 1-year immutable cache * header. Content is stable per backend deploy (icons change only when the * `idn-finlogos` npm dep is bumped); clients can cache aggressively. * * Public on purpose — these are brand-mark icons, not sensitive data. The * catalog endpoint (`GET /api/client/payment-methods`) is still authenticated; * leaking the icon URL by itself reveals nothing useful. * * 404 on unknown slug. We deliberately do NOT 200-with-placeholder here — * upstream owns the "show placeholder" fallback, and 404ing tells operators * about typo'd slugs in the CC payment-method form. */ import { createReadStream } from 'fs' import { hasIconSlug, resolveIconPath } from '../../services/payment-icon.service.js' const SLUG_RE = /^[a-z0-9][a-z0-9-]{0,63}$/ export const paymentIconRoutes = async (app) => { app.get('/payment-icons/:slug.svg', async (request, reply) => { const { slug } = request.params // Guard against path-traversal / oversized slug before touching the FS. if (!SLUG_RE.test(slug) || !hasIconSlug(slug)) { return reply.code(404).send({ success: false, error: { code: 'NOT_FOUND', message: 'Unknown payment icon slug' }, }) } return reply .type('image/svg+xml') .header('Cache-Control', 'public, max-age=31536000, immutable') .send(createReadStream(resolveIconPath(slug))) }) }