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(...).
¿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.
@dataclassclassPaymentService: customer_validator: CustomerValidation
payment_validator: PaymentDataValidator
stripe_payment_processor
notifier: Notifier
transaction_logger: TransactionLogger
defprocessTransaction(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.
Cómo aplicar SRP en un procesador de pagos con Stripe