mirror of
https://github.com/i701/sarlink-portal-api.git
synced 2025-07-07 18:26:30 +00:00
refactor(payment): enhance payment verification with detailed response structure and transaction data 🔨
This commit is contained in:
@ -28,6 +28,20 @@ PAYMENT_BASE_URL = env("PAYMENT_BASE_URL", default="") # type: ignore
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Transaction:
|
||||||
|
ref: str
|
||||||
|
sourceBank: str
|
||||||
|
trxDate: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PaymentVerificationResponse:
|
||||||
|
message: str
|
||||||
|
success: bool
|
||||||
|
transaction: Optional[Transaction] = None
|
||||||
|
|
||||||
|
|
||||||
class InsufficientFundsError(Exception):
|
class InsufficientFundsError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -110,7 +124,7 @@ class UpdatePaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
self.perform_update(serializer)
|
self.perform_update(serializer)
|
||||||
devices.update(
|
devices.update(
|
||||||
is_active=True, expiry_date=device_expire_date, has_a_pending_payment=False
|
is_active=False, expiry_date=device_expire_date, has_a_pending_payment=False
|
||||||
)
|
)
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
@ -145,7 +159,6 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
devices = payment.devices.all()
|
devices = payment.devices.all()
|
||||||
payment_status = False
|
|
||||||
if method == "WALLET":
|
if method == "WALLET":
|
||||||
if user.wallet_balance < payment.amount: # type: ignore
|
if user.wallet_balance < payment.amount: # type: ignore
|
||||||
return Response(
|
return Response(
|
||||||
@ -153,7 +166,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
payment_status = self.process_wallet_payment(
|
self.process_wallet_payment(
|
||||||
user,
|
user,
|
||||||
payment,
|
payment,
|
||||||
)
|
)
|
||||||
@ -161,15 +174,12 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
data = {
|
data = {
|
||||||
"benefName": f"{user.first_name} {user.last_name}", # type: ignore
|
"benefName": f"{user.first_name} {user.last_name}", # type: ignore
|
||||||
"accountNo": user.acc_no, # type: ignore
|
"accountNo": user.acc_no, # type: ignore
|
||||||
"absAmount": payment.amount,
|
"absAmount": "{:.2f}".format(payment.amount),
|
||||||
"time": localtime(timezone.now() + timedelta(minutes=5)).strftime(
|
"time": localtime(payment.created_at).strftime("%Y-%m-%d %H:%M"),
|
||||||
"%Y-%m-%d %H:%M"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
payment_status = self.verify_transfer_payment(data, payment)
|
payment_verification_response = self.verify_transfer_payment(data, payment)
|
||||||
|
|
||||||
if payment_status:
|
if payment_verification_response.success:
|
||||||
# Update devices
|
|
||||||
expiry_date = timezone.now() + timedelta(days=30 * payment.number_of_months)
|
expiry_date = timezone.now() + timedelta(days=30 * payment.number_of_months)
|
||||||
devices.update(
|
devices.update(
|
||||||
is_active=True,
|
is_active=True,
|
||||||
@ -177,7 +187,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
has_a_pending_payment=False,
|
has_a_pending_payment=False,
|
||||||
registered=True,
|
registered=True,
|
||||||
)
|
)
|
||||||
# Need to add to omada if its a new device and not an existing device
|
# add to omada if its a new device and not an existing device
|
||||||
device_list = []
|
device_list = []
|
||||||
for device in devices:
|
for device in devices:
|
||||||
device_list.append(
|
device_list.append(
|
||||||
@ -193,12 +203,22 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
device.save()
|
device.save()
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{"message": f"Payment verified successfully using [{method}]."},
|
{
|
||||||
|
"status": payment_verification_response.success,
|
||||||
|
"message": payment_verification_response.message,
|
||||||
|
"transaction": asdict(payment_verification_response.transaction)
|
||||||
|
if payment_verification_response.transaction
|
||||||
|
else None,
|
||||||
|
},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return Response(
|
return Response(
|
||||||
{"message": f"Payment verification FAILED using [{method}]."},
|
{
|
||||||
|
"status": payment_verification_response.success,
|
||||||
|
"message": payment_verification_response.message
|
||||||
|
or "Topup payment verification failed.",
|
||||||
|
},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -215,7 +235,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
user.save()
|
user.save()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def verify_transfer_payment(self, data, payment):
|
def verify_transfer_payment(self, data, payment) -> PaymentVerificationResponse:
|
||||||
if not PAYMENT_BASE_URL:
|
if not PAYMENT_BASE_URL:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"PAYMENT_BASE_URL is not set. Please set it in your environment variables."
|
"PAYMENT_BASE_URL is not set. Please set it in your environment variables."
|
||||||
@ -230,41 +250,62 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
logger.error(f"HTTPError: {e}")
|
logger.error(f"HTTPError: {e}")
|
||||||
return False
|
return PaymentVerificationResponse(
|
||||||
|
message="Payment verification failed.", success=False, transaction=None
|
||||||
|
)
|
||||||
mib_resp = response.json()
|
mib_resp = response.json()
|
||||||
logger.info("MIB Verification Response ->", mib_resp)
|
logger.info("MIB Verification Response ->", mib_resp)
|
||||||
if not response.json().get("success"):
|
if not response.json().get("success"):
|
||||||
return mib_resp["success"]
|
return PaymentVerificationResponse(
|
||||||
|
message=mib_resp["message"],
|
||||||
|
success=mib_resp["success"],
|
||||||
|
transaction=None,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
payment.paid = True
|
payment.paid = True
|
||||||
payment.paid_at = timezone.now()
|
payment.paid_at = timezone.now()
|
||||||
payment.method = "TRANSFER"
|
payment.method = "TRANSFER"
|
||||||
payment.mib_reference = mib_resp["transaction"]["ref"] or ""
|
payment.mib_reference = mib_resp["transaction"]["ref"] or ""
|
||||||
payment.save()
|
payment.save()
|
||||||
return True
|
return PaymentVerificationResponse(
|
||||||
|
message=mib_resp["message"],
|
||||||
|
success=mib_resp["success"],
|
||||||
|
transaction=Transaction(
|
||||||
|
ref=payment.mib_reference,
|
||||||
|
sourceBank=mib_resp["transaction"]["sourceBank"],
|
||||||
|
trxDate=mib_resp["transaction"]["trxDate"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeletePaymentView(StaffEditorPermissionMixin, generics.DestroyAPIView):
|
class CancelPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||||
queryset = Payment.objects.all()
|
queryset = Payment.objects.all()
|
||||||
serializer_class = PaymentSerializer
|
serializer_class = PaymentSerializer
|
||||||
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": "Payment has already been cancelled."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
if instance.user != user and not user.is_superuser:
|
if instance.user != user and not user.is_superuser:
|
||||||
return Response(
|
return Response(
|
||||||
{"message": "You are not authorized to delete this payment."},
|
{"message": "You are not authorized to cancel this payment."},
|
||||||
status=status.HTTP_403_FORBIDDEN,
|
status=status.HTTP_403_FORBIDDEN,
|
||||||
)
|
)
|
||||||
if instance.paid:
|
if instance.paid:
|
||||||
return Response(
|
return Response(
|
||||||
{"message": "Paid payments cannot be deleted."},
|
{"message": "Paid payments cannot be cancelled."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
devices = instance.devices.all()
|
devices = instance.devices.all()
|
||||||
|
instance.status = "CANCELLED"
|
||||||
|
instance.save()
|
||||||
devices.update(is_active=False, expiry_date=None, has_a_pending_payment=False)
|
devices.update(is_active=False, expiry_date=None, has_a_pending_payment=False)
|
||||||
return super().delete(request, *args, **kwargs)
|
return super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ListCreateTopupView(StaffEditorPermissionMixin, generics.ListCreateAPIView):
|
class ListCreateTopupView(StaffEditorPermissionMixin, generics.ListCreateAPIView):
|
||||||
@ -308,20 +349,6 @@ class TopupDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
|
|||||||
return queryset.filter(user=self.request.user)
|
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):
|
class VerifyTopupPaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||||
queryset = Topup.objects.all()
|
queryset = Topup.objects.all()
|
||||||
serializer_class = TopupSerializer
|
serializer_class = TopupSerializer
|
||||||
|
Reference in New Issue
Block a user