Merge pull request #9 from i701/feat/topups
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 5m37s

feat/topups
This commit is contained in:
Abdulla Aidhaan
2025-07-05 17:42:57 +05:00
committed by GitHub
7 changed files with 56 additions and 11 deletions

View File

@ -25,6 +25,7 @@ class TopupAdmin(admin.ModelAdmin):
"amount",
"paid",
"paid_at",
"status",
"created_at",
"is_expired",
"expires_at",

View File

@ -51,6 +51,7 @@ class Command(BaseCommand):
min_value=100.00,
max_value=5000.00,
),
status=random.choice(["PENDING", "PAID", "CANCELLED"]),
user=random_user,
updated_at=timezone.now(),
expires_at=expires_at_date,

View File

@ -0,0 +1,25 @@
# Generated by Django 5.2 on 2025-07-05 12:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("billing", "0010_add_expiry_notification_sent_to_topup"),
]
operations = [
migrations.AddField(
model_name="topup",
name="status",
field=models.CharField(
choices=[
("PENDING", "Pending"),
("PAID", "Paid"),
("CANCELLED", "Cancelled"),
],
default="PENDING",
max_length=20,
),
),
]

View File

@ -52,6 +52,15 @@ class Topup(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="topups")
paid = models.BooleanField(default=False)
paid_at = models.DateTimeField(null=True, blank=True)
status = models.CharField(
max_length=20,
choices=[
("PENDING", "Pending"),
("PAID", "Paid"),
("CANCELLED", "Cancelled"),
],
default="PENDING",
)
mib_reference = models.CharField(default="", null=True, blank=True)
expires_at = models.DateTimeField(null=True, blank=True)
expiry_notification_sent = models.BooleanField(default=False)

View File

@ -44,6 +44,7 @@ class TopupSerializer(serializers.ModelSerializer):
"amount",
"user",
"paid",
"status",
"mib_reference",
"is_expired",
"expires_at",

View File

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

View File

@ -355,9 +355,8 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
)
else:
topup.paid = True
topup.paid_at = timezone.now()
topup.mib_reference = mib_resp["transaction"]["ref"] or ""
topup.paid_at = timezone.now()
topup.paid_at = mib_resp["transaction"]["trxDate"]
topup.save()
return PaymentVerificationResponse(
message=mib_resp["message"],
@ -397,6 +396,8 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
if topup_verification_response.success:
user.wallet_balance += topup_instance.amount # type: ignore
user.save()
topup_instance.status = "PAID"
topup_instance.save()
return Response(
{
"status": topup_verification_response.success,
@ -418,17 +419,22 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
)
class DeleteTopupView(StaffEditorPermissionMixin, generics.DestroyAPIView):
queryset = Topup.objects.all()
class CancelTopupView(StaffEditorPermissionMixin, generics.UpdateAPIView):
queryset = Topup.objects.all().select_related("user")
serializer_class = TopupSerializer
lookup_field = "pk"
def delete(self, request, *args, **kwargs):
def update(self, request, *args, **kwargs):
instance = self.get_object()
user = request.user
if instance.status == "CANCELLED":
return Response(
{"message": "Topup has already been cancelled."},
status=status.HTTP_400_BAD_REQUEST,
)
if instance.is_expired:
return Response(
{"message": "Expired topups cannot be deleted."},
{"message": "Expired topups cannot be cancelled."},
status=status.HTTP_400_BAD_REQUEST,
)
if (
@ -445,4 +451,6 @@ class DeleteTopupView(StaffEditorPermissionMixin, generics.DestroyAPIView):
{"message": "Paid topups cannot be deleted."},
status=status.HTTP_400_BAD_REQUEST,
)
return super().delete(request, *args, **kwargs)
instance.status = "CANCELLED"
instance.save()
return super().update(request, *args, **kwargs)