i701 83db42cc60
Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 4m12s
Refactor and enhance device management and authentication features
- Updated the `reverse_dhivehi_string` function to correct the range for combining characters.
- Added new device handling in the health check view and integrated the `add_new_devices_to_omada` task.
- Improved date handling in `CreateTemporaryUserView` to ensure proper string conversion.
- Enhanced OTP sending by converting mobile numbers to strings.
- Implemented MAC address validation in the `Device` model using a custom validator.
- Removed unnecessary fields from the `CreateDeviceSerializer`.
- Normalized MAC address format in the `DeviceListCreateAPIView`.
- Updated the `djangopasswordlessknox` package to improve code consistency and readability.
- Added migration to enforce MAC address validation in the database.
2025-04-25 14:37:27 +05:00

247 lines
8.5 KiB
Python

# Create your views here.
# billing/views.py
import os
from datetime import timedelta
import requests
from django.utils import timezone
from django.utils.timezone import localtime
from rest_framework import generics, status
from rest_framework.response import Response
from api.mixins import StaffEditorPermissionMixin
from api.tasks import add_new_devices_to_omada
from apibase.env import BASE_DIR, env
from .models import Device, Payment
from .serializers import PaymentSerializer, UpdatePaymentSerializer
env.read_env(os.path.join(BASE_DIR, ".env"))
PAYMENT_BASE_URL = env("PAYMENT_BASE_URL", default=None)
if not PAYMENT_BASE_URL:
raise ValueError(
"PAYMENT_BASE_URL is not set. Please set it in your environment variables."
)
class InsufficientFundsError(Exception):
pass
class ListCreatePaymentView(StaffEditorPermissionMixin, generics.ListCreateAPIView):
serializer_class = PaymentSerializer
queryset = Payment.objects.all().select_related("user")
def get_queryset(self):
queryset = super().get_queryset()
if self.request.user.is_superuser:
return queryset
return queryset.filter(user=self.request.user)
def create(self, request):
data = request.data
user = request.user
amount = data.get("amount")
number_of_months = data.get("number_of_months")
device_ids = data.get("device_ids", [])
print(amount, number_of_months, device_ids)
for device_id in device_ids:
device = Device.objects.filter(id=device_id, user=user).first()
print("DEVICE", device)
if not device:
return Response(
{"message": f"Device with id {device_id} not found."},
status=status.HTTP_400_BAD_REQUEST,
)
if not amount or not number_of_months:
return Response(
{"message": "amount and number_of_months are required."},
status=status.HTTP_400_BAD_REQUEST,
)
if not device_ids:
return Response(
{"message": "device_ids are required."},
status=status.HTTP_400_BAD_REQUEST,
)
# Create payment
payment = Payment.objects.create(
amount=amount,
number_of_months=number_of_months,
paid=data.get("paid", False),
user=user,
)
# Connect devices to payment
devices = Device.objects.filter(id__in=device_ids, user=user)
for device in devices:
device.has_a_pending_payment = True
device.save()
payment.devices.set(devices)
serializer = PaymentSerializer(payment)
return Response(serializer.data, status=status.HTTP_201_CREATED)
class PaymentDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
queryset = Payment.objects.select_related("user").all()
serializer_class = PaymentSerializer
lookup_field = "pk"
class UpdatePaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIView):
queryset = Payment.objects.select_related("user").all()
serializer_class = UpdatePaymentSerializer
lookup_field = "pk"
def update(self, request, *args, **kwargs):
instance = self.get_object()
number_of_months = instance.number_of_months
device_expire_date = timezone.now() + timedelta(days=30 * number_of_months)
devices = instance.devices.all()
serializer = self.get_serializer(instance, data=request.data, partial=False)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
devices.update(
is_active=True, expiry_date=device_expire_date, has_a_pending_payment=False
)
return Response(serializer.data)
class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
serializer_class = PaymentSerializer
queryset = Payment.objects.all()
lookup_field = "pk"
def update(self, request, *args, **kwargs):
# TODO: Fix check for success payment
payment = self.get_object()
data = request.data
user = request.user
print("logged in user", user)
print("Payment user", payment.user)
if payment.paid:
return Response(
{"message": "Payment has already been verified."},
status=status.HTTP_400_BAD_REQUEST,
)
if payment.user != user and not user.is_superuser:
return Response(
{"message": "You are not authorized to verify this payment."},
status=status.HTTP_403_FORBIDDEN,
)
method = data.get("method")
if not method:
return Response(
{"message": "method is required. 'WALLET' or 'TRANSFER'"},
status=status.HTTP_400_BAD_REQUEST,
)
devices = payment.devices.all()
payment_status = False
if method == "WALLET":
if user.wallet_balance < payment.amount:
return Response(
{"message": "Insufficient funds in wallet."},
status=status.HTTP_400_BAD_REQUEST,
)
else:
payment_status = self.process_wallet_payment(
user,
payment,
)
if method == "TRANSFER":
data = {
"benefName": f"{user.first_name} {user.last_name}",
"accountNo": user.acc_no,
"absAmount": payment.amount,
"time": localtime(timezone.now() + timedelta(minutes=5)).strftime(
"%Y-%m-%d %H:%M"
),
}
payment_status = self.verify_transfer_payment(data, payment)
if payment_status:
# Update devices
expiry_date = timezone.now() + timedelta(days=30 * payment.number_of_months)
devices.update(
is_active=True,
expiry_date=expiry_date,
has_a_pending_payment=False,
registered=True,
)
# Need to add to omada if its a new device and not an existing device
device_list = []
for device in devices:
device_list.append(
{
"mac": device.mac,
"name": device.name,
}
)
if not device.registered:
# Add to omada
add_new_devices_to_omada.delay(new_devices=device_list)
device.registered = True
device.save()
return Response(
{"message": f"Payment verified successfully using [{method}]."}
)
else:
return Response(
{"message": f"Payment verification FAILED using [{method}]."}
)
def process_wallet_payment(self, user, payment):
print("processing wallet payment...")
print(user, payment.amount)
payment.paid = True
payment.paid_at = timezone.now()
payment.method = "WALLET"
payment.save()
user.wallet_balance -= payment.amount
user.save()
return True
def verify_transfer_payment(self, data, payment):
print(data)
response = requests.post(
f"{PAYMENT_BASE_URL}/verify-payment",
json=data,
headers={"Content-Type": "application/json"},
)
response.raise_for_status()
mib_resp = response.json()
if not response.json().get("success"):
return mib_resp["success"]
else:
return True
class DeletePaymentView(StaffEditorPermissionMixin, generics.DestroyAPIView):
queryset = Payment.objects.all()
serializer_class = PaymentSerializer
lookup_field = "pk"
def delete(self, request, *args, **kwargs):
instance = self.get_object()
user = request.user
if instance.user != user and not user.is_superuser:
return Response(
{"message": "You are not authorized to delete this payment."},
status=status.HTTP_403_FORBIDDEN,
)
if instance.paid:
return Response(
{"message": "Paid payments cannot be deleted."},
status=status.HTTP_400_BAD_REQUEST,
)
devices = instance.devices.all()
devices.update(is_active=False, expiry_date=None, has_a_pending_payment=False)
return super().delete(request, *args, **kwargs)