Sistema de Pagos

Arquitectura

El sistema de pagos centraliza todas las transacciones en una colección payments en Firestore, alimentada por tres fuentes:

class="highlight">
1
2
3
Stripe Checkout (web)  ──webhook──→  handleStripeCitaWebhook  ──→  payments
Doctor (manual)        ──callable──→  registerManualPayment    ──→  payments
Admin (refund)         ──callable──→  processRefund            ──→  payments

Colección payments (Firestore)

Cada documento representa una transacción individual.

Campo Tipo Descripción
type string stripe, manual, refund
amount number Monto (negativo para refunds)
currency string mxn o usd
status string completed, refunded, pending
doctorId string UID del doctor
clinicaId string ID de la clínica
pacienteId string UID del paciente (opcional para manuales)
citaServicio reference Referencia a la cita (DocumentReference)
description string Descripción del pago
stripePaymentIntentId string ID de Stripe (solo para tipo stripe)
refundOf reference Referencia al pago original (solo para refunds)
registeredBy string UID de quien registró (manuales)
createdAt timestamp Fecha de creación

Reglas Firestore

class="highlight">
1
2
3
4
match /payments/{paymentId} {
  allow read: if isAuthenticated();
  allow write: if false; // Solo Cloud Functions
}

Cloud Functions

handleStripeCitaWebhook

  • Trigger: HTTP POST desde Stripe webhook
  • Evento: checkout.session.completed
  • Acción: Crea documento en payments, actualiza paymentStatus de la cita
  • Idempotencia: Verifica stripePaymentIntentId duplicado antes de crear

registerManualPayment

  • Trigger: Callable desde app Flutter (doctor)
  • Parámetros: citaId, amount, description, paymentMethod
  • Validación: Verifica que el doctor sea dueño de la cita

processRefund

  • Trigger: Callable desde admin panel
  • Parámetros: paymentId, reason
  • Acción: Si el pago original es Stripe, ejecuta refund via Stripe API. Crea documento payment con amount negativo.

Flujo de Pago del Paciente (Stripe)

  1. Paciente abre detalle de cita → toca “Pagar cita médica”
  2. App llama a API para crear Stripe Checkout Session (con Stripe Connect del doctor)
  3. Se guarda checkoutSessionId en la cita
  4. Se abre URL de Stripe Checkout en browser
  5. Paciente completa pago en Stripe
  6. Webhook recibe checkout.session.completed
  7. Cloud Function crea payment doc + actualiza cita

Pantallas UI

Doctor: “Ingresos”

  • Summary card con total del mes
  • Lista de pagos (Stripe + manuales)
  • FAB “Registrar Pago” → modal con campos: cita, monto, descripción, método

Paciente: “Mis Pagos”

  • Lista de pagos ordenada por fecha
  • Bottom sheet con detalle de cada pago

Admin Panel: “/payments”

  • Métricas agregadas (total, por tipo)
  • Tabla filtrable por fecha, tipo, doctor
  • Export CSV
  • Real-time con onSnapshot

Stripe Connect

El sistema usa Stripe Connect para que cada doctor reciba pagos en su cuenta:

Cuenta Doctor Tipo Estado
acct_1RUIUyKFe63Zb0DL Dr. Carvajal Standard Activa
acct_1Kb607Jjr5pAQ1FN Daniel Muvdi Standard Activa

Riesgo: Capacidad de crear nuevas cuentas Connect se suspende el 2026-06-03 por inactividad. Se creó cuenta Express de prueba para demostrar actividad.

Seguridad

  • sk_live fue removido de app_constants.dart (commit 90fd21e)
  • Pendiente: migrar completamente a Cloud Functions (Flowlu #693)
  • Toda escritura a payments es server-side (Cloud Functions)

This site uses Just the Docs, a documentation theme for Jekyll.