feat: deposit module — QR flow, PayDepositPage, BookingDetailPanel widget

- Backend: GET /api/pay/:slug (public) — returns active hold confirmation URL
- PayDepositPage: public page /:slug/pay for guests (no login required)
- DepositSettingsPage: QR code section with print button
- BookingDetailPanel: DepositWidget in payment tab
  - Наличными / ЮКасса (QR) buttons
  - hold_created: copy link + refresh
  - hold_confirmed: release form (вернуть / списать)
  - Final status badges

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 11:22:55 +03:00
parent b91859218a
commit 754f03b7fa
7 changed files with 470 additions and 2 deletions

View File

@@ -288,6 +288,33 @@ const deposit: FastifyPluginAsync = async (fastify) => {
},
)
// ── GET /api/pay/:slug — public, no auth ─────────────────────────────────
fastify.get<{ Params: { slug: string } }>(
'/api/pay/:slug',
async (request, reply) => {
const { slug } = request.params
const hotelId = await getHotelId(slug)
if (!hotelId) return reply.code(404).send({ error: 'Not found' })
const { rows } = await db.query(
`SELECT d.yookassa_confirmation_url, d.amount, h.name AS hotel_name
FROM booking_deposits d
JOIN hotels h ON h.id = d.hotel_id
WHERE d.hotel_id = $1 AND d.status = 'hold_created'
ORDER BY d.created_at DESC LIMIT 1`,
[hotelId],
)
if (!rows[0]?.yookassa_confirmation_url) {
return reply.code(404).send({ error: 'No active payment' })
}
return {
confirmationUrl: rows[0].yookassa_confirmation_url,
amount: rows[0].amount,
hotelName: rows[0].hotel_name,
}
},
)
// ── POST /api/webhooks/yookassa ───────────────────────────────────────────
fastify.post<{ Body: { event: string; object: { id: string; status: string } } }>(
'/api/webhooks/yookassa',