Cómo aplicar SRP en un procesador de pagos con Stripe
Clase 4 de 27 • Curso de Patrones de Diseño y SOLID en Python
Contenido del curso
Principios SOLID
- 2

Principio de responsabilidad única en SOLID
05:59 min - 3

Refactorizando código Python con principios SOLID
11:14 min - 4

Cómo aplicar SRP en un procesador de pagos con Stripe
Viendo ahora - 5

Open Closed Principle: extensión sin modificación
02:39 min - 6

Cómo usar clases abstractas en Python
14:46 min - 7

Principio de Liskov en S.O.L.I.D.
03:18 min - 8

Principio de sustitución de Liskov en Python
06:38 min - 9

Interface Segregation: cuándo separar contratos
02:33 min - 10

Segregación de interfaces en procesadores de pagos
09:05 min - 11

Principio de inversión de dependencias explicado
04:13 min - 12

Principio de inversión de dependencias: servicio de pagos flexible
05:56 min
Reestructuración del proyecto
Patrones de Diseño
- 14

Qué son los patrones de diseño: definición y categorías
03:54 min - 15

Strategy Pattern con Python y setprocessor
01:55 min - 16

Strategy Pattern para pagos en Python
10:58 min - 17

Factory Pattern: centralizar creación de objetos
03:05 min - 18

Patrón Factory para procesar pagos con match
11:06 min - 19

Patrón Decorator en 5 pasos para funcionalidad dinámica
03:06 min - 20

Patrón decorador en servicios de pagos
12:57 min - 21

Builder Pattern: construcción paso a paso
01:28 min - 22

Builder pattern para servicios de pagos
18:55 min - 23

Observer Pattern en sistemas de eventos
01:48 min - 24

Observer en sistemas de pagos con Python
11:11 min - 25

Chain of Responsibility para validar pagos
02:04 min - 26

Chain of Responsibility en servicios de pagos
16:27 min - 27

Arquitectura robusta para procesadores de pago
03:19 min
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:
ValueErroren 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. PaymentProcessorconvertido en data class para simplificar instanciación.- Firma de
processTransactionconservada, ahora con retorno tipado a Charge. - Validaciones reemplazadas por
raise ValueErrorcon 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
sourceenpayment_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
emailniphone, 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:
StripePaymentProcessorrefleja su dominio real. - Python data class: instanciación clara, sin
__init__explícito. - Python type hints:
processTransaction -> Chargemejora legibilidad y contratos. - Manejo de excepciones:
ValueErroren validaciones; propagación destripe_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.