feat: implement customer registration, profile management, and auth tests
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 35s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 35s
This commit is contained in:
parent
d32c653013
commit
4ca070ad8c
3 changed files with 257 additions and 60 deletions
|
|
@ -5,18 +5,19 @@ from orders.models import CustomerProfile
|
||||||
class UserRegisterSerializer(serializers.ModelSerializer):
|
class UserRegisterSerializer(serializers.ModelSerializer):
|
||||||
phone = serializers.CharField(write_only=True, required=False, allow_blank=True)
|
phone = serializers.CharField(write_only=True, required=False, allow_blank=True)
|
||||||
address = serializers.CharField(write_only=True, required=False, allow_blank=True)
|
address = serializers.CharField(write_only=True, required=False, allow_blank=True)
|
||||||
password = serializers.CharField(write_only=True)
|
password = serializers.CharField(write_only=True, required=False, default='TradhoxOtpUser2026!')
|
||||||
first_name = serializers.CharField(required=False, allow_blank=True)
|
first_name = serializers.CharField(required=False, allow_blank=True)
|
||||||
last_name = serializers.CharField(required=False, allow_blank=True)
|
last_name = serializers.CharField(required=False, allow_blank=True)
|
||||||
|
email = serializers.EmailField(required=False, allow_blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['username', 'email', 'password', 'first_name', 'last_name', 'phone', 'address']
|
fields = ['username', 'email', 'password', 'first_name', 'last_name', 'phone', 'address']
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
phone = validated_data.pop('phone', '')
|
phone = validated_data.pop('phone', '') or validated_data.get('username')
|
||||||
address = validated_data.pop('address', '')
|
address = validated_data.pop('address', '')
|
||||||
password = validated_data.pop('password')
|
password = validated_data.pop('password', 'TradhoxOtpUser2026!')
|
||||||
|
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
username=validated_data['username'],
|
username=validated_data['username'],
|
||||||
|
|
@ -25,5 +26,12 @@ class UserRegisterSerializer(serializers.ModelSerializer):
|
||||||
last_name=validated_data.get('last_name', ''),
|
last_name=validated_data.get('last_name', ''),
|
||||||
password=password
|
password=password
|
||||||
)
|
)
|
||||||
CustomerProfile.objects.create(user=user, phone=phone, default_address=address)
|
CustomerProfile.objects.create(
|
||||||
|
user=user,
|
||||||
|
phone=phone,
|
||||||
|
default_address=address,
|
||||||
|
gender='Prefer not to say',
|
||||||
|
wallet_balance=0.00,
|
||||||
|
loyalty_coins=200
|
||||||
|
)
|
||||||
return user
|
return user
|
||||||
|
|
|
||||||
123
api/tests.py
123
api/tests.py
|
|
@ -1,3 +1,124 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework.test import APIClient
|
||||||
|
from rest_framework import status
|
||||||
|
from orders.models import CustomerProfile
|
||||||
|
|
||||||
# Create your tests here.
|
class CustomerAuthAndProfileTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
self.test_phone = '9876543210'
|
||||||
|
self.test_user = User.objects.create_user(
|
||||||
|
username=self.test_phone,
|
||||||
|
first_name='Existing',
|
||||||
|
last_name='Patron',
|
||||||
|
email='patron@example.com',
|
||||||
|
password='TestPassword123!'
|
||||||
|
)
|
||||||
|
self.test_profile = CustomerProfile.objects.create(
|
||||||
|
user=self.test_user,
|
||||||
|
phone=self.test_phone,
|
||||||
|
default_address='123 Heritage Lane, Chennai',
|
||||||
|
wallet_balance=150.00,
|
||||||
|
loyalty_coins=250,
|
||||||
|
gender='Female'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_signup_successful(self):
|
||||||
|
new_phone = '9123456789'
|
||||||
|
payload = {
|
||||||
|
'phone': new_phone,
|
||||||
|
'full_name': 'New Artisan Patron',
|
||||||
|
'email': 'newpatron@tradhox.com',
|
||||||
|
'address': '45 Craft Colony, Kanchipuram'
|
||||||
|
}
|
||||||
|
response = self.client.post('/api/v1/customer/auth/register/', payload, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(response.data['username'], new_phone)
|
||||||
|
self.assertEqual(response.data['phone'], new_phone)
|
||||||
|
self.assertEqual(response.data['first_name'], 'New')
|
||||||
|
self.assertEqual(response.data['last_name'], 'Artisan Patron')
|
||||||
|
|
||||||
|
# Verify saved in database
|
||||||
|
saved_user = User.objects.get(username=new_phone)
|
||||||
|
self.assertEqual(saved_user.first_name, 'New')
|
||||||
|
self.assertEqual(saved_user.email, 'newpatron@tradhox.com')
|
||||||
|
self.assertEqual(saved_user.customer_profile.phone, new_phone)
|
||||||
|
self.assertEqual(saved_user.customer_profile.loyalty_coins, 200)
|
||||||
|
|
||||||
|
def test_signup_duplicate_phone_rejected(self):
|
||||||
|
# Attempt to register with already existing phone
|
||||||
|
payload = {
|
||||||
|
'phone': self.test_phone,
|
||||||
|
'full_name': 'Duplicate Person',
|
||||||
|
'email': 'duplicate@example.com'
|
||||||
|
}
|
||||||
|
response = self.client.post('/api/v1/customer/auth/register/', payload, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertIn('already registered', response.data['error'].lower())
|
||||||
|
self.assertEqual(response.data['code'], 'already_registered')
|
||||||
|
|
||||||
|
def test_signup_invalid_phone_rejected(self):
|
||||||
|
payload = {
|
||||||
|
'phone': '12345', # Not 10 digits
|
||||||
|
'full_name': 'Invalid Phone User'
|
||||||
|
}
|
||||||
|
response = self.client.post('/api/v1/customer/auth/register/', payload, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertIn('valid 10-digit', response.data['error'])
|
||||||
|
|
||||||
|
def test_get_user_profile_by_phone(self):
|
||||||
|
response = self.client.get(f'/api/v1/customer/profile/?phone={self.test_phone}')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data['phone'], self.test_phone)
|
||||||
|
self.assertEqual(response.data['username'], self.test_phone)
|
||||||
|
self.assertEqual(response.data['first_name'], 'Existing')
|
||||||
|
self.assertEqual(response.data['last_name'], 'Patron')
|
||||||
|
self.assertEqual(response.data['full_name'], 'Existing Patron')
|
||||||
|
self.assertEqual(response.data['email'], 'patron@example.com')
|
||||||
|
self.assertEqual(response.data['gender'], 'Female')
|
||||||
|
self.assertEqual(response.data['wallet_balance'], 150.00)
|
||||||
|
self.assertEqual(response.data['loyalty_coins'], 250)
|
||||||
|
self.assertEqual(response.data['address'], '123 Heritage Lane, Chennai')
|
||||||
|
|
||||||
|
def test_put_user_profile_update(self):
|
||||||
|
update_payload = {
|
||||||
|
'phone': self.test_phone,
|
||||||
|
'full_name': 'Updated Name',
|
||||||
|
'email': 'updated@example.com',
|
||||||
|
'gender': 'Male',
|
||||||
|
'address': '789 Updated St, Coimbatore'
|
||||||
|
}
|
||||||
|
response = self.client.put('/api/v1/customer/profile/', update_payload, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(response.data['first_name'], 'Updated')
|
||||||
|
self.assertEqual(response.data['last_name'], 'Name')
|
||||||
|
self.assertEqual(response.data['email'], 'updated@example.com')
|
||||||
|
self.assertEqual(response.data['gender'], 'Male')
|
||||||
|
self.assertEqual(response.data['address'], '789 Updated St, Coimbatore')
|
||||||
|
|
||||||
|
# Verify DB persisted
|
||||||
|
self.test_user.refresh_from_db()
|
||||||
|
self.assertEqual(self.test_user.first_name, 'Updated')
|
||||||
|
self.assertEqual(self.test_user.email, 'updated@example.com')
|
||||||
|
self.assertEqual(self.test_user.customer_profile.gender, 'Male')
|
||||||
|
|
||||||
|
def test_send_otp_duplicate_register_fails(self):
|
||||||
|
payload = {
|
||||||
|
'mobile_number': self.test_phone,
|
||||||
|
'otp_code': '123456',
|
||||||
|
'purpose': 'register'
|
||||||
|
}
|
||||||
|
response = self.client.post('/api/v1/customer/auth/send-whatsapp/', payload, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertIn('already exists', response.data['message'])
|
||||||
|
|
||||||
|
def test_send_otp_nonexistent_login_fails(self):
|
||||||
|
payload = {
|
||||||
|
'mobile_number': '9999900000',
|
||||||
|
'otp_code': '123456',
|
||||||
|
'purpose': 'login'
|
||||||
|
}
|
||||||
|
response = self.client.post('/api/v1/customer/auth/send-whatsapp/', payload, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
self.assertIn('Account not found', response.data['message'])
|
||||||
|
|
|
||||||
178
api/views.py
178
api/views.py
|
|
@ -8,6 +8,7 @@ from rest_framework import status, permissions
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from orders.models import CustomerProfile
|
||||||
from .serializers import UserRegisterSerializer
|
from .serializers import UserRegisterSerializer
|
||||||
|
|
||||||
audit_logger = logging.getLogger('audit')
|
audit_logger = logging.getLogger('audit')
|
||||||
|
|
@ -16,60 +17,116 @@ class UserRegisterView(APIView):
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = UserRegisterSerializer(data=request.data)
|
raw_phone = request.data.get('phone') or request.data.get('username') or ''
|
||||||
|
clean_digits = ''.join(c for c in str(raw_phone) if c.isdigit())
|
||||||
|
phone_10 = clean_digits[-10:] if len(clean_digits) >= 10 else clean_digits
|
||||||
|
|
||||||
|
if not phone_10 or len(phone_10) != 10:
|
||||||
|
return Response(
|
||||||
|
{"error": "A valid 10-digit mobile phone number is required."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for duplicate registration using phone number as primary key
|
||||||
|
existing_user = (
|
||||||
|
User.objects.filter(username=phone_10).first()
|
||||||
|
or User.objects.filter(customer_profile__phone=phone_10).first()
|
||||||
|
)
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
audit_logger.warning(f"AUDIT - Duplicate registration attempt for phone '{phone_10}'")
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"error": "This phone number is already registered. Please sign in instead.",
|
||||||
|
"code": "already_registered",
|
||||||
|
"phone": phone_10
|
||||||
|
},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prepare normalized registration data
|
||||||
|
req_data = request.data.copy()
|
||||||
|
req_data['username'] = phone_10
|
||||||
|
req_data['phone'] = phone_10
|
||||||
|
if not req_data.get('password'):
|
||||||
|
req_data['password'] = 'TradhoxOtpUser2026!'
|
||||||
|
|
||||||
|
if 'full_name' in req_data and not req_data.get('first_name'):
|
||||||
|
parts = str(req_data['full_name']).strip().split()
|
||||||
|
req_data['first_name'] = parts[0] if parts else ''
|
||||||
|
req_data['last_name'] = ' '.join(parts[1:]) if len(parts) > 1 else ''
|
||||||
|
elif 'name' in req_data and not req_data.get('first_name'):
|
||||||
|
parts = str(req_data['name']).strip().split()
|
||||||
|
req_data['first_name'] = parts[0] if parts else ''
|
||||||
|
req_data['last_name'] = ' '.join(parts[1:]) if len(parts) > 1 else ''
|
||||||
|
|
||||||
|
serializer = UserRegisterSerializer(data=req_data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
user = serializer.save()
|
||||||
audit_logger.info(f"AUDIT - User Register: Username '{serializer.validated_data.get('username')}' registered successfully.")
|
profile = getattr(user, 'customer_profile', None)
|
||||||
return Response({"message": "User registered successfully"}, status=status.HTTP_201_CREATED)
|
audit_logger.info(f"AUDIT - User Register: Username/Phone '{phone_10}' registered successfully.")
|
||||||
else:
|
return Response({
|
||||||
username = request.data.get('username')
|
"message": "User registered successfully",
|
||||||
phone = request.data.get('phone', username)
|
"username": user.username,
|
||||||
if username and User.objects.filter(username=username).exists():
|
"phone": profile.phone if profile else user.username,
|
||||||
user = User.objects.get(username=username)
|
"first_name": user.first_name,
|
||||||
CustomerProfile.objects.get_or_create(user=user, defaults={'phone': phone or username})
|
"last_name": user.last_name,
|
||||||
return Response({"message": "User account already registered"}, status=status.HTTP_200_OK)
|
"full_name": f"{user.first_name} {user.last_name}".strip() or user.username,
|
||||||
|
"email": user.email,
|
||||||
|
"wallet_balance": float(profile.wallet_balance) if profile else 0.0,
|
||||||
|
"loyalty_coins": profile.loyalty_coins if profile else 200,
|
||||||
|
"address": profile.default_address if profile else ""
|
||||||
|
}, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
class UserProfileView(APIView):
|
class UserProfileView(APIView):
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
username = request.query_params.get('username') or request.query_params.get('phone')
|
identifier = request.query_params.get('phone') or request.query_params.get('username')
|
||||||
user = None
|
user = None
|
||||||
if request.user and request.user.is_authenticated:
|
if request.user and request.user.is_authenticated:
|
||||||
user = request.user
|
user = request.user
|
||||||
elif username:
|
elif identifier:
|
||||||
clean_digits = ''.join(c for c in str(username) if c.isdigit())
|
clean_digits = ''.join(c for c in str(identifier) if c.isdigit())
|
||||||
raw_10 = clean_digits[-10:] if len(clean_digits) >= 10 else clean_digits
|
raw_10 = clean_digits[-10:] if len(clean_digits) >= 10 else clean_digits
|
||||||
from django.db.models import Q
|
user = (
|
||||||
user = User.objects.filter(Q(username__icontains=raw_10) | Q(username=username)).first()
|
User.objects.filter(username=raw_10).first()
|
||||||
|
or User.objects.filter(customer_profile__phone=raw_10).first()
|
||||||
|
or User.objects.filter(username=str(identifier)).first()
|
||||||
|
)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
profile = getattr(user, 'customer_profile', None)
|
profile, _ = CustomerProfile.objects.get_or_create(user=user, defaults={'phone': user.username})
|
||||||
return Response({
|
return Response({
|
||||||
"username": user.username,
|
"username": user.username,
|
||||||
"first_name": user.first_name,
|
"first_name": user.first_name,
|
||||||
"last_name": user.last_name,
|
"last_name": user.last_name,
|
||||||
|
"full_name": f"{user.first_name} {user.last_name}".strip() or user.username,
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"phone": profile.phone if profile else user.username,
|
"phone": profile.phone or user.username,
|
||||||
"gender": profile.gender if profile else "Prefer not to say",
|
"gender": profile.gender or "Prefer not to say",
|
||||||
"wallet_balance": float(profile.wallet_balance) if profile else 0.0,
|
"wallet_balance": float(profile.wallet_balance),
|
||||||
"loyalty_coins": profile.loyalty_coins if profile else 200,
|
"loyalty_coins": profile.loyalty_coins,
|
||||||
"address": profile.default_address if profile else ""
|
"address": profile.default_address or ""
|
||||||
})
|
}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
username = request.data.get('username') or request.data.get('phone')
|
identifier = request.data.get('phone') or request.data.get('username')
|
||||||
user = None
|
user = None
|
||||||
if request.user and request.user.is_authenticated:
|
if request.user and request.user.is_authenticated:
|
||||||
user = request.user
|
user = request.user
|
||||||
elif username:
|
elif identifier:
|
||||||
clean_digits = ''.join(c for c in str(username) if c.isdigit())
|
clean_digits = ''.join(c for c in str(identifier) if c.isdigit())
|
||||||
raw_10 = clean_digits[-10:] if len(clean_digits) >= 10 else clean_digits
|
raw_10 = clean_digits[-10:] if len(clean_digits) >= 10 else clean_digits
|
||||||
from django.db.models import Q
|
user = (
|
||||||
user = User.objects.filter(Q(username__icontains=raw_10) | Q(username=username)).first()
|
User.objects.filter(username=raw_10).first()
|
||||||
|
or User.objects.filter(customer_profile__phone=raw_10).first()
|
||||||
|
or User.objects.filter(username=str(identifier)).first()
|
||||||
|
)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
return Response({"error": "User profile not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({"error": "User profile not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
@ -80,12 +137,21 @@ class UserProfileView(APIView):
|
||||||
user.first_name = request.data['first_name']
|
user.first_name = request.data['first_name']
|
||||||
if 'last_name' in request.data:
|
if 'last_name' in request.data:
|
||||||
user.last_name = request.data['last_name']
|
user.last_name = request.data['last_name']
|
||||||
|
if 'full_name' in request.data:
|
||||||
|
parts = str(request.data['full_name']).strip().split()
|
||||||
|
user.first_name = parts[0] if parts else ''
|
||||||
|
user.last_name = ' '.join(parts[1:]) if len(parts) > 1 else ''
|
||||||
|
elif 'name' in request.data:
|
||||||
|
parts = str(request.data['name']).strip().split()
|
||||||
|
user.first_name = parts[0] if parts else ''
|
||||||
|
user.last_name = ' '.join(parts[1:]) if len(parts) > 1 else ''
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
from orders.models import CustomerProfile
|
profile, _ = CustomerProfile.objects.get_or_create(user=user, defaults={'phone': user.username})
|
||||||
profile, _ = CustomerProfile.objects.get_or_create(user=user)
|
if 'phone' in request.data and request.data['phone']:
|
||||||
if 'phone' in request.data:
|
clean_phone_digits = ''.join(c for c in str(request.data['phone']) if c.isdigit())
|
||||||
profile.phone = request.data['phone']
|
new_phone_10 = clean_phone_digits[-10:] if len(clean_phone_digits) >= 10 else clean_phone_digits
|
||||||
|
profile.phone = new_phone_10
|
||||||
if 'gender' in request.data:
|
if 'gender' in request.data:
|
||||||
profile.gender = request.data['gender']
|
profile.gender = request.data['gender']
|
||||||
if 'wallet_balance' in request.data:
|
if 'wallet_balance' in request.data:
|
||||||
|
|
@ -101,12 +167,15 @@ class UserProfileView(APIView):
|
||||||
return Response({
|
return Response({
|
||||||
"message": "Profile updated successfully",
|
"message": "Profile updated successfully",
|
||||||
"username": user.username,
|
"username": user.username,
|
||||||
|
"phone": profile.phone or user.username,
|
||||||
"email": user.email,
|
"email": user.email,
|
||||||
"first_name": user.first_name,
|
"first_name": user.first_name,
|
||||||
"last_name": user.last_name,
|
"last_name": user.last_name,
|
||||||
|
"full_name": f"{user.first_name} {user.last_name}".strip() or user.username,
|
||||||
"gender": profile.gender,
|
"gender": profile.gender,
|
||||||
"wallet_balance": float(profile.wallet_balance),
|
"wallet_balance": float(profile.wallet_balance),
|
||||||
"loyalty_coins": profile.loyalty_coins
|
"loyalty_coins": profile.loyalty_coins,
|
||||||
|
"address": profile.default_address
|
||||||
}, status=status.HTTP_200_OK)
|
}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def send_msg91_whatsapp_direct(mobile_number, otp_code, template_name="mobile_number_verification"):
|
def send_msg91_whatsapp_direct(mobile_number, otp_code, template_name="mobile_number_verification"):
|
||||||
|
|
@ -187,39 +256,37 @@ class SendWhatsAppOTPView(APIView):
|
||||||
return Response({"error": "mobile_number and otp_code are required"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": "mobile_number and otp_code are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
clean_phone = str(mobile_number).replace('+', '').strip()
|
clean_phone = str(mobile_number).replace('+', '').strip()
|
||||||
raw_10 = clean_phone[-10:] if len(clean_phone) >= 10 else clean_phone
|
raw_digits = ''.join(c for c in clean_phone if c.isdigit())
|
||||||
|
phone_10 = raw_digits[-10:] if len(raw_digits) >= 10 else raw_digits
|
||||||
|
|
||||||
# Account existence validation
|
if not phone_10 or len(phone_10) != 10:
|
||||||
from django.db.models import Q
|
return Response({"error": "Please enter a valid 10-digit mobile number"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
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:
|
# Account existence validation using phone number as primary key
|
||||||
audit_logger.warning(f"AUDIT - Login attempt for non-existent mobile: '{mobile_number}'")
|
user_exists = (
|
||||||
|
User.objects.filter(username=phone_10).exists()
|
||||||
|
or CustomerProfile.objects.filter(phone=phone_10).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
if purpose == 'login' and not user_exists:
|
||||||
|
audit_logger.warning(f"AUDIT - Login attempt for non-existent mobile: '{phone_10}'")
|
||||||
return Response({
|
return Response({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "Account not found. Please create a patron account first."
|
"message": "Account not found. Please create a patron account first."
|
||||||
}, status=status.HTTP_404_NOT_FOUND)
|
}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
if purpose == 'register' and user_exists is True:
|
if purpose == 'register' and user_exists:
|
||||||
audit_logger.warning(f"AUDIT - Register attempt for existing mobile: '{mobile_number}'")
|
audit_logger.warning(f"AUDIT - Register attempt for existing mobile: '{phone_10}'")
|
||||||
return Response({
|
return Response({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "An account with this mobile number already exists. Please Sign In."
|
"message": "An account with this phone number already exists. Please Sign In."
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
audit_logger.info(f"AUDIT - Send WhatsApp OTP: mobile '{mobile_number}', OTP '{otp_code}'")
|
audit_logger.info(f"AUDIT - Send WhatsApp OTP: mobile '{phone_10}', OTP '{otp_code}'")
|
||||||
|
|
||||||
# Store OTP in backend cache for 5 minutes (300 seconds)
|
# Store OTP in backend cache for 5 minutes (300 seconds)
|
||||||
cache.set(f"whatsapp_otp_{clean_phone}", str(otp_code), timeout=300)
|
cache.set(f"whatsapp_otp_{clean_phone}", str(otp_code), timeout=300)
|
||||||
|
cache.set(f"whatsapp_otp_{phone_10}", str(otp_code), timeout=300)
|
||||||
|
|
||||||
whatsapp_urls = [
|
whatsapp_urls = [
|
||||||
getattr(settings, 'WHATSAPP_SERVICE_URL', 'https://betawhatsapp.tradhox.com/api/send/'),
|
getattr(settings, 'WHATSAPP_SERVICE_URL', 'https://betawhatsapp.tradhox.com/api/send/'),
|
||||||
|
|
@ -313,7 +380,10 @@ class VerifyOTPView(APIView):
|
||||||
return Response({"error": "mobile_number and otp_code are required"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": "mobile_number and otp_code are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
clean_phone = str(mobile_number).replace('+', '').strip()
|
clean_phone = str(mobile_number).replace('+', '').strip()
|
||||||
stored_otp = cache.get(f"whatsapp_otp_{clean_phone}")
|
raw_digits = ''.join(c for c in clean_phone if c.isdigit())
|
||||||
|
phone_10 = raw_digits[-10:] if len(raw_digits) >= 10 else clean_digits
|
||||||
|
|
||||||
|
stored_otp = cache.get(f"whatsapp_otp_{clean_phone}") or cache.get(f"whatsapp_otp_{phone_10}")
|
||||||
|
|
||||||
if stored_otp and str(stored_otp).strip() == str(otp_code).strip():
|
if stored_otp and str(stored_otp).strip() == str(otp_code).strip():
|
||||||
audit_logger.info(f"AUDIT - OTP Verification SUCCESS: mobile '{mobile_number}'")
|
audit_logger.info(f"AUDIT - OTP Verification SUCCESS: mobile '{mobile_number}'")
|
||||||
|
|
@ -327,5 +397,3 @@ class VerifyOTPView(APIView):
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": "Invalid or expired OTP verification code. Access denied."
|
"message": "Invalid or expired OTP verification code. Access denied."
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue