import logging import json import urllib.request import urllib.error from django.conf import settings from django.core.cache import cache from rest_framework import status, permissions from rest_framework.response import Response from rest_framework.views import APIView from django.contrib.auth.models import User from .serializers import UserRegisterSerializer audit_logger = logging.getLogger('audit') class UserRegisterView(APIView): permission_classes = [permissions.AllowAny] def post(self, request): serializer = UserRegisterSerializer(data=request.data) if serializer.is_valid(): serializer.save() audit_logger.info(f"AUDIT - User Register: Username '{serializer.validated_data.get('username')}' registered successfully.") return Response({"message": "User registered successfully"}, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class UserProfileView(APIView): permission_classes = [permissions.IsAuthenticated] def get(self, request): user = request.user profile = getattr(user, 'customer_profile', None) return Response({ "username": user.username, "email": user.email, "phone": profile.phone if profile else "", "address": profile.default_address if profile else "" }) def put(self, request): user = request.user profile = getattr(user, 'customer_profile', None) user.email = request.data.get('email', user.email) user.save() if profile: profile.phone = request.data.get('phone', profile.phone) profile.default_address = request.data.get('address', profile.default_address) profile.save() return Response({"message": "Profile updated successfully"}) def send_msg91_whatsapp_direct(mobile_number, otp_code, template_name="mobile_number_verification"): clean_digits = ''.join(c for c in str(mobile_number) if c.isdigit()) target_number = f"91{clean_digits}" if len(clean_digits) == 10 else clean_digits msg91_payload = { "integrated_number": "918610694498", "content_type": "template", "payload": { "messaging_product": "whatsapp", "type": "template", "template": { "name": template_name, "language": { "code": "en", "policy": "deterministic" }, "namespace": None, "to_and_components": [ { "to": [target_number], "components": { "body_1": { "type": "text", "value": str(otp_code) }, "button_1": { "subtype": "url", "type": "text", "value": str(otp_code) } } } ] } } } try: req_data = json.dumps(msg91_payload).encode('utf-8') req = urllib.request.Request( 'https://control.msg91.com/api/v5/whatsapp/whatsapp-outbound-message/bulk/', data=req_data, headers={ 'accept': 'application/json', 'authkey': '563180AluxJHIeTGn6aaf97aeP1', 'content-type': 'application/json' } ) with urllib.request.urlopen(req, timeout=10) as resp: body_bytes = resp.read() try: res_data = json.loads(body_bytes.decode('utf-8')) except Exception: res_data = {"text": body_bytes.decode('utf-8', errors='ignore')} return resp.status, res_data except urllib.error.HTTPError as e: err_body = e.read().decode('utf-8', errors='ignore') try: err_data = json.loads(err_body) except Exception: err_data = {"text": err_body} return e.code, err_data except Exception as e: return 500, {"error": str(e)} class SendWhatsAppOTPView(APIView): permission_classes = [permissions.AllowAny] def post(self, request): mobile_number = request.data.get('mobile_number') otp_code = request.data.get('otp_code') template_name = request.data.get('template_name', 'mobile_number_verification') purpose = request.data.get('purpose', 'login') if not mobile_number or not otp_code: return Response({"error": "mobile_number and otp_code are required"}, status=status.HTTP_400_BAD_REQUEST) clean_phone = str(mobile_number).replace('+', '').strip() raw_10 = clean_phone[-10:] if len(clean_phone) >= 10 else clean_phone # Account existence validation from django.db.models import Q user_exists = None try: from orders.models import CustomerProfile user_exists = ( User.objects.filter(Q(username__icontains=raw_10) | Q(email__icontains=raw_10)).exists() or CustomerProfile.objects.filter(Q(phone__icontains=raw_10) | Q(user__username__icontains=raw_10)).exists() ) except Exception as db_err: audit_logger.warning(f"DB lookup notice for {mobile_number}: {db_err}") user_exists = None if purpose == 'login' and user_exists is False: audit_logger.warning(f"AUDIT - Login attempt for non-existent mobile: '{mobile_number}'") return Response({ "status": "error", "message": "Account not found. Please create a patron account first." }, status=status.HTTP_404_NOT_FOUND) if purpose == 'register' and user_exists is True: audit_logger.warning(f"AUDIT - Register attempt for existing mobile: '{mobile_number}'") return Response({ "status": "error", "message": "An account with this mobile number already exists. Please Sign In." }, status=status.HTTP_400_BAD_REQUEST) audit_logger.info(f"AUDIT - Send WhatsApp OTP: mobile '{mobile_number}', OTP '{otp_code}'") # Store OTP in backend cache for 5 minutes (300 seconds) cache.set(f"whatsapp_otp_{clean_phone}", str(otp_code), timeout=300) whatsapp_urls = [ getattr(settings, 'WHATSAPP_SERVICE_URL', 'https://betawhatsapp.tradhox.com/api/send/'), 'https://betawhatsapp.tradhox.com/send/', 'http://betawhatsapp.tradhox.com/api/send/', 'http://betawhatsapp.tradhox.com/send/', 'http://127.0.0.1:8081/api/send/', 'http://127.0.0.1:8081/send/' ] payload = { "mobile_number": mobile_number, "otp_code": otp_code, "template_name": template_name } req_data = json.dumps(payload).encode('utf-8') last_error = None res_data = None status_code = 500 for url in whatsapp_urls: try: req = urllib.request.Request( url, data=req_data, headers={'Content-Type': 'application/json', 'User-Agent': 'TradhoxBackend/1.0'} ) with urllib.request.urlopen(req, timeout=6) as resp: status_code = resp.status body_bytes = resp.read() try: res_data = json.loads(body_bytes.decode('utf-8')) except Exception: res_data = {"text": body_bytes.decode('utf-8', errors='ignore')} if status_code == 200 and res_data.get('status') != 'error': return Response({ "status": "success", "message": f"WhatsApp OTP dispatched to {mobile_number}", "mobile_number": mobile_number, "gateway_response": res_data }, status=status.HTTP_200_OK) except urllib.error.HTTPError as e: status_code = e.code err_body = e.read().decode('utf-8', errors='ignore') try: res_data = json.loads(err_body) except Exception: res_data = {"text": err_body} last_error = f"HTTP {e.code}: {res_data}" audit_logger.warning(f"WhatsApp gateway attempt {url} returned HTTP {e.code}: {res_data}") except Exception as e: last_error = str(e) audit_logger.warning(f"WhatsApp gateway attempt {url} failed: {e}") # Fallback directly to MSG91 engine audit_logger.info(f"Attempting direct MSG91 WhatsApp dispatch for {mobile_number}") msg91_status, msg91_res = send_msg91_whatsapp_direct(mobile_number, otp_code, template_name) if msg91_status == 200 and msg91_res.get('status') == 'success': return Response({ "status": "success", "message": f"WhatsApp OTP dispatched to {mobile_number}", "mobile_number": mobile_number, "gateway_response": msg91_res }, status=status.HTTP_200_OK) if status_code == 200: return Response({ "status": "success", "message": f"WhatsApp OTP request submitted for {mobile_number}", "mobile_number": mobile_number, "gateway_response": res_data }, status=status.HTTP_200_OK) else: return Response({ "status": "error", "message": f"WhatsApp service gateway error: {last_error or msg91_res}", "mobile_number": mobile_number }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class VerifyOTPView(APIView): permission_classes = [permissions.AllowAny] def post(self, request): mobile_number = request.data.get('mobile_number') otp_code = request.data.get('otp_code') if not mobile_number or not otp_code: return Response({"error": "mobile_number and otp_code are required"}, status=status.HTTP_400_BAD_REQUEST) clean_phone = str(mobile_number).replace('+', '').strip() stored_otp = cache.get(f"whatsapp_otp_{clean_phone}") if stored_otp and str(stored_otp).strip() == str(otp_code).strip(): audit_logger.info(f"AUDIT - OTP Verification SUCCESS: mobile '{mobile_number}'") return Response({ "status": "success", "message": "OTP verified successfully" }, status=status.HTTP_200_OK) else: audit_logger.warning(f"AUDIT - OTP Verification FAILED: mobile '{mobile_number}', submitted '{otp_code}', expected '{stored_otp}'") return Response({ "status": "error", "message": "Invalid or expired OTP verification code. Access denied." }, status=status.HTTP_400_BAD_REQUEST)