import { useState, useEffect } from 'react' import { Plus, Trash2, Pencil, Check, X, Loader2, CreditCard, GripVertical, ArrowUp, ArrowDown, Zap, Eye, EyeOff } from 'lucide-react' import { api, type HotelPaymentMethod, type PaymentGateway } from '../lib/api' import { useAuth } from '../contexts/AuthContext' import { cn } from '../lib/utils' const GATEWAY_MODULES = [ { id: 'deposit', label: 'Депозит (QR)' }, { id: 'booking-widget', label: 'Онлайн-бронирование' }, { id: 'room-service', label: 'Room Service' }, ] const CURRENCIES = ['RUB', 'USD', 'EUR', 'GBP', 'CNY', 'AED', 'KZT', 'BYN', 'AMD', 'GEL'] const METHOD_TYPES: Array<{ id: HotelPaymentMethod['type']; label: string }> = [ { id: 'cash', label: 'Наличные' }, { id: 'card', label: 'Банковская карта' }, { id: 'transfer', label: 'Перевод / СБП' }, { id: 'other', label: 'Другое' }, ] const CHECKIN_OPTIONS: Array<{ id: 'none' | 'soft' | 'hard'; label: string; desc: string; color: string }> = [ { id: 'none', label: 'Не контролировать', desc: 'Заселение без проверки оплаты', color: 'border-slate-200 dark:border-slate-600' }, { id: 'soft', label: 'Предупреждение', desc: 'Показать предупреждение, но разрешить заселить', color: 'border-amber-400 dark:border-amber-500' }, { id: 'hard', label: 'Блокировать', desc: 'Запретить заселение пока не оплачено', color: 'border-red-400 dark:border-red-500' }, ] interface EditState { name: string currency: string type: HotelPaymentMethod['type'] } export function PaymentSettingsPage() { const { user } = useAuth() const slug = user?.hotelSlug ?? '' const [methods, setMethods] = useState([]) const [loading, setLoading] = useState(true) const [requireCheckin, setRequireCheckin] = useState<'none' | 'soft' | 'hard'>('none') const [savingCheckin, setSavingCheckin] = useState(false) const [editingId, setEditingId] = useState(null) const [editState, setEditState] = useState({ name: '', currency: 'RUB', type: 'cash' }) const [adding, setAdding] = useState(false) const [newState, setNewState] = useState({ name: '', currency: 'RUB', type: 'cash' }) const [addingRow, setAddingRow] = useState(false) // Payment gateways const [gateways, setGateways] = useState([]) const [gwFormOpen, setGwFormOpen] = useState(false) const [gwEditId, setGwEditId] = useState(null) const [gwLabel, setGwLabel] = useState('ЮКасса') const [gwShopId, setGwShopId] = useState('') const [gwSecretKey, setGwSecretKey] = useState('') const [gwCurrency, setGwCurrency] = useState('RUB') const [gwModules, setGwModules] = useState(['deposit', 'booking-widget', 'room-service']) const [gwSaving, setGwSaving] = useState(false) const [gwSecretVisible, setGwSecretVisible] = useState(false) useEffect(() => { if (!slug) return Promise.all([ api.paymentMethods.list(slug), api.paymentMethods.getSettings(slug).catch(() => ({ requirePaymentCheckin: 'none' as const })), api.paymentGateways.list(slug).catch(() => []), ]).then(([ms, s, gws]) => { setMethods(ms) setRequireCheckin(s.requirePaymentCheckin) setGateways(gws) }).finally(() => setLoading(false)) }, [slug]) const handleCheckinChange = async (val: 'none' | 'soft' | 'hard') => { setRequireCheckin(val) setSavingCheckin(true) try { await api.paymentMethods.updateSettings(slug, val) } catch { // revert would need old value — just keep it } finally { setSavingCheckin(false) } } const handleAdd = async () => { if (!newState.name.trim()) return setAddingRow(true) try { const m = await api.paymentMethods.create(slug, { name: newState.name.trim(), currency: newState.currency, type: newState.type, sortOrder: methods.length, }) setMethods(p => [...p, m]) setAdding(false) setNewState({ name: '', currency: 'RUB', type: 'cash' }) } catch { /* ignore */ } finally { setAddingRow(false) } } const startEdit = (m: HotelPaymentMethod) => { setEditingId(m.id) setEditState({ name: m.name, currency: m.currency, type: m.type }) } const saveEdit = async (id: string) => { if (!editState.name.trim()) return try { const updated = await api.paymentMethods.update(slug, id, { name: editState.name.trim(), currency: editState.currency, type: editState.type, }) setMethods(p => p.map(m => m.id === id ? updated : m)) } catch { /* ignore */ } setEditingId(null) } const toggleActive = async (m: HotelPaymentMethod) => { try { const updated = await api.paymentMethods.update(slug, m.id, { isActive: !m.isActive }) setMethods(p => p.map(x => x.id === m.id ? updated : x)) } catch { /* ignore */ } } const handleDelete = async (id: string) => { if (!confirm('Удалить способ оплаты?')) return await api.paymentMethods.remove(slug, id).catch(() => {}) setMethods(p => p.filter(m => m.id !== id)) } const openGwForm = (gw?: PaymentGateway) => { if (gw) { setGwEditId(gw.id); setGwLabel(gw.label); setGwShopId(gw.shopId ?? '') setGwSecretKey(''); setGwCurrency(gw.currency); setGwModules(gw.modules ?? ['deposit','booking-widget','room-service']) } else { setGwEditId(null); setGwLabel('ЮКасса'); setGwShopId(''); setGwSecretKey('') setGwCurrency('RUB'); setGwModules(['deposit','booking-widget','room-service']) } setGwFormOpen(true) setGwSecretVisible(false) } const saveGateway = async () => { if (!gwShopId.trim()) return setGwSaving(true) try { const data = { label: gwLabel, shopId: gwShopId.trim(), secretKey: gwSecretKey.trim() || undefined, currency: gwCurrency, modules: gwModules } if (gwEditId) { const updated = await api.paymentGateways.update(slug, gwEditId, data) setGateways(p => p.map(g => g.id === gwEditId ? updated : g)) } else { const created = await api.paymentGateways.create(slug, { ...data, provider: 'yookassa' }) setGateways(p => [...p, created]) } setGwFormOpen(false) } catch { /* ignore */ } finally { setGwSaving(false) } } const deleteGateway = async (id: string) => { if (!confirm('Удалить платёжный шлюз?')) return await api.paymentGateways.remove(slug, id).catch(() => {}) setGateways(p => p.filter(g => g.id !== id)) } const toggleGwModule = (mod: string) => { setGwModules(p => p.includes(mod) ? p.filter(m => m !== mod) : [...p, mod]) } const moveItem = async (id: string, dir: -1 | 1) => { const idx = methods.findIndex(m => m.id === id) if (idx < 0) return const newIdx = idx + dir if (newIdx < 0 || newIdx >= methods.length) return const updated = [...methods] ;[updated[idx], updated[newIdx]] = [updated[newIdx], updated[idx]] setMethods(updated) // Update sort_order for both await Promise.all([ api.paymentMethods.update(slug, updated[idx].id, { sortOrder: idx }), api.paymentMethods.update(slug, updated[newIdx].id, { sortOrder: newIdx }), ]).catch(() => {}) } if (loading) { return (
) } return (

Настройки оплаты

Способы оплаты и контроль расчётов при заселении.

{/* Check-in payment control */}

Контроль оплаты при заселении

Что делать если гость ещё не оплатил при нажатии кнопки «Заселить»

{CHECKIN_OPTIONS.map(opt => ( ))}
{/* Payment methods */}
Способы оплаты {!adding && ( )}
{/* Add form */} {adding && (
setNewState(p => ({ ...p, name: e.target.value }))} placeholder="Название (напр. Наличные USD)" className="input py-1.5 text-sm" onKeyDown={e => e.key === 'Enter' && handleAdd()} />
)} {methods.length === 0 && !adding && (

Нет способов оплаты. Нажмите «Добавить».

)} {methods.map((m, idx) => (
{editingId === m.id ? (
setEditState(p => ({ ...p, name: e.target.value }))} className="input py-1.5 text-sm" onKeyDown={e => e.key === 'Enter' && saveEdit(m.id)} />
) : (
{m.name} {m.currency} · {METHOD_TYPES.find(t => t.id === m.type)?.label}
)}
))}
{/* Payment gateways */}
Онлайн-оплата (ЮКасса)

Один раз настройте шлюз — выберите в каких модулях он работает

{!gwFormOpen && ( )}
{gateways.length === 0 && !gwFormOpen && (

Нет платёжных шлюзов. Нажмите «Добавить».

)} {/* Add/Edit form */} {gwFormOpen && (

{gwEditId ? 'Редактировать шлюз' : 'Новый шлюз'}

setGwLabel(e.target.value)} className="input py-1.5 text-sm" placeholder="ЮКасса" />
setGwShopId(e.target.value)} className="input py-1.5 text-sm font-mono" placeholder="123456" />
setGwSecretKey(e.target.value)} className="input py-1.5 text-sm font-mono pr-8" placeholder={gwEditId ? '••••••••' : 'test_...'} />

Использовать в модулях:

{GATEWAY_MODULES.map(m => ( ))}
)}
{gateways.map(gw => (
{gw.label} {gw.isActive ? 'Активен' : 'Отключён'}

Shop ID: {gw.shopId ?? '—'} · {gw.currency}

{(gw.modules ?? []).map(m => ( {GATEWAY_MODULES.find(x => x.id === m)?.label ?? m} ))}
))}
Способы оплаты появляются в панели бронирования при приёме платежей. Скрытые методы не отображаются.
) }