Merge pull request #6 from i701/feat/topups
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 4m19s

feature/topups
This commit is contained in:
Abdulla Aidhaan
2025-07-04 20:16:22 +05:00
committed by GitHub
5 changed files with 69 additions and 14 deletions

View File

@ -18,9 +18,7 @@ def assign_device_permissions(sender, instance, created, **kwargs):
atoll_read_permission = Permission.objects.get(codename="view_atoll")
island_read_permission = Permission.objects.get(codename="view_island")
payment_permissions = Permission.objects.filter(content_type__model="payment")
topup_permissions = Permission.objects.filter(
content_type__model="topup"
).exclude(codename__startswith="delete_")
topup_permissions = Permission.objects.filter(content_type__model="topup")
for permission in topup_permissions:
instance.user_permissions.add(permission)

View File

@ -46,6 +46,7 @@ class TopupSerializer(serializers.ModelSerializer):
"paid",
"mib_reference",
"is_expired",
"expires_at",
"created_at",
"updated_at",
]

View File

@ -198,3 +198,10 @@ class TopupTests(TestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["amount"], 50.00)
self.assertEqual(response.json()["user"]["id"], getattr(self.real_user, "id"))
def test_delete_topup(self):
topup = Topup.objects.create(amount=50.00, user=self.real_user)
url = reverse("delete-topup", kwargs={"pk": topup.pk})
response = self.client.delete(url, format="json")
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(Topup.objects.count(), 0)

View File

@ -9,6 +9,7 @@ from .views import (
ListCreateTopupView,
VerifyTopupPaymentAPIView,
TopupDetailAPIView,
DeleteTopupView,
)
urlpatterns = [
@ -35,4 +36,9 @@ urlpatterns = [
VerifyTopupPaymentAPIView.as_view(),
name="verify-topup-payment",
),
path(
"topup/<str:pk>/delete/",
DeleteTopupView.as_view(),
name="delete-topup",
),
]

View File

@ -18,6 +18,8 @@ import logging
from .models import Device, Payment, Topup
from .serializers import PaymentSerializer, UpdatePaymentSerializer, TopupSerializer
from .filters import PaymentFilter, TopupFilter
from dataclasses import dataclass, asdict
from typing import Optional
env.read_env(os.path.join(BASE_DIR, ".env"))
@ -271,13 +273,15 @@ class ListCreateTopupView(StaffEditorPermissionMixin, generics.ListCreateAPIView
def create(self, request, *args, **kwargs):
data = request.data
user = request.user
current_time = timezone.now()
expires_at = current_time + timedelta(minutes=10) # Topup expires in 10 minutes
amount = data.get("amount")
if not amount:
return Response(
{"message": "amount is required."},
status=status.HTTP_400_BAD_REQUEST,
)
topup = Topup.objects.create(amount=amount, user=user)
topup = Topup.objects.create(amount=amount, user=user, expires_at=expires_at)
serializer = TopupSerializer(topup)
return Response(serializer.data, status=status.HTTP_201_CREATED)
@ -300,12 +304,26 @@ class TopupDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
return queryset.filter(user=self.request.user)
@dataclass
class Transaction:
ref: str
sourceBank: str
trxDate: str
@dataclass
class PaymentVerificationResponse:
message: str
success: bool
transaction: Optional[Transaction] = None
class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIView):
queryset = Topup.objects.all()
serializer_class = TopupSerializer
lookup_field = "pk"
def verify_transfer_topup(self, data, topup):
def verify_transfer_topup(self, data, topup) -> PaymentVerificationResponse:
if not PAYMENT_BASE_URL:
raise ValueError(
"PAYMENT_BASE_URL is not set. Please set it in your environment variables."
@ -320,18 +338,32 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
response.raise_for_status()
except requests.exceptions.HTTPError as e:
logger.error(f"HTTPError: {e}")
return False # Or handle the error as appropriate
return PaymentVerificationResponse(
message="Payment verification failed.", success=False, transaction=None
)
mib_resp = response.json()
print(mib_resp)
if not response.json().get("success"):
return mib_resp["success"]
return PaymentVerificationResponse(
message=mib_resp["message"],
success=mib_resp["success"],
transaction=None,
)
else:
topup.paid = True
# topup.paid_at = timezone.now() # Assuming Topup model has paid_at field
topup.paid_at = timezone.now()
topup.mib_reference = mib_resp["transaction"]["ref"] or ""
topup.paid_at = timezone.now()
topup.save()
return True
return PaymentVerificationResponse(
message=mib_resp["message"],
success=mib_resp["success"],
transaction=Transaction(
ref=topup.mib_reference,
sourceBank=mib_resp["transaction"]["sourceBank"],
trxDate=mib_resp["transaction"]["trxDate"],
),
)
def update(self, request, *args, **kwargs):
topup_instance = self.get_object()
@ -355,16 +387,27 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
topup_instance.created_at + timedelta(minutes=5)
).strftime("%Y-%m-%d %H:%M"),
}
print("payment payload in view ->", data)
topup_status = self.verify_transfer_topup(data, topup_instance)
if topup_status:
print("DATA", data)
topup_verification_response = self.verify_transfer_topup(data, topup_instance)
print("TOPUP VERIFICATION RESPONSE", topup_verification_response)
if topup_verification_response.success:
return Response(
{"message": "Topup payment verified successfully."},
{
"status": topup_verification_response.success,
"message": topup_verification_response.message,
"transaction": asdict(topup_verification_response.transaction)
if topup_verification_response.transaction
else None,
},
status=status.HTTP_200_OK,
)
else:
return Response(
{"message": "Topup payment verification failed."},
{
"status": topup_verification_response.success,
"message": topup_verification_response.message
or "Topup payment verification failed.",
},
status=status.HTTP_400_BAD_REQUEST,
)