Contenido del curso

Principios SOLID

Patrones de Diseño

Cómo aplicar SRP en un procesador de pagos con Stripe

Resumen

Aplicar el principio de responsabilidad única (SRP) en Python con Stripe no solo ordena el código: también hace el flujo de cobro más claro, testeable y mantenible. Aquí verás cómo se pasó de un PaymentProcessor monolítico a un diseño con validadores, servicio orquestador y componentes dedicados para notificación y registro, usando data class, type hints y manejo de excepciones.

¿Qué problema resuelve el principio de responsabilidad única en este procesador de pagos?

Separar responsabilidades evita que una clase haga de todo: validar, cobrar, notificar y loguear. El punto de partida fue un procesador rudimentario que incumplía SOLID. Se reorganizó con clases específicas: CustomerValidation, PaymentDataValidator, Notifier, TransactionLogger y un StripePaymentProcessor, coordinadas por PaymentService.

  • Importaciones y entorno al inicio del archivo: organización estándar de la industria.
  • PaymentProcessor como data class: instanciación simple sin definir init manual.
  • processTransaction con type hints: retorna una instancia de Stripe Charge.
  • Validaciones con excepciones: ValueError en lugar de objetos vacíos.
  • Corrección de flujo: si no hay email ni teléfono, igual se devuelve el charge exitoso.

¿Cómo estaba el código antes y qué ajustes iniciales se hicieron?

  • Importaciones y variables de entorno (por ejemplo, stripe_api_key) movidas al inicio.
  • PaymentProcessor convertido en data class para simplificar instanciación.
  • Firma de processTransaction conservada, ahora con retorno tipado a Charge.
  • Validaciones reemplazadas por raise ValueError con mensajes claros.
  • Bug corregido: ausencia de email y teléfono ya no corta el flujo; se devuelve el cargo igualmente.

¿Cuáles responsabilidades se identifican y por qué separarlas?

  • Validación de customer: nombre y contact info correctos.
  • Validación de pago: existencia de source en payment_data.
  • Procesamiento del pago: interacción con la librería de Stripe.
  • Notificación: por email o SMS, comportamiento moqueado.
  • Registro en logs: persistir contexto del cargo.

Separarlas permite que cada clase cambie por razones únicas, cumpliendo SRP.

¿Qué clases nuevas encapsulan cada responsabilidad?

  • CustomerValidation con validate(customer_data).
  • PaymentDataValidator con validate(payment_data).
  • Notifier con sendConfirmation(customer_data) para email o teléfono.
  • TransactionLogger con log(customer_data, payment_data, charge).
  • StripePaymentProcessor (renombrado para ser semántico) con processTransaction(...).
  • PaymentService como orquestador.

Ejemplo de estructura mínima:

from dataclasses import dataclass class CustomerValidation: def validate(self, customer_data: dict) -> None: if not customer_data.get("name"): raise ValueError("missing name") if not customer_data.get("contact_info"): raise ValueError("missing contact info") class PaymentDataValidator: def validate(self, payment_data: dict) -> None: if "source" not in payment_data: raise ValueError("missing source") class Notifier: def sendConfirmation(self, customer_data: dict) -> None: if customer_data.get("email"): print("enviar email moqueado.") elif customer_data.get("phone"): print("enviar sms moqueado.") class TransactionLogger: def log(self, customer_data: dict, payment_data: dict, charge): print(f"pagó {payment_data['amount']} la persona {customer_data['name']}")

¿Cómo coordina PaymentService el flujo con validadores, processor y notificaciones?

PaymentService concentra el flujo: valida datos, procesa con StripePaymentProcessor, notifica con Notifier y registra con TransactionLogger. Devuelve el Charge de Stripe y propaga stripe_error si algo falla. Se usa try/except para manejar ValueError ya emitidos por los validadores.

@dataclass class PaymentService: customer_validator: CustomerValidation payment_validator: PaymentDataValidator stripe_payment_processor notifier: Notifier transaction_logger: TransactionLogger def processTransaction(self, customer_data: dict, payment_data: dict) -> "Charge": try: self.customer_validator.validate(customer_data) except ValueError as e: raise e try: self.payment_validator.validate(payment_data) except ValueError as e: raise e try: charge = self.stripe_payment_processor.processTransaction(customer_data, payment_data) except stripe_error as e: raise e self.notifier.sendConfirmation(customer_data) self.transaction_logger.log(customer_data, payment_data, charge) return charge

¿Qué cambio clave evita errores en las validaciones?

En lugar de if esperando booleanos, los validadores ya hacen raise ValueError. Por eso, en PaymentService se usa try/except y se re-lanza la misma excepción. Esto elimina falsos positivos de validación y simplifica el control de flujo.

try: customer_validator.validate(customer_data) except ValueError as e: raise e

¿Qué se corrige en notificaciones y retorno del cargo?

  • Si no hay email ni phone, el flujo continúa y se retorna el charge exitoso.
  • La notificación es moqueada: impresión por consola según disponibilidad de contacto.
  • El logger persiste contexto: monto (p. ej., 500 o 700) y nombre (p. ej., John Doe).

¿Cómo se maneja un fallo de Stripe con tokens de prueba?

  • Se reproducen fallos con token de riesgo como “radar block”: la tarjeta es declinada por defecto.
  • El StripePaymentProcessor levanta la excepción capturada; PaymentService la propaga.
  • En el código cliente, se puede envolver la llamada en try/except y reportar: “falló el procesamiento: {error}”.
try: charge = payment_service.processTransaction(customer_data, payment_data) except Exception as e: print(f"falló el procesamiento: {e}")

¿Qué habilidades y conceptos aplicas al refactor bajo SOLID?

Refactorizar con SRP exige precisión y criterio. Aquí se aplican prácticas de alto impacto para código de pagos en producción.

  • SRP (principio de responsabilidad única): cada clase cambia por una razón única.
  • Orquestación con PaymentService: coordina validación, cobro, notificación y logging.
  • Renombramiento semántico: StripePaymentProcessor refleja su dominio real.
  • Python data class: instanciación clara, sin __init__ explícito.
  • Python type hints: processTransaction -> Charge mejora legibilidad y contratos.
  • Manejo de excepciones: ValueError en validaciones; propagación de stripe_error.
  • Mocks funcionales: envío de email/SMS como impresión controlada.
  • Corrección de flujo: retorno del charge aunque no haya medios de contacto.
  • Depuración con debugger y breakpoints: verificación de estados intermedios.
  • Pruebas de error con tokens de Stripe: “radar block” simula tarjeta declinada.

¿Tú cómo separarías aún más las responsabilidades o qué cambios harías para reforzar SRP en este flujo de pagos? Comparte tus ideas y mejoras en los comentarios.