Skip to content

Руководство по операциям с выплатами (для админов)

Внутреннее — только для операторов

Эта страница документирует Admin → Payout Management для администраторов платформы FundlyHub. Она описывает инструменты, которые перемещают реальные деньги и пишут в финансовый реестр (ledger). Прочитайте правила безопасности денег перед запуском чего-либо. Деструктивные действия требуют права manage_system_settings. Названия элементов интерфейса (UI) приведены на английском — именно так они отображаются в админ-панели.

Payout Management находится на админ-странице Payout Management и имеет три вкладки: Transfers (реестр по каждому пожертвованию), Creator Payouts (выплаты Stripe на банковские счета создателей) и Connected Accounts. Это руководство сосредоточено на вкладке Transfers, где происходят релизы и ремедиация.


Модель статусов переводов на основе доказательств

Каждая строка пожертвования на вкладке Transfers несёт один из пяти бейджей статуса. Статус выводится из доказательств, а не из одного флага — строка считается «деньги переведены» только тогда, когда Stripe это реально подтверждает. Это ключевой инвариант, который не даёт платформе платить дважды или молча «терять» средства.

БейджЗначениеГотова к релизу?
HeldСредства удерживаются платформой, и соответствующее списание прошло расчёт (settled), и у создателя есть активный connected account. Готова к релизу сейчас.Да
Pending settlementУдерживается, но списание ещё не прошло расчёт. Станет Held автоматически, как только Stripe его рассчитает.Ещё нет
TransferredСуществует аудит-строка реестра с реальным tr_… — Stripe доказуемо переместил деньги.Готово
DirectDestination charge: Stripe перевёл деньги создателю в момент списания. Отдельный перевод не нужен.Готово
AnomalyКрасный бейдж. Реестр утверждает, что деньги переведены, но ничто в Stripe это не подтверждает (класс бага #1126). Требует ремедиации.Нужна проверка

Почему «Held» состоит из двух частей: строка считается готовой к релизу только когда списание прошло расчёт и connected account создателя активен (has_connected_account && charges_enabled). Рассчитанные, но не онбордингованные средства отслеживаются отдельно как Held — awaiting onboarding — деньги в безопасности, их просто нельзя перевести, пока создатель не завершит Stripe Connect.

Фильтр статуса наверху вкладки Transfers позволяет ограничить таблицу любым одним статусом (Held, Pending settlement, Transferred, Direct, Anomaly).


KPI-плитки

Над таблицей расположены четыре основные плитки. Первые три кликабельны — клик ведёт на вкладку Transfers, отфильтрованную по этому статусу.

ПлиткаЧто считаетДействие по клику
Ready to releaseHeld-строки, прошедшие расчёт и с активным connected account — переводимы прямо сейчас. Подсказка показывает чистую сумму.Фильтрует Transfers по held
Awaiting settlementPending-settlement строки — релизятся автоматически после расчёта.Фильтрует Transfers по pending_settlement
AnomaliesСтроки, чей реестр утверждает движение денег без доказательств Stripe. При 0 подсказка показывает «ledger clean».Фильтрует Transfers по anomaly
Connected accountsВсего connected accounts, в подсказке N verified · M pending setup.

Под плитками вспомогательная полоса несёт метрики меньшего веса:

  • Monthly volume — переводы за этот месяц
  • Platform fees — чаевые за этот месяц
  • Held — awaiting onboarding — рассчитанные удержанные средства, которые платформа пока не может перевести, потому что создатель не подключил Stripe (чистая сумма в подсказке)
  • Transferred — подтверждённые Stripe + direct, за всё время
  • Total donations — за всё время

Сама метка вкладки Transfers показывает живые бейджи: N ready (warning) и N anomalies (error), когда эти счётчики ненулевые.


Меню Actions

Выпадающее меню Actions (вверху справа на вкладке Transfers) защищено правом manage_system_settings. В нём четыре пункта.

Release N ready ($X)

Создаёт реальные переводы Stripe для каждой Held-строки, готовой к релизу. Поведение:

  • Проходит по каждому создателю с удержанными средствами и запускает путь релиза по каждому пожертвованию для тех, чей connected account активен. Создатели без активного connected account пропускаются (считаются отдельно, не как ошибки).
  • Каждый перевод запускается по одному на пожертвование с установленным source_transaction, поэтому каждое пожертвование отображается отдельной строкой в connected account создателя.
  • Переводы — за вычетом комиссий (net): сумма в реестре Held уже является чистой суммой к выплате создателю.
  • Адоптировать, а не платить дважды: перед созданием перевода путь релиза проверяет в Stripe наличие уже существующего живого перевода для этого списания (через charge.transfer или per-donation transfer_group). Если такой уже есть, он его адоптирует — пишет аудит-строку и переключает запись реестра без нового движения денег — вместо создания второго перевода.
  • Строки, чьё списание ещё не прошло расчёт, отклоняются Stripe и остаются Held; webhook повторит их при следующем balance.available.

Диалог подтверждения резюмирует объём перед запуском. После прогона таблица и счётчики KPI перечитываются с сервера (без оптимистичных переключений — бейдж меняется, только если обновление реестра в БД действительно зафиксировалось).

Remediate anomalies

Открывает диалог Remediate transfer anomalies. Это инструмент восстановления для Anomaly-строк — реестр говорит о движении денег, но доказательств Stripe нет. Он никогда не создаёт переводы Stripe.

Рабочий процесс: Detect → проверка → ExecuteAttest как операторским переопределением):

  1. Creator user ID (необязательно). Пусто — сканирует всю платформу на аномалии. Ограничение одним создателем также включает ещё удерживаемые строки этого создателя, чтобы подтверждённые ручные выплаты можно было засвидетельствовать (attest).

  2. Detect — только чтение. Проверяет каждую аномалию против Stripe и классифицирует её. Проверьте чипы классификации:

    ЧипЗначениеПоведение Execute
    restore_to_heldДоказуемо «застряло» — перевода Stripe не найдено.Восстанавливается в held → снова попадает в обычный пайплайн релиза.
    already_transferredРеальный перевод Stripe существует; отсутствовала только аудит-строка.Аудит-строка дописывается (деньги не движутся).
    direct_confirmedПодтверждённый destination charge.Помечается как direct (деньги не движутся).
    subscription_excludedЗапись подписки, исключена из этого пути.Без изменений.
    held_unprovenВсё ещё held, без доказательств Stripe в любую сторону — только attest.Execute не трогает.
    attestedПомечено оператором как оплачено вне платформы.
    errorОшибка Stripe/БД по этой строке; печатается под строкой. Ничего не изменено.
  3. Execute — restore N ($X) + backfill M — применяет не-attest классификации: восстанавливает недоказанные не-held строки в Held, дописывает аудит-строки для подтверждённых переводов, помечает подтверждённые destination charges. Переводы Stripe не создаются. Диалог остаётся открытым, показывая классификацию после Execute (чтобы ошибки записи не прятались за тостом успеха).

  4. Attest paid off-platform — N ($X) — операторское переопределение, показывается только когда выбран создатель и есть кандидаты на attest. Помечает пожертвования создателя как оплаченные БЕЗ доказательств Stripe. Используйте только когда вы лично проверили, что деньги дошли до создателя (например, ручной перевод через дашборд Stripe или банковская выплата). Записывает выплату как доказательство без движения денег и запрашивает причину для аудит-лога.

Restore против Attest — правило двойной оплаты

Execute-restore возвращает недоказанные строки в Held, чтобы обычный пайплайн их оплатил. Attest фиксирует, что они уже оплачены вне платформы, и денег не двигает. НИКОГДА не делайте Execute-restore для создателя, которому вы уже заплатили вручную — это оплатит дважды. Если вы не на 100% уверены, что деньги уже переместились, attest — неправильный инструмент; если вы на 100% уверены, что переместились, restore — неправильный инструмент. В сомнениях — отмените и сначала проверьте в дашборде Stripe.

Reconcile held with Stripe

Проходит по каждой Held-строке и спрашивает Stripe, существует ли уже живой перевод для её списания — разрешается из любого источника доказательств: charge.transfer (эпоха source-transaction) или per-donation transfer_group (donation-<id>, эпоха plain-transfer). Если найден живой, нереверсированный перевод, он его адоптирует: пишет аудит-строку и переключает запись в Transferred без движения денег.

Это инструмент восстановления для «Stripe заплатил создателю, но откат БД оставил строку Held». Безопасен для запуска в любое время — он только адоптирует существующие переводы, никогда не создаёт новые. Тост показывает checked / reconciled (совпало с существующими переводами) / genuinely held / skipped / errors.

Backfill settlement data

