feat: YooKassa webhook, payment timeout, reserved status, BookingConfirmPage
- Add YooKassa webhook handler (POST /api/yookassa/webhook) that updates booking to confirmed+paid on payment.succeeded, cancelled on payment.canceled - Add public status endpoint GET /api/online-bookings/:id for return URL polling - Add 'reserved' booking status (violet) shown in calendar while awaiting payment - Add 'website' to BookingSource type - Payment timeout job (every 2 min) auto-cancels expired unpaid bookings - Widget setting: "Время ожидания оплаты" (5–60 min, default 15) - BookingConfirmPage at /booking-confirm/:id — polls status, countdown timer, shows success/pending/cancelled state - Widget bookings now use status='reserved' instead of 'inquiry' when YooKassa is configured; set to 'confirmed' by webhook on payment.succeeded - Migration 070: add 'reserved' to bookings status constraint + payment_expires_at Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ export interface WidgetSettings {
|
||||
showRental: boolean
|
||||
roomDisplayMode: 'rooms' | 'categories'
|
||||
minNights: number
|
||||
paymentTimeout: number // minutes to wait for payment before auto-cancel
|
||||
paymentProvider: 'yukassa' | 'tinkoff' | 'cloudpayments' | 'none'
|
||||
showPromo: boolean
|
||||
allowExtraBeds: boolean
|
||||
@@ -1142,6 +1143,7 @@ export function BookingWidgetPage() {
|
||||
showRental: (hs as any).widgetShowRental !== undefined ? Boolean((hs as any).widgetShowRental) : prev.showRental,
|
||||
roomDisplayMode: (String((hs as any).widgetRoomMode ?? prev.roomDisplayMode)) as 'rooms' | 'categories',
|
||||
minNights: (hs as any).widgetMinNights !== undefined ? Number((hs as any).widgetMinNights) : prev.minNights,
|
||||
paymentTimeout: (hs as any).widgetPaymentTimeout !== undefined ? Number((hs as any).widgetPaymentTimeout) : prev.paymentTimeout,
|
||||
showPromo: (hs as any).widgetShowPromo !== undefined ? Boolean((hs as any).widgetShowPromo) : prev.showPromo,
|
||||
allowExtraBeds: (hs as any).widgetExtraBeds !== undefined ? Boolean((hs as any).widgetExtraBeds) : prev.allowExtraBeds,
|
||||
allowChildren: (hs as any).widgetChildren !== undefined ? Boolean((hs as any).widgetChildren) : prev.allowChildren,
|
||||
@@ -1158,6 +1160,7 @@ export function BookingWidgetPage() {
|
||||
showRental: rentalActive,
|
||||
roomDisplayMode: 'rooms',
|
||||
minNights: 1,
|
||||
paymentTimeout: 15,
|
||||
paymentProvider: 'yukassa',
|
||||
showPromo: true,
|
||||
allowExtraBeds: true,
|
||||
@@ -1188,6 +1191,7 @@ export function BookingWidgetPage() {
|
||||
widget_show_rental: s.showRental,
|
||||
widget_room_mode: s.roomDisplayMode,
|
||||
widget_min_nights: s.minNights,
|
||||
widget_payment_timeout: s.paymentTimeout,
|
||||
widget_show_promo: s.showPromo,
|
||||
widget_extra_beds: s.allowExtraBeds,
|
||||
widget_children: s.allowChildren,
|
||||
@@ -1442,6 +1446,17 @@ export function BookingWidgetPage() {
|
||||
onChange={e => set('minNights', Math.max(1, parseInt(e.target.value) || 1))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">
|
||||
Время ожидания оплаты (минут)
|
||||
</label>
|
||||
<input
|
||||
type="number" min={5} max={60} className="input w-24"
|
||||
value={settings.paymentTimeout}
|
||||
onChange={e => set('paymentTimeout', Math.max(5, parseInt(e.target.value) || 15))}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-slate-400">По умолчанию 15 мин. Если не оплачено — бронь отменяется.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Additional services */}
|
||||
|
||||
Reference in New Issue
Block a user