All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 33s
319 lines
13 KiB
Python
319 lines
13 KiB
Python
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)
|
|
else:
|
|
username = request.data.get('username')
|
|
phone = request.data.get('phone', username)
|
|
if username and User.objects.filter(username=username).exists():
|
|
user = User.objects.get(username=username)
|
|
CustomerProfile.objects.get_or_create(user=user, defaults={'phone': phone or username})
|
|
return Response({"message": "User account already registered"}, status=status.HTTP_200_OK)
|
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
class UserProfileView(APIView):
|
|
permission_classes = [permissions.AllowAny]
|
|
|
|
def get(self, request):
|
|
username = request.query_params.get('username') or request.query_params.get('phone')
|
|
user = None
|
|
if request.user and request.user.is_authenticated:
|
|
user = request.user
|
|
elif username:
|
|
clean_digits = ''.join(c for c in str(username) if c.isdigit())
|
|
raw_10 = clean_digits[-10:] if len(clean_digits) >= 10 else clean_digits
|
|
from django.db.models import Q
|
|
user = User.objects.filter(Q(username__icontains=raw_10) | Q(username=username)).first()
|
|
|
|
if not user:
|
|
return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
profile = getattr(user, 'customer_profile', None)
|
|
return Response({
|
|
"username": user.username,
|
|
"first_name": user.first_name,
|
|
"last_name": user.last_name,
|
|
"email": user.email,
|
|
"phone": profile.phone if profile else user.username,
|
|
"address": profile.default_address if profile else ""
|
|
})
|
|
|
|
def put(self, request):
|
|
username = request.data.get('username') or request.data.get('phone')
|
|
user = None
|
|
if request.user and request.user.is_authenticated:
|
|
user = request.user
|
|
elif username:
|
|
clean_digits = ''.join(c for c in str(username) if c.isdigit())
|
|
raw_10 = clean_digits[-10:] if len(clean_digits) >= 10 else clean_digits
|
|
from django.db.models import Q
|
|
user = User.objects.filter(Q(username__icontains=raw_10) | Q(username=username)).first()
|
|
|
|
if not user:
|
|
return Response({"error": "User profile not found"}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
if 'email' in request.data:
|
|
user.email = request.data['email']
|
|
if 'first_name' in request.data:
|
|
user.first_name = request.data['first_name']
|
|
if 'last_name' in request.data:
|
|
user.last_name = request.data['last_name']
|
|
user.save()
|
|
|
|
from orders.models import CustomerProfile
|
|
profile, _ = CustomerProfile.objects.get_or_create(user=user)
|
|
if 'phone' in request.data:
|
|
profile.phone = request.data['phone']
|
|
if 'address' in request.data:
|
|
profile.default_address = request.data['address']
|
|
profile.save()
|
|
|
|
audit_logger.info(f"AUDIT - User Profile Updated: Username '{user.username}', Email '{user.email}'")
|
|
|
|
return Response({
|
|
"message": "Profile updated successfully",
|
|
"username": user.username,
|
|
"email": user.email,
|
|
"first_name": user.first_name,
|
|
"last_name": user.last_name
|
|
}, status=status.HTTP_200_OK)
|
|
|
|
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)
|
|
|
|
|