feat: long-stay deposit charge + refunds + auto-confirm widget + white page fix

- Widget white page fix: fallback photo when real rooms have no photos array
- Long-stay deposits: if booking > threshold days and toggle on → full charge instead of hold (bypasses YooKassa 7-day limit)
- DepositSettingsPage: toggle + day threshold input for long-stay full payment
- Refund endpoint: POST /deposit/refund supports full and partial refunds via YooKassa refunds API
- DepositWidget: refund UI for yookassa_charge deposits (full/partial, shows remaining amount)
- partially_refunded status support with badge and "Вернуть ещё" button
- Auto-confirm: webhook now auto-confirms booking status on payment.succeeded for widget bookings
- PaymentSettingsPage: auto-confirm toggle per gateway (shown when booking-widget module active)
- Migration 067: long_stay columns on hotel_deposit_settings, auto_confirm_on_payment on gateways, refunded_amount on deposits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-07 20:30:26 +03:00
parent bddfa355f2
commit 57531f2022
9 changed files with 368 additions and 53 deletions

View File

@@ -91,6 +91,32 @@ export async function capturePayment(params: {
}
}
// Full or partial refund of a captured (charged) payment
export async function createRefund(params: {
shopId: string
secretKey: string
paymentId: string
amount: number
}): Promise<void> {
const auth = Buffer.from(`${params.shopId}:${params.secretKey}`).toString('base64')
const res = await fetch('https://api.yookassa.ru/v3/refunds', {
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
'Idempotence-Key': randomUUID(),
},
body: JSON.stringify({
payment_id: params.paymentId,
amount: { value: params.amount.toFixed(2), currency: 'RUB' },
}),
})
if (!res.ok) {
const err = await res.text()
throw new Error(`YooKassa refund error ${res.status}: ${err}`)
}
}
export async function cancelPayment(params: {
shopId: string
secretKey: string