Tesis 1.0.0
Loading...
Searching...
No Matches
views.py
Go to the documentation of this file.
1import logging
2from decimal import Decimal
3from rest_framework import status
4from rest_framework.response import Response
5from rest_framework.views import APIView
6from django.conf import settings
7from django.http import HttpResponse
8from django.shortcuts import redirect
9from django.views import View
10from django.http import HttpRequest
11import requests
12
13from .models import PaymentRequest, PaymentNotification
14from .serializers import (
15 PaymentRequestSerializer,
16 CreatePreferenceSerializer,
17 WebhookNotificationSerializer
18)
19from .services import CartService, MercadoPagoService
20
21logger = logging.getLogger('payment_service')
22
23class CreatePreferenceView(APIView):
24 """
25 Endpoint para crear una preferencia de pago en Mercado Pago
26 """
27
28 def post(self, request, *args, **kwargs):
29 # Log de datos recibidos
30 logger.info(f"Datos recibidos en create-preference: {request.data}")
31
32 # Validar datos de entrada
33 serializer = CreatePreferenceSerializer(data=request.data)
34 if not serializer.is_valid():
35 logger.error(f"Datos inválidos: {serializer.errors}")
36 return Response({"error": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
37
38 # Extraer token del usuario
39 user_token = serializer.validated_data['user_token']
40 email = serializer.validated_data.get('email')
41
42 logger.info(f"Token recibido: {user_token[:10]}... (truncado por seguridad)")
43 # Obtener datos del carrito
44 cart_service = CartService()
45 cart_data = cart_service.get_cart(user_token)
46
47 if not cart_data:
48 logger.error(f"No se pudo obtener el carrito con el token proporcionado: {user_token[:10]}...")
49
50 # Proporcionar un mensaje más específico para ayudar en la resolución del problema
51 error_message = "No se pudo obtener el carrito o está vacío"
52 if settings.DEBUG:
53 error_message += ". Para pruebas, utiliza un token que contenga 'test' para generar un carrito de prueba, o proporciona un token válido de un usuario con sesión iniciada."
54
55 return Response(
56 {"error": error_message},
57 status=status.HTTP_400_BAD_REQUEST
58 )
59
60 # Verificar que el carrito tenga productos
61 if not cart_data.get("productos", []):
62 logger.error(f"El carrito está vacío: {cart_data}")
63 return Response(
64 {"error": "El carrito está vacío"},
65 status=status.HTTP_400_BAD_REQUEST
66 )
67
68 # Crear solicitud de pago en la base de datos
69 total_amount = Decimal(str(cart_data.get("total", "0")))
70 payment_request = PaymentRequest.objects.create(
71 user_token=user_token,
72 cart_data=cart_data,
73 total_amount=total_amount,
74 status="pending"
75 )
76
77 # Convertir carrito a formato de Mercado Pago
78 mp_service = MercadoPagoService()
79 items = mp_service.process_cart_to_items(cart_data)
80
81 if not items:
82 payment_request.status = "error"
83 payment_request.save()
84 return Response(
85 {"error": "No se pudieron procesar los ítems del carrito"},
86 status=status.HTTP_400_BAD_REQUEST
87 )
88
89 # Crear preferencia en Mercado Pago
90 notification_url = f"{request.build_absolute_uri('/').rstrip('/')}/payment/webhook/"
91 # Definir back_urls según el entorno
92 env = request.data.get('env') or request.POST.get('env')
93 if env == 'mobile':
94 back_urls = {
95 "success": "foodapp://success",
96 "failure": "https://ispcfood.netlify.app/error",
97 "pending": "https://ispcfood.netlify.app/pendiente"
98 }
99 else:
100 back_urls = {
101 "success": "https://ispcfood.netlify.app/exito",
102 "failure": "https://ispcfood.netlify.app/error",
103 "pending": "https://ispcfood.netlify.app/pendiente"
104 }
105 preference = mp_service.create_preference(
106 items=items,
107 external_reference=str(payment_request.id),
108 payer_email=email,
109 notification_url=notification_url,
110 back_urls=back_urls
111 )
112
113 if not preference:
114 payment_request.status = "error"
115 payment_request.save()
116 return Response(
117 {"error": "Error al crear la preferencia de pago"},
118 status=status.HTTP_500_INTERNAL_SERVER_ERROR
119 )
120
121 # Actualizar solicitud de pago con datos de la preferencia
122 payment_request.preference_id = preference.get("id")
123 payment_request.init_point = preference.get("init_point")
124 payment_request.save()
125
126 # Devolver datos al cliente
127 return Response({
128 "init_point": preference.get("init_point"),
129 "preference_id": preference.get("id"),
130 "payment_request_id": str(payment_request.id)
131 }, status=status.HTTP_201_CREATED)
132
133
134class WebhookView(APIView):
135 """
136 Endpoint para recibir notificaciones (webhooks) de Mercado Pago
137 """
138
139 def post(self, request, *args, **kwargs):
140 logger.info(f"Webhook recibido: {request.data}")
141
142 # Validar datos de entrada
143 serializer = WebhookNotificationSerializer(data=request.data)
144 if not serializer.is_valid():
145 logger.error(f"Datos inválidos en webhook: {serializer.errors}")
146 return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
147
148 data = serializer.validated_data
149
150 # Determinar el tipo de notificación
151 if 'topic' in data and data['topic'] == 'merchant_order':
152 # Notificación IPNv1 de Merchant Order
153 notification_type = 'merchant_order'
154 resource_id = data.get('id')
155 elif 'topic' in data and data['topic'] == 'payment':
156 # Notificación IPNv1 de Payment
157 notification_type = 'payment'
158 resource_id = data.get('id')
159 elif 'resource' in data and 'topic' in data:
160 # Notificación IPNv2
161 notification_type = data.get('topic')
162 resource_id = data.get('resource', '').split('/')[-1]
163 elif 'type' in data:
164 # Notificación directa de Webhook
165 notification_type = data.get('type')
166 resource_id = data.get('data', {}).get('id')
167 else:
168 logger.error(f"Tipo de notificación no reconocido: {data}")
169 return Response({"error": "Tipo de notificación no reconocido"}, status=status.HTTP_400_BAD_REQUEST)
170 # Guardar la notificación en la base de datos
171 notification = PaymentNotification.objects.create(
172 topic=notification_type,
173 payment_id=resource_id if notification_type == 'payment' else None,
174 raw_data=data
175 )
176
177 # Si es una notificación de pago, procesarla
178 if notification_type == 'payment':
179 self._process_payment_notification(notification)
180
181 # Siempre responder con éxito para que Mercado Pago no reintente
182 return HttpResponse(status=200)
183
184 def _process_payment_notification(self, notification):
185 """
186 Procesa una notificación de pago
187 """
188 try:
189 mp_service = MercadoPagoService()
190 payment_data = mp_service.get_payment(notification.payment_id)
191
192 if not payment_data:
193 logger.error(f"No se pudo obtener información del pago {notification.payment_id}")
194 return
195
196 # Obtener la referencia externa (ID de la solicitud de pago)
197 external_reference = payment_data.get('external_reference')
198 if not external_reference:
199 logger.error(f"Pago sin referencia externa: {notification.payment_id}")
200 return
201
202 # Buscar la solicitud de pago
203 try:
204 payment_request = PaymentRequest.objects.get(id=external_reference)
205 except PaymentRequest.DoesNotExist:
206 logger.error(f"No se encontró la solicitud de pago con ID {external_reference}")
207 return
208
209 # Vincular la notificación a la solicitud de pago
210 notification.payment_request = payment_request
211 notification.save()
212
213 # Actualizar el estado de la solicitud de pago
214 payment_status = payment_data.get('status')
215 payment_request.status = payment_status
216 payment_request.save()
217
218 # Si el pago fue aprobado, confirmar el pedido
219 if payment_status == 'approved':
220 logger.info(f"Pago aprobado, confirmando pedido: {payment_request.id}")
221
222 cart_service = CartService()
223 success, response = cart_service.confirm_order(
224 payment_request.user_token,
225 notification.payment_id
226 )
227
228 if success:
229 notification.processed = True
230 notification.save()
231 logger.info(f"Pedido confirmado correctamente: {payment_request.id}")
232 else:
233 logger.error(f"Error al confirmar pedido: {response}")
234
235 except Exception as e:
236 logger.exception(f"Error al procesar notificación de pago: {str(e)}")
237
239 """
240 Vista para manejar la URL de éxito de Mercado Pago, consultar el backend principal y redirigir al frontend con el ticket
241 """
242 def get(self, request: HttpRequest):
243 external_reference = request.GET.get('external_reference')
244 payment_id = request.GET.get('payment_id')
245 if external_reference:
246 # Consultar al backend principal para obtener los datos del ticket
247 try:
248 url = f"{settings.MAIN_BACKEND_URL}/appCART/ticket/{external_reference}/"
249 resp = requests.get(url, timeout=10)
250 if resp.status_code == 200:
251 # Redirigir al frontend local solo a /exito
252 return redirect("http://localhost:4200/exito")
253 else:
254 logger.error(f"No se pudo obtener el ticket del backend principal: {resp.status_code} - {resp.text}")
255 return redirect("http://localhost:4200/error")
256 except Exception as e:
257 logger.exception(f"Error al consultar el backend principal para el ticket: {str(e)}")
258 return redirect("http://localhost:4200/error")
259 else:
260 logger.error("No se encontró external_reference en la URL de éxito")
261 return redirect("http://localhost:4200/error")
post(self, request, *args, **kwargs)
Definition views.py:28
get(self, HttpRequest request)
Definition views.py:242
post(self, request, *args, **kwargs)
Definition views.py:139
_process_payment_notification(self, notification)
Definition views.py:184