fix: unify YooKassa webhook to single URL + add webhook instruction in UI
- Consolidate online booking payment handling into existing /api/webhooks/yookassa (was previously split — deposit.ts had partial handling, new route had full) - deposit.ts webhook now fully handles online_bookings: reserved→confirmed, payment_status=paid, paid_amount, WS broadcast, and confirmation email - Remove POST /api/yookassa/webhook (keep only GET /api/online-bookings/:id) - PaymentSettingsPage: add webhook URL instruction panel with copy button showing https://api.hotelsync.ru/api/webhooks/yookassa and required events Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { FastifyPluginAsync } from 'fastify'
|
||||
import { db } from '../db'
|
||||
import { createHold, createCharge, capturePayment, cancelPayment, createRefund } from '../services/yookassa'
|
||||
import { transporter } from '../email'
|
||||
import { transporter, sendBookingConfirmedEmail } from '../email'
|
||||
import { broadcast } from './ws'
|
||||
|
||||
type SlugParam = { Params: { slug: string } }
|
||||
type SlugBookingParam = { Params: { slug: string; bookingId: string } }
|
||||
@@ -731,32 +732,68 @@ ${refund > 0 && (!items || items.length === 0) ? `<p style="color:#16a34a">Ос
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-confirm booking on successful online widget payment
|
||||
if (object.status === 'succeeded') {
|
||||
const { rows: obRows } = await db.query(
|
||||
`SELECT ob.booking_id, ob.hotel_id
|
||||
// Handle online widget booking payment
|
||||
if (object.status === 'succeeded' || object.status === 'canceled') {
|
||||
const { rows: obRows } = await db.query<{
|
||||
id: string; booking_id: string | null; hotel_id: string; slug: string
|
||||
guest_name: string; guest_email: string | null
|
||||
check_in: string; check_out: string; total_amount: string; hotel_name: string
|
||||
}>(
|
||||
`SELECT ob.id, ob.booking_id, ob.hotel_id, h.slug, h.name AS hotel_name,
|
||||
ob.guest_name, ob.guest_email, ob.check_in, ob.check_out, ob.total_amount
|
||||
FROM online_bookings ob
|
||||
WHERE ob.yookassa_payment_id = $1`,
|
||||
JOIN hotels h ON h.id = ob.hotel_id
|
||||
WHERE ob.yookassa_payment_id = $1
|
||||
LIMIT 1`,
|
||||
[object.id],
|
||||
)
|
||||
if (obRows[0]?.booking_id) {
|
||||
await db.query(
|
||||
`UPDATE online_bookings SET yookassa_status = 'succeeded' WHERE yookassa_payment_id = $1`,
|
||||
[object.id],
|
||||
)
|
||||
// Check gateway auto_confirm_on_payment
|
||||
const { rows: gwRows } = await db.query(
|
||||
`SELECT auto_confirm_on_payment FROM hotel_payment_gateways
|
||||
WHERE hotel_id = $1 AND is_active = true
|
||||
ORDER BY created_at LIMIT 1`,
|
||||
[obRows[0].hotel_id],
|
||||
)
|
||||
const autoConfirm = gwRows[0]?.auto_confirm_on_payment !== false // default true
|
||||
if (autoConfirm) {
|
||||
if (obRows[0]) {
|
||||
const ob = obRows[0]
|
||||
if (object.status === 'succeeded') {
|
||||
await db.query(
|
||||
`UPDATE bookings SET status = 'confirmed' WHERE id = $1 AND status = 'inquiry'`,
|
||||
[obRows[0].booking_id],
|
||||
`UPDATE online_bookings SET status = 'paid', yookassa_status = 'succeeded' WHERE id = $1`,
|
||||
[ob.id],
|
||||
)
|
||||
if (ob.booking_id) {
|
||||
const { rows: bRows } = await db.query(
|
||||
`UPDATE bookings
|
||||
SET status = 'confirmed',
|
||||
payment_status = 'paid',
|
||||
paid_amount = total_amount,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1 RETURNING *`,
|
||||
[ob.booking_id],
|
||||
)
|
||||
if (bRows[0]) {
|
||||
broadcast(ob.slug, { type: 'booking:updated', booking: bRows[0] })
|
||||
}
|
||||
}
|
||||
if (ob.guest_email) {
|
||||
sendBookingConfirmedEmail({
|
||||
to: ob.guest_email,
|
||||
guestName: ob.guest_name,
|
||||
hotelName: ob.hotel_name,
|
||||
checkIn: ob.check_in,
|
||||
checkOut: ob.check_out,
|
||||
totalAmount: ob.total_amount,
|
||||
bookingConfirmUrl: `https://app.hotelsync.ru/booking-confirm/${ob.id}`,
|
||||
}).catch(() => {})
|
||||
}
|
||||
} else {
|
||||
await db.query(
|
||||
`UPDATE online_bookings SET status = 'cancelled', yookassa_status = 'canceled' WHERE id = $1`,
|
||||
[ob.id],
|
||||
)
|
||||
if (ob.booking_id) {
|
||||
const { rows: bRows } = await db.query(
|
||||
`UPDATE bookings SET status = 'cancelled', updated_at = NOW()
|
||||
WHERE id = $1 RETURNING *`,
|
||||
[ob.booking_id],
|
||||
)
|
||||
if (bRows[0]) {
|
||||
broadcast(ob.slug, { type: 'booking:updated', booking: bRows[0] })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user