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__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Transaction:
|
||||
ref: str
|
||||
sourceBank: str
|
||||
trxDate: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class PaymentVerificationResponse:
|
||||
message: str
|
||||
success: bool
|
||||
transaction: Optional[Transaction] = None
|
||||
|
||||
|
||||
class InsufficientFundsError(Exception):
|
||||
pass
|
||||
|
||||
@ -110,7 +124,7 @@ class UpdatePaymentAPIView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||
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
|
||||
is_active=False, expiry_date=device_expire_date, has_a_pending_payment=False
|
||||
)
|
||||
return Response(serializer.data)
|
||||
|
||||
@ -145,7 +159,6 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||
)
|
||||
|
||||
devices = payment.devices.all()
|
||||
payment_status = False
|
||||
if method == "WALLET":
|
||||
if user.wallet_balance < payment.amount: # type: ignore
|
||||
return Response(
|
||||
@ -153,7 +166,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
else:
|
||||
payment_status = self.process_wallet_payment(
|
||||
self.process_wallet_payment(
|
||||
user,
|
||||
payment,
|
||||
)
|
||||
@ -161,15 +174,12 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||
data = {
|
||||
"benefName": f"{user.first_name} {user.last_name}", # type: ignore
|
||||
"accountNo": user.acc_no, # type: ignore
|
||||
"absAmount": payment.amount,
|
||||
"time": localtime(timezone.now() + timedelta(minutes=5)).strftime(
|
||||
"%Y-%m-%d %H:%M"
|
||||
),
|
||||
"absAmount": "{:.2f}".format(payment.amount),
|
||||
"time": localtime(payment.created_at).strftime("%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:
|
||||
# Update devices
|
||||
if payment_verification_response.success:
|
||||
expiry_date = timezone.now() + timedelta(days=30 * payment.number_of_months)
|
||||
devices.update(
|
||||
is_active=True,
|
||||
@ -177,7 +187,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||
has_a_pending_payment=False,
|
||||
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 = []
|
||||
for device in devices:
|
||||
device_list.append(
|
||||
@ -193,12 +203,22 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||
device.save()
|
||||
|
||||
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,
|
||||
)
|
||||
else:
|
||||
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,
|
||||
)
|
||||
|
||||
@ -215,7 +235,7 @@ class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||
user.save()
|
||||
return True
|
||||
|
||||
def verify_transfer_payment(self, data, payment):
|
||||
def verify_transfer_payment(self, data, payment) -> PaymentVerificationResponse:
|
||||
if not PAYMENT_BASE_URL:
|
||||
raise ValueError(
|
||||
"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()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.error(f"HTTPError: {e}")
|
||||
return False
|
||||
return PaymentVerificationResponse(
|
||||
message="Payment verification failed.", success=False, transaction=None
|
||||
)
|
||||
mib_resp = response.json()
|
||||
logger.info("MIB Verification Response ->", 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:
|
||||
payment.paid = True
|
||||
payment.paid_at = timezone.now()
|
||||
payment.method = "TRANSFER"
|
||||
payment.mib_reference = mib_resp["transaction"]["ref"] or ""
|
||||
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()
|
||||
serializer_class = PaymentSerializer
|
||||
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": "Payment has already been cancelled."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if instance.user != user and not user.is_superuser:
|
||||
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,
|
||||
)
|
||||
if instance.paid:
|
||||
return Response(
|
||||
{"message": "Paid payments cannot be deleted."},
|
||||
{"message": "Paid payments cannot be cancelled."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
devices = instance.devices.all()
|
||||
instance.status = "CANCELLED"
|
||||
instance.save()
|
||||
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):
|
||||
@ -308,20 +349,6 @@ 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
|
||||
|
Reference in New Issue
Block a user