Skip to content

Commit

Permalink
Capture payment phone numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
sravfeyn committed Nov 7, 2024
1 parent 1abdbca commit 5923600
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 6 deletions.
1 change: 1 addition & 0 deletions connectid/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'users.apps.UsersConfig',
'messaging',
'oauth2_provider',
'payments',
'rest_framework',
'axes',
'fcm_django',
Expand Down
Empty file added payments/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions payments/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class PaymentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'payments'
31 changes: 31 additions & 0 deletions payments/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.1.7 on 2024-11-06 16:50

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='PaymentProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(max_length=128, region=None)),
('telecom_provider', models.CharField(max_length=50,blank=True, null=True)),
('is_verified', models.BooleanField(default=False)),
('is_validated', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='payment_profile', to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file added payments/migrations/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions payments/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db import models

from phonenumber_field.modelfields import PhoneNumberField
from users.models import ConnectUser


class PaymentProfile(models.Model):
user = models.OneToOneField(
ConnectUser,
on_delete=models.CASCADE,
related_name='payment_profile'
)
phone_number = PhoneNumberField()
telecom_provider = models.CharField(max_length=50, blank=True, null=True)
# whether the number is verified using OTP
is_verified = models.BooleanField(default=False)
# whether the number is a valid payment receiver
is_validated = models.BooleanField(default=False)

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
71 changes: 71 additions & 0 deletions payments/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from django.http import JsonResponse, HttpResponse, Http404
from django.views.decorators.http import require_POST
from oauth2_provider.decorators import protected_resource
from utils.rest_framework import ClientProtectedResourceAuth
from rest_framework.decorators import api_view
from rest_framework.views import APIView
from users.models import ConnectUser, PhoneDevice
from utils.twilio import lookup_telecom_provider
from .models import PaymentProfile


@api_view(['POST'])
def update_payment_profile_phone(request):
user = request.user
phone_number = request.data.get('phone_number')
telecom_provider = lookup_telecom_provider(phone_number)
payment_profile, created = PaymentProfile.objects.update_or_create(
user=user,
defaults={
'phone_number': phone_number,
'telecom_provider': telecom_provider,
'is_verified': False,
'is_validated': False
}
)
return PhoneDevice.send_otp_httpresponse(phone_number=payment_profile.phone_number, user=payment_profile.user)


@api_view(['POST'])
def confirm_payment_profile_otp(request):
PaymentProfile.objects.get(user=request.user)
payment_profile = request.user.payment_profile
device = PhoneDevice.objects.get(phone_number=payment_profile.phone_number, user=payment_profile.user)
if not device.verify_token(request.data.get('token')):
return JsonResponse({"error": "OTP token is incorrect"}, status=401)

payment_profile.is_verified = True
payment_profile.save()
return JsonResponse({"success": True})


@require_POST
@protected_resource(scopes=[])
def validate_payment_phone_number(request):
username = request.data["username"]
phone_number = request.data["phone_number"]
user = ConnectUser.objects.get(username=username)
profile = getattr(user, "payment_profile")

if not profile or profile.phone_number != phone_number:
raise Http404("Payment number not found")

profile.is_validated = True
return HttpResponse()


class ValidatePhoneNumber(APIView):
authentication_classes = [ClientProtectedResourceAuth]

def post(self, request, *args, **kwargs):
username = request.data["username"]
phone_number = request.data["phone_number"]
user = ConnectUser.objects.get(username=username)
profile = getattr(user, "payment_profile")

if not profile or profile.phone_number != phone_number:
raise Http404("Payment number not found")

profile.is_validated = True
profile.save()
return HttpResponse()
4 changes: 4 additions & 0 deletions users/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.urls import path

from . import views
from payments import views as payment_views

urlpatterns = [
path('', views.test, name='test'),
Expand Down Expand Up @@ -32,4 +33,7 @@
path('fetch_db_key', views.fetch_db_key, name='fetch_db_key'),
path('recover/initiate_deactivation', views.initiate_deactivation, name='initiate_deactivation'),
path('recover/confirm_deactivation', views.confirm_deactivation, name='confirm_deactivation'),
path('profile/payment_phone_number', payment_views.update_payment_profile_phone, name='update_payment_profile_phone'),
path('profile/confirm_payment_otp', payment_views.confirm_payment_profile_otp, name='confirm_payment_profile_otp'),
path('profile/validate_payment_phone_number', payment_views.ValidatePhoneNumber.as_view(), name='validate_payment_phone_number'),
]
32 changes: 26 additions & 6 deletions users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.contrib.auth.hashers import check_password
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.http import HttpResponse, JsonResponse
from django.utils.timezone import now
from django.views import View
Expand Down Expand Up @@ -180,7 +180,9 @@ def confirm_secondary_recovery_otp(request):
status.step = RecoveryStatus.RecoverySteps.RESET_PASSWORD
status.save()
db_key = UserKey.get_or_create_key_for_user(user)
return JsonResponse({"name": user.name, "username": user.username, "db_key": db_key.key})
user_data = {"name": user.name, "username": user.username, "db_key": db_key.key}
user_data.update(user_payment_profile(user))
return JsonResponse(user_data)


@api_view(['POST'])
Expand All @@ -199,8 +201,7 @@ def confirm_password(request):
if not check_password(password, user.password):
return HttpResponse(status=401)
status.delete()
db_key = UserKey.get_or_create_key_for_user(user)
return JsonResponse({"name": user.name, "username": user.username, "secondary_phone_validate_by": user.recovery_phone_validation_deadline, "db_key": db_key.key})
return JsonResponse(user_data(user))


@api_view(['POST'])
Expand Down Expand Up @@ -305,6 +306,26 @@ def set_recovery_pin(request):
return HttpResponse()


def user_data(user):
db_key = UserKey.get_or_create_key_for_user(user)
user_data = {"name": user.name, "username": user.username, "secondary_phone_validate_by": user.recovery_phone_validation_deadline, "db_key": db_key.key}
user_data.update(user_payment_profile(user))
return user_data


def user_payment_profile(user):
try:
profile = user.payment_profile
return {"payment_profile": {
"phone_number": profile.phone_number,
"telecom_provider": profile.telecom_provider,
"is_verified": profile.is_verified,
"is_validated": profile.is_validated,
}}
except ObjectDoesNotExist:
return {}


@api_view(['POST'])
@permission_classes([])
def confirm_recovery_pin(request):
Expand All @@ -322,8 +343,7 @@ def confirm_recovery_pin(request):
return JsonResponse({"error": "Recovery PIN is incorrect"}, status=401)
status.step = RecoveryStatus.RecoverySteps.RESET_PASSWORD
status.save()
db_key = UserKey.get_or_create_key_for_user(user)
return JsonResponse({"name": user.name, "username": user.username, "secondary_phone_validate_by": user.recovery_phone_validation_deadline, "db_key": db_key.key})
return JsonResponse(user_data(user))


@api_view(['GET'])
Expand Down

0 comments on commit 5923600

Please sign in to comment.