Manual de operaciones de pagos (admins)
Interno — solo operadores
Esta página documenta Admin → Payout Management para administradores de la plataforma FundlyHub. Describe herramientas que mueven dinero real y escriben en el libro mayor financiero (ledger). Lee las reglas de seguridad del dinero antes de ejecutar nada. Las acciones destructivas requieren el permiso manage_system_settings. Las etiquetas de la interfaz (UI) se muestran en inglés — así aparecen en el panel de administración.
Payout Management está en la página de administración Payout Management y tiene tres pestañas: Transfers (el libro mayor donación por donación), Creator Payouts (pagos de Stripe a las cuentas bancarias de los creadores) y Connected Accounts. Este manual se centra en la pestaña Transfers, donde ocurren las liberaciones y la remediación.
El modelo de estado basado en evidencias
Cada fila de donación en la pestaña Transfers lleva uno de cinco distintivos (badges) de estado. El estado se deriva de la evidencia, no de un único indicador — una fila solo dice "el dinero se movió" cuando Stripe lo prueba de verdad. Este es el invariante central que evita que la plataforma pague dos veces o deje fondos varados en silencio.
| Distintivo | Significado | ¿Liberable? |
|---|---|---|
| Held | Los fondos están retenidos por la plataforma, y el cargo subyacente se ha liquidado (settled), y el creador tiene una connected account activa. Listo para liberar ahora. | Sí |
| Pending settlement | Retenido, pero el cargo subyacente aún no se ha liquidado. Pasará a Held automáticamente en cuanto Stripe lo liquide. | Todavía no |
| Transferred | Existe una fila de auditoría del ledger con un tr_… real — Stripe movió el dinero de forma verificable. | Hecho |
| Direct | Un destination charge: Stripe movió el dinero al creador en el momento del cargo. No hace falta una transferencia aparte. | Hecho |
| Anomaly | El distintivo rojo. El ledger afirma que el dinero se movió, pero nada en Stripe lo prueba (la clase de bug #1126). Necesita remediación. | Necesita revisión |
Por qué "Held" tiene dos partes: una fila solo cuenta como liberable cuando el cargo se ha liquidado y la connected account del creador está activa (has_connected_account && charges_enabled). Los fondos liquidados pero sin onboarding se rastrean por separado como Held — awaiting onboarding — el dinero está a salvo, solo que no puede transferirse hasta que el creador complete Stripe Connect.
El filtro de estado en la parte superior de la pestaña Transfers permite acotar la tabla a un único estado (Held, Pending settlement, Transferred, Direct, Anomaly).
Las tarjetas KPI
Cuatro tarjetas principales se sitúan sobre la tabla. Las tres primeras son clicables — al hacer clic se entra directamente en la pestaña Transfers filtrada por ese estado.
| Tarjeta | Qué cuenta | Acción al hacer clic |
|---|---|---|
| Ready to release | Filas Held liquidadas y con una connected account activa — liberables ahora mismo. La nota muestra el importe neto. | Filtra Transfers por held |
| Awaiting settlement | Filas pending-settlement — se liberan automáticamente una vez liquidadas. | Filtra Transfers por pending_settlement |
| Anomalies | Filas cuyo ledger afirma movimiento de dinero sin evidencia de Stripe. Cuando es 0, la nota dice "ledger clean". | Filtra Transfers por anomaly |
| Connected accounts | Total de connected accounts, con N verified · M pending setup en la nota. | — |
Debajo de las tarjetas, una franja secundaria lleva métricas de menor peso:
- Monthly volume — transferencias de este mes
- Platform fees — propinas de este mes
- Held — awaiting onboarding — fondos retenidos y liquidados que la plataforma aún no puede transferir porque el creador no ha conectado Stripe (importe neto en la nota)
- Transferred — verificadas por Stripe + direct, histórico total
- Total donations — histórico total
La propia etiqueta de la pestaña Transfers muestra distintivos en vivo: N ready (warning) y N anomalies (error) cuando esos contadores no son cero.
El menú Actions
El desplegable Actions (arriba a la derecha de la pestaña Transfers) está protegido tras manage_system_settings. Tiene cuatro elementos.
Release N ready ($X)
Crea transferencias reales de Stripe para cada fila Held que sea liberable. Comportamiento:
- Recorre cada creador con fondos retenidos y ejecuta la ruta de liberación por donación para aquellos cuya connected account esté activa. Los creadores sin connected account activa se omiten (se cuentan aparte, no como fallidos).
- Cada transferencia se dispara una por donación con
source_transactiondefinido, así que cada donación aparece como su propia línea en la connected account del creador. - Las transferencias son netas de comisiones: el importe del ledger en Held ya es el neto pagable al creador.
- Adoptar, no pagar dos veces: antes de crear una transferencia, la ruta de liberación comprueba en Stripe si ya existe una transferencia activa para ese cargo (vía
charge.transfero eltransfer_grouppor donación). Si ya existe, la adopta — escribe la fila de auditoría y voltea la entrada del ledger sin nuevo movimiento de dinero — en lugar de crear una segunda transferencia. - Las filas cuyo cargo aún no se ha liquidado son rechazadas por Stripe y se dejan en Held; el webhook las reintenta en el próximo
balance.available.
Un diálogo de confirmación resume el alcance antes de disparar nada. Tras la ejecución, la tabla y los contadores KPI se vuelven a leer del servidor (sin volteos optimistas — el distintivo solo cambia si la actualización del ledger en la BD realmente se confirmó).
Remediate anomalies
Abre el diálogo Remediate transfer anomalies. Es la herramienta de recuperación para las filas Anomaly — el ledger dice que el dinero se movió pero no hay evidencia de Stripe. Nunca crea transferencias de Stripe.
El flujo es Detect → revisar → Execute (con Attest como anulación del operador):
Creator user ID (opcional). Vacío escanea toda la plataforma en busca de anomalías. Acotar a un solo creador también incluye las filas aún retenidas de ese creador, para poder atestar pagos manuales confirmados.
Detect — solo lectura. Verifica cada anomalía contra Stripe y la clasifica. Revisa los chips de clasificación:
Chip Significado Comportamiento de Execute restore_to_held Verificablemente varada — no se encontró transferencia de Stripe. Restaurada a held → vuelve a entrar en el pipeline de liberación normal. already_transferred Existe una transferencia real de Stripe; solo faltaba la fila de auditoría. Se rellena la fila de auditoría (no se mueve dinero). direct_confirmed Destination charge confirmado. Se marca como direct (no se mueve dinero). subscription_excluded Entrada de suscripción, excluida de esta ruta. Sin cambios. held_unproven Aún retenida, sin prueba de Stripe en ningún sentido — solo attest. Execute no la toca. attested Marcada como pagada fuera de la plataforma por un operador. — error Error de Stripe/BD en esta fila; se imprime debajo de la fila. No se cambió nada. — Execute — restore N ($X) + backfill M — aplica las clasificaciones que no son attest: restaura a Held las filas no probadas que no están en held, rellena filas de auditoría para transferencias con evidencia, marca los destination charges confirmados. No se crean transferencias de Stripe. El diálogo permanece abierto mostrando la clasificación tras Execute (para que los errores de escritura no queden ocultos tras un toast de éxito).
Attest paid off-platform — N ($X) — anulación del operador, solo se muestra cuando hay un creador acotado y candidatos de attest. Marca las donaciones del creador como pagadas SIN evidencia de Stripe. Úsalo solo cuando hayas verificado personalmente que el dinero llegó al creador (p. ej. una transferencia manual desde el dashboard de Stripe o un pago bancario). Registra el pago como evidencia sin mover dinero, y pide un motivo que va al registro de auditoría.
Restore vs. Attest — la regla del doble pago
Execute-restore devuelve las filas no probadas a Held para que el pipeline normal las pague. Attest registra que ya se pagaron fuera de la plataforma y no mueve dinero. NUNCA hagas Execute-restore a un creador al que ya pagaste manualmente — eso lo paga dos veces. Si no estás 100% seguro de que el dinero ya se movió, attest es la herramienta equivocada; si estás 100% seguro de que se movió, restore es la herramienta equivocada. En la duda, cancela y verifica primero en el dashboard de Stripe.
Reconcile held with Stripe
Recorre cada fila Held y pregunta a Stripe si ya existe una transferencia activa para su cargo — resuelta desde cualquiera de las fuentes de evidencia: charge.transfer (era source-transaction) o el transfer_group por donación (donation-<id>, era plain-transfer). Si encuentra una transferencia activa y no revertida, la adopta: escribe la fila de auditoría y voltea la entrada a Transferred sin movimiento de dinero.
Es la herramienta de recuperación para "Stripe pagó al creador pero un rollback de la BD dejó la fila en Held". Es seguro ejecutarla en cualquier momento — solo adopta transferencias existentes, nunca crea nuevas. El toast informa checked / reconciled (coincidieron con transferencias existentes) / genuinely held / skipped / errors.
Backfill settlement data
Las donaciones retenidas antiguas son anteriores a los webhooks de liquidación, así que su stripe_available_on es NULL — se muestran como Pending settlement para siempre y la ruta de liberación las omite. Esta acción extrae el stripe_available_on real de Stripe para esas filas, y repara las marcas falsas de destination-charge. Es idempotente — seguro de re-ejecutar. El toast informa cuántas de las filas que faltaban se rellenaron y cuántas quedan aún.
Árbol de decisión
Una fila muestra Anomaly → Abre Remediate anomalies, ejecuta Detect (acota al creador si lo conoces). Revisa los chips: las filas already_transferred / direct_confirmed solo necesitan Execute para rellenar la evidencia (no se mueve dinero). Las filas restore_to_held están genuinamente varadas — Execute las restaura a Held para que el pipeline las pague, a menos que tengas evidencia de que al creador ya se le pagó manualmente, en cuyo caso usa Attest. Nunca restaures una fila de un creador al que pagaste a mano.
Una fila muestra Held pero el creador dice que le pagaron → No liberes. Primero ejecuta Reconcile held with Stripe — si Stripe ya tiene una transferencia activa para ese cargo, reconcile la adopta y la fila pasa a Transferred sin mover dinero. Si reconcile no encuentra nada pero has verificado personalmente un pago fuera de la plataforma, acota Remediate anomalies a ese creador y haz Attest paid off-platform con un motivo. Liberar en su lugar pagaría dos veces.
Pending settlement de hace meses → La fila probablemente es anterior a los webhooks de liquidación y tiene una fecha de liquidación NULL. Ejecuta Backfill settlement data para extraer stripe_available_on de Stripe. Una vez rellenada y liquidada, pasa a Held y se vuelve liberable (o se libera automáticamente en la siguiente pasada).
Reglas de seguridad del dinero
Léelas antes de ejecutar cualquier acción
- Cada cambio de estado necesita evidencia. Una fila solo pasa a Transferred/Direct cuando hay un
tr_…real de Stripe, uncharge.transfer, untransfer_groupcoincidente o una atestación del operador auditada. Ningún indicador cambia solo por metadatos. - La remediación nunca mueve dinero. Detect es solo lectura; Execute, Reconcile y Backfill solo adoptan transferencias existentes, rellenan filas de auditoría o marcan estados confirmados. Ninguno llama nunca a
transfers.create. Solo Release N ready crea transferencias de Stripe. - Guardas de coincidencia exacta. Execute y Attest llevan los números de esta ejecución de Detect (
expectedCandidates/expectedRestoreCents) a una guarda de coincidencia exacta del lado del servidor. Si el ledger cambió entre Detect y Execute, el servidor devuelve 409 → "State changed — re-run Detect". Cuando veas un 409, no reintentes a ciegas — vuelve a ejecutar Detect y revisa la clasificación nueva antes de actuar. - Nunca pagues dos veces. No hagas Execute-restore a un creador al que ya pagaste manualmente. Usa Attest para pagos confirmados fuera de la plataforma; Restore solo para fondos genuinamente varados.
- Release adopta antes de pagar. La ruta de liberación comprueba en Stripe una transferencia activa preexistente y la adopta en vez de crear una segunda — pero esta red de seguridad no sustituye a las reglas de arriba.
¿Dudas sobre una fila concreta o una ejecución de remediación atascada? Escribe a tech@cyty.io con el ID de la donación y el texto del toast de Detect/Execute.