import { FastifyPluginAsync } from 'fastify' import { db } from '../db' type SlugParam = { Params: { slug: string } } type SlugIdParam = { Params: { slug: string; id: string } } const MODULES = ['deposit', 'booking-widget', 'room-service'] as const function canAccess(userSlug: string | null, role: string, slug: string) { if (role === 'super_admin') return true return userSlug === slug } const paymentGateways: FastifyPluginAsync = async (fastify) => { const getHotelId = async (slug: string): Promise => { const { rows } = await db.query('SELECT id FROM hotels WHERE slug = $1', [slug]) return rows[0]?.id ?? null } // ── GET /api/hotels/:slug/payment-gateways ───────────────────────────────── fastify.get('/api/hotels/:slug/payment-gateways', { onRequest: [fastify.authenticate] }, async (req, reply) => { const { slug } = req.params if (!canAccess(req.user.hotelSlug, req.user.role, slug)) return reply.code(403).send({ error: 'Forbidden' }) const hotelId = await getHotelId(slug) if (!hotelId) return reply.code(404).send({ error: 'Not found' }) const { rows } = await db.query( `SELECT id, provider, label, shop_id, CASE WHEN secret_key IS NOT NULL THEN '••••••••' ELSE NULL END AS secret_key, currency, is_active, modules, created_at FROM hotel_payment_gateways WHERE hotel_id = $1 ORDER BY created_at`, [hotelId], ) return rows.map(r => ({ id: r.id, provider: r.provider, label: r.label, shopId: r.shop_id, secretKey: r.secret_key, currency: r.currency, isActive: r.is_active, modules: r.modules ?? MODULES, createdAt: r.created_at, autoConfirmOnPayment: r.auto_confirm_on_payment ?? true, })) }) // ── POST /api/hotels/:slug/payment-gateways ──────────────────────────────── fastify.post( '/api/hotels/:slug/payment-gateways', { onRequest: [fastify.authenticate] }, async (req, reply) => { const { slug } = req.params if (!canAccess(req.user.hotelSlug, req.user.role, slug)) return reply.code(403).send({ error: 'Forbidden' }) const hotelId = await getHotelId(slug) if (!hotelId) return reply.code(404).send({ error: 'Not found' }) const { provider = 'yookassa', label, shopId, secretKey, currency = 'RUB', modules = MODULES } = req.body const { rows } = await db.query( `INSERT INTO hotel_payment_gateways (hotel_id, provider, label, shop_id, secret_key, currency, modules) VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *`, [hotelId, provider, label, shopId || null, secretKey || null, currency, JSON.stringify(modules)], ) const r = rows[0] return { id: r.id, provider: r.provider, label: r.label, shopId: r.shop_id, secretKey: r.secret_key ? '••••••••' : null, currency: r.currency, isActive: r.is_active, modules: r.modules, autoConfirmOnPayment: r.auto_confirm_on_payment ?? true } } ) // ── PATCH /api/hotels/:slug/payment-gateways/:id ─────────────────────────── fastify.patch( '/api/hotels/:slug/payment-gateways/:id', { onRequest: [fastify.authenticate] }, async (req, reply) => { const { slug, id } = req.params if (!canAccess(req.user.hotelSlug, req.user.role, slug)) return reply.code(403).send({ error: 'Forbidden' }) const hotelId = await getHotelId(slug) if (!hotelId) return reply.code(404).send({ error: 'Not found' }) const { label, shopId, secretKey, currency, isActive, modules, autoConfirmOnPayment } = req.body const { rows } = await db.query( `UPDATE hotel_payment_gateways SET label = COALESCE($1, label), shop_id = COALESCE($2, shop_id), secret_key = CASE WHEN $3 IS NOT NULL AND $3 != '••••••••' THEN $3 ELSE secret_key END, currency = COALESCE($4, currency), is_active = COALESCE($5, is_active), modules = COALESCE($6::jsonb, modules), auto_confirm_on_payment = COALESCE($7, auto_confirm_on_payment) WHERE id = $8 AND hotel_id = $9 RETURNING *`, [label ?? null, shopId ?? null, secretKey ?? null, currency ?? null, isActive ?? null, modules ? JSON.stringify(modules) : null, autoConfirmOnPayment ?? null, id, hotelId], ) if (!rows[0]) return reply.code(404).send({ error: 'Not found' }) const r = rows[0] return { id: r.id, provider: r.provider, label: r.label, shopId: r.shop_id, secretKey: r.secret_key ? '••••••••' : null, currency: r.currency, isActive: r.is_active, modules: r.modules, autoConfirmOnPayment: r.auto_confirm_on_payment ?? true } } ) // ── DELETE /api/hotels/:slug/payment-gateways/:id ────────────────────────── fastify.delete('/api/hotels/:slug/payment-gateways/:id', { onRequest: [fastify.authenticate] }, async (req, reply) => { const { slug, id } = req.params if (!canAccess(req.user.hotelSlug, req.user.role, slug)) return reply.code(403).send({ error: 'Forbidden' }) const hotelId = await getHotelId(slug) if (!hotelId) return reply.code(404).send({ error: 'Not found' }) await db.query('DELETE FROM hotel_payment_gateways WHERE id = $1 AND hotel_id = $2', [id, hotelId]) return { ok: true } }) } export default paymentGateways // Helper: get active gateway for a given module export async function getGatewayForModule(hotelId: string, module: string) { const { rows } = await db.query( `SELECT * FROM hotel_payment_gateways WHERE hotel_id = $1 AND is_active = true AND modules @> $2::jsonb ORDER BY created_at LIMIT 1`, [hotelId, JSON.stringify([module])], ) return rows[0] ?? null }