Старые удержанные пожертвования предшествуют settlement-вебхукам, поэтому их stripe_available_on равно NULL — они вечно отображаются как Pending settlement, и путь релиза их пропускает. Это действие подтягивает реальное stripe_available_on из Stripe для таких строк и чинит ложные пометки destination-charge. Идемпотентно — безопасно перезапускать. Тост сообщает, сколько из отсутствующих строк было дозаполнено и сколько ещё осталось.


Дерево решений

Строка показывает Anomaly → Откройте Remediate anomalies, запустите Detect (ограничьте создателем, если знаете его). Проверьте чипы: строки already_transferred / direct_confirmed нужно лишь Execute для дозаполнения доказательств (деньги не движутся). Строки restore_to_held действительно «застряли» — Execute восстановит их в Held, чтобы пайплайн их оплатил, если только у вас нет доказательств, что создателю уже заплатили вручную, — тогда Attest. Никогда не восстанавливайте строку для создателя, которому вы заплатили вручную.

Строка показывает Held, но создатель говорит, что ему заплатили → Не релизьте. Сначала запустите Reconcile held with Stripe — если в Stripe уже есть живой перевод для этого списания, reconcile его адоптирует, и строка переключится в Transferred без движения денег. Если reconcile ничего не нашёл, но вы лично проверили выплату вне платформы, ограничьте Remediate anomalies этим создателем и сделайте Attest paid off-platform с указанием причины. Релиз вместо этого приведёт к двойной оплате.

Pending settlement многомесячной давности → Строка, вероятно, предшествует settlement-вебхукам и имеет NULL дату расчёта. Запустите Backfill settlement data, чтобы подтянуть stripe_available_on из Stripe. После дозаполнения и расчёта она перейдёт в Held и станет готовой к релизу (или релизнется автоматически на следующем прогоне).


Правила безопасности денег

Прочитайте перед запуском любого действия

  • Каждое переключение статуса требует доказательств. Строка переключается в Transferred/Direct только при наличии реального tr_… Stripe, charge.transfer, совпадающего transfer_group или аудированного операторского attestation. Ни один флаг не переключается только по метаданным.
  • Ремедиация не двигает деньги. Detect — только чтение; Execute, Reconcile и Backfill только адоптируют существующие переводы, дозаполняют аудит-строки или помечают подтверждённые состояния. Ни один из них никогда не вызывает transfers.create. Только Release N ready создаёт переводы Stripe.
  • Защита точного совпадения. Execute и Attest несут числа этого прогона Detect (expectedCandidates / expectedRestoreCents) в серверную защиту точного совпадения. Если реестр изменился между Detect и Execute, сервер возвращает 409 → «State changed — re-run Detect». При 409 не повторяйте вслепую — перезапустите Detect и проверьте свежую классификацию перед действием.
  • Никогда не платите дважды. Не делайте Execute-restore для создателя, которому уже заплатили вручную. Используйте Attest для подтверждённых выплат вне платформы; Restore — только для действительно «застрявших» средств.
  • Release адоптирует перед оплатой. Путь релиза проверяет в Stripe уже существующий живой перевод и адоптирует его вместо создания второго — но эта подстраховка не заменяет правила выше.

Вопросы по конкретной строке или зависшему прогону ремедиации? Напишите на tech@cyty.io с ID пожертвования и текстом тоста Detect/Execute.

Built with VitePress