mirror of
https://github.com/i701/sarlink-portal-api.git
synced 2025-07-07 18:26:30 +00:00
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
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:
@ -25,6 +25,7 @@ class TopupAdmin(admin.ModelAdmin):
|
|||||||
"amount",
|
"amount",
|
||||||
"paid",
|
"paid",
|
||||||
"paid_at",
|
"paid_at",
|
||||||
|
"status",
|
||||||
"created_at",
|
"created_at",
|
||||||
"is_expired",
|
"is_expired",
|
||||||
"expires_at",
|
"expires_at",
|
||||||
|
@ -51,6 +51,7 @@ class Command(BaseCommand):
|
|||||||
min_value=100.00,
|
min_value=100.00,
|
||||||
max_value=5000.00,
|
max_value=5000.00,
|
||||||
),
|
),
|
||||||
|
status=random.choice(["PENDING", "PAID", "CANCELLED"]),
|
||||||
user=random_user,
|
user=random_user,
|
||||||
updated_at=timezone.now(),
|
updated_at=timezone.now(),
|
||||||
expires_at=expires_at_date,
|
expires_at=expires_at_date,
|
||||||
|
25
billing/migrations/0011_topup_status.py
Normal file
25
billing/migrations/0011_topup_status.py
Normal 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -52,6 +52,15 @@ class Topup(models.Model):
|
|||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="topups")
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="topups")
|
||||||
paid = models.BooleanField(default=False)
|
paid = models.BooleanField(default=False)
|
||||||
paid_at = models.DateTimeField(null=True, blank=True)
|
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)
|
mib_reference = models.CharField(default="", null=True, blank=True)
|
||||||
expires_at = models.DateTimeField(null=True, blank=True)
|
expires_at = models.DateTimeField(null=True, blank=True)
|
||||||
expiry_notification_sent = models.BooleanField(default=False)
|
expiry_notification_sent = models.BooleanField(default=False)
|
||||||
|
@ -44,6 +44,7 @@ class TopupSerializer(serializers.ModelSerializer):
|
|||||||
"amount",
|
"amount",
|
||||||
"user",
|
"user",
|
||||||
"paid",
|
"paid",
|
||||||
|
"status",
|
||||||
"mib_reference",
|
"mib_reference",
|
||||||
"is_expired",
|
"is_expired",
|
||||||
"expires_at",
|
"expires_at",
|
||||||
|
@ -9,7 +9,7 @@ from .views import (
|
|||||||
ListCreateTopupView,
|
ListCreateTopupView,
|
||||||
VerifyTopupPaymentAPIView,
|
VerifyTopupPaymentAPIView,
|
||||||
TopupDetailAPIView,
|
TopupDetailAPIView,
|
||||||
DeleteTopupView,
|
CancelTopupView,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -37,8 +37,8 @@ urlpatterns = [
|
|||||||
name="verify-topup-payment",
|
name="verify-topup-payment",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"topup/<str:pk>/delete/",
|
"topup/<str:pk>/cancel/",
|
||||||
DeleteTopupView.as_view(),
|
CancelTopupView.as_view(),
|
||||||
name="delete-topup",
|
name="cancel-topup",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -355,9 +355,8 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
topup.paid = True
|
topup.paid = True
|
||||||
topup.paid_at = timezone.now()
|
|
||||||
topup.mib_reference = mib_resp["transaction"]["ref"] or ""
|
topup.mib_reference = mib_resp["transaction"]["ref"] or ""
|
||||||
topup.paid_at = timezone.now()
|
topup.paid_at = mib_resp["transaction"]["trxDate"]
|
||||||
topup.save()
|
topup.save()
|
||||||
return PaymentVerificationResponse(
|
return PaymentVerificationResponse(
|
||||||
message=mib_resp["message"],
|
message=mib_resp["message"],
|
||||||
@ -397,6 +396,8 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
|
|||||||
if topup_verification_response.success:
|
if topup_verification_response.success:
|
||||||
user.wallet_balance += topup_instance.amount # type: ignore
|
user.wallet_balance += topup_instance.amount # type: ignore
|
||||||
user.save()
|
user.save()
|
||||||
|
topup_instance.status = "PAID"
|
||||||
|
topup_instance.save()
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"status": topup_verification_response.success,
|
"status": topup_verification_response.success,
|
||||||
@ -418,17 +419,22 @@ class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIVi
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeleteTopupView(StaffEditorPermissionMixin, generics.DestroyAPIView):
|
class CancelTopupView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||||
queryset = Topup.objects.all()
|
queryset = Topup.objects.all().select_related("user")
|
||||||
serializer_class = TopupSerializer
|
serializer_class = TopupSerializer
|
||||||
lookup_field = "pk"
|
lookup_field = "pk"
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
user = request.user
|
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:
|
if instance.is_expired:
|
||||||
return Response(
|
return Response(
|
||||||
{"message": "Expired topups cannot be deleted."},
|
{"message": "Expired topups cannot be cancelled."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
@ -445,4 +451,6 @@ class DeleteTopupView(StaffEditorPermissionMixin, generics.DestroyAPIView):
|
|||||||
{"message": "Paid topups cannot be deleted."},
|
{"message": "Paid topups cannot be deleted."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
return super().delete(request, *args, **kwargs)
|
instance.status = "CANCELLED"
|
||||||
|
instance.save()
|
||||||
|
return super().update(request, *args, **kwargs)
|
||||||
|
Reference in New Issue
Block a user