Skip to content

Commit

Permalink
1199072_Add_Payment_Status_and_move_mapping_from_PG_to_HOPE_fix (#4036)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarekBiczysko authored Jul 15, 2024
1 parent 38e7938 commit 775271d
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 29 deletions.
2 changes: 1 addition & 1 deletion backend/hct_mis_api/apps/core/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_get_count_and_percentage(self) -> None:

def test_get_payment_delivered_quantity_status_and_value(self) -> None:
with self.assertRaisesMessage(Exception, "Invalid delivered quantity"):
get_payment_delivered_quantity_status_and_value(None, Decimal(10.00)) # type: ignore
get_payment_delivered_quantity_status_and_value(None, Decimal(10.00))
with self.assertRaisesMessage(Exception, "Invalid delivered quantity"):
get_payment_delivered_quantity_status_and_value("", Decimal(10.00))
self.assertEqual(
Expand Down
6 changes: 5 additions & 1 deletion backend/hct_mis_api/apps/payment/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,11 @@ def is_reconciled(self) -> bool:

return (
self.eligible_payments.exclude(
status__in=[GenericPayment.STATUS_PENDING, GenericPayment.STATUS_SENT_TO_PG]
status__in=[
GenericPayment.STATUS_PENDING,
GenericPayment.STATUS_SENT_TO_PG,
GenericPayment.STATUS_SENT_TO_FSP,
]
).count()
== self.eligible_payments.count()
)
Expand Down
25 changes: 16 additions & 9 deletions backend/hct_mis_api/apps/payment/services/payment_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,9 @@ class PaymentRecordData(FlexibleArgumentsDataclassMixin):
record_code: str
parent: str
status: str
hope_status: str
auth_code: str
payout_amount: float
fsp_code: str
payout_amount: Optional[float] = None
message: Optional[str] = None

def get_hope_status(self, entitlement_quantity: Decimal) -> str:
Expand All @@ -163,9 +162,8 @@ def get_transferred_status_based_on_delivery_amount() -> str:
self.payout_amount, entitlement_quantity
)
except Exception:
raise PaymentGatewayAPI.PaymentGatewayAPIException(
f"Invalid delivered_quantity {self.payout_amount} for Payment {self.remote_id}"
)
logger.error(f"Invalid delivered_quantity {self.payout_amount} for Payment {self.remote_id}")
_hope_status = Payment.STATUS_ERROR
return _hope_status

mapping = {
Expand All @@ -180,7 +178,8 @@ def get_transferred_status_based_on_delivery_amount() -> str:

hope_status = mapping.get(self.status)
if not hope_status:
raise PaymentGatewayAPI.PaymentGatewayAPIException(f"Invalid Payment status: {self.status}")
logger.error(f"Invalid Payment status: {self.status}")
hope_status = Payment.STATUS_ERROR

return hope_status() if callable(hope_status) else hope_status

Expand Down Expand Up @@ -435,8 +434,16 @@ def update_payment(
_payment.fsp_auth_code = matching_pg_payment.auth_code
update_fields = ["status", "status_date", "fsp_auth_code"]

if _payment.status not in Payment.ALLOW_CREATE_VERIFICATION and matching_pg_payment.message:
_payment.reason_for_unsuccessful_payment = matching_pg_payment.message
if _payment.status in [
Payment.STATUS_ERROR,
Payment.STATUS_MANUALLY_CANCELLED,
]:
if matching_pg_payment.message:
_payment.reason_for_unsuccessful_payment = matching_pg_payment.message
elif matching_pg_payment.payout_amount:
_payment.reason_for_unsuccessful_payment = f"Delivered amount: {matching_pg_payment.payout_amount}"
else:
_payment.reason_for_unsuccessful_payment = "Unknown error"
update_fields.append("reason_for_unsuccessful_payment")

delivered_quantity = matching_pg_payment.payout_amount
Expand All @@ -449,7 +456,7 @@ def update_payment(
try:
_payment.delivered_quantity = to_decimal(delivered_quantity)
_payment.delivered_quantity_usd = get_quantity_in_usd(
amount=Decimal(delivered_quantity),
amount=Decimal(delivered_quantity), # type: ignore
currency=_payment_plan.currency,
exchange_rate=Decimal(_exchange_rate),
currency_exchange_date=_payment_plan.currency_exchange_date,
Expand Down
148 changes: 131 additions & 17 deletions backend/hct_mis_api/apps/payment/tests/test_payment_gateway_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
)
from hct_mis_api.apps.payment.services.payment_gateway import (
AddRecordsResponseData,
PaymentGatewayAPI,
PaymentGatewayService,
PaymentInstructionStatus,
PaymentRecordData,
Expand Down Expand Up @@ -131,7 +130,6 @@ def test_sync_records_for_split(
record_code="1",
parent="1",
status="TRANSFERRED_TO_BENEFICIARY",
hope_status=Payment.STATUS_DISTRIBUTION_SUCCESS,
auth_code="1",
payout_amount=float(self.payments[0].entitlement_quantity),
fsp_code="1",
Expand All @@ -158,7 +156,6 @@ def test_sync_records_for_split(
record_code="2",
parent="2",
status="ERROR",
hope_status=Payment.STATUS_ERROR,
auth_code="2",
payout_amount=0.0,
fsp_code="2",
Expand Down Expand Up @@ -188,6 +185,24 @@ def test_sync_records_for_split(
def test_sync_records(
self, get_quantity_in_usd_mock: Any, get_records_for_payment_instruction_mock: Any, get_exchange_rate_mock: Any
) -> None:
collector = IndividualFactory(household=None)
hoh = IndividualFactory(household=None)
hh = HouseholdFactory(head_of_household=hoh)
IndividualRoleInHouseholdFactory(household=hh, individual=hoh, role=ROLE_PRIMARY)
IndividualFactory.create_batch(2, household=hh)
self.payments.append(
PaymentFactory(
parent=self.pp,
household=hh,
status=Payment.STATUS_PENDING,
currency="PLN",
collector=collector,
delivered_quantity=None,
delivered_quantity_usd=None,
financial_service_provider=self.pg_fsp,
)
)

self.dm.sent_to_payment_gateway = True
self.dm.save()

Expand All @@ -199,10 +214,74 @@ def test_sync_records(
modified="2023-10-11",
record_code="1",
parent="1",
status="TRANSFERRED_TO_BENEFICIARY",
hope_status=Payment.STATUS_DISTRIBUTION_SUCCESS,
status="ERROR",
auth_code="1",
fsp_code="1",
message="Error",
),
PaymentRecordData(
id=2,
remote_id=str(self.payments[1].id),
created="2023-10-10",
modified="2023-10-11",
record_code="2",
parent="2",
status="ERROR",
auth_code="2",
fsp_code="2",
payout_amount=1.23,
),
PaymentRecordData(
id=3,
remote_id=str(self.payments[2].id),
created="2023-10-10",
modified="2023-10-11",
record_code="3",
parent="3",
status="CANCELLED",
auth_code="3",
fsp_code="3",
),
]

pg_service = PaymentGatewayService()
pg_service.api.get_records_for_payment_instruction = get_records_for_payment_instruction_mock # type: ignore

pg_service.sync_records()
assert get_records_for_payment_instruction_mock.call_count == 1
self.pp.refresh_from_db()
self.payments[0].refresh_from_db()
self.payments[1].refresh_from_db()
self.payments[2].refresh_from_db()
assert self.payments[0].status == Payment.STATUS_ERROR
assert self.payments[1].status == Payment.STATUS_ERROR
assert self.payments[2].status == Payment.STATUS_MANUALLY_CANCELLED
assert self.payments[0].reason_for_unsuccessful_payment == "Error"
assert self.payments[1].reason_for_unsuccessful_payment == "Delivered amount: 1.23"
assert self.payments[2].reason_for_unsuccessful_payment == "Unknown error"
assert self.pp.is_reconciled

@mock.patch("hct_mis_api.apps.payment.models.PaymentPlan.get_exchange_rate", return_value=2.0)
@mock.patch(
"hct_mis_api.apps.payment.services.payment_gateway.PaymentGatewayAPI.get_records_for_payment_instruction"
)
@mock.patch("hct_mis_api.apps.payment.services.payment_gateway.get_quantity_in_usd", return_value=100.00)
def test_sync_records_error_messages(
self, get_quantity_in_usd_mock: Any, get_records_for_payment_instruction_mock: Any, get_exchange_rate_mock: Any
) -> None:
self.dm.sent_to_payment_gateway = True
self.dm.save()

get_records_for_payment_instruction_mock.return_value = [
PaymentRecordData(
id=1,
remote_id=str(self.payments[0].id),
created="2023-10-10",
modified="2023-10-11",
record_code="1",
parent="1",
status="PENDING",
auth_code="1",
payout_amount=float(self.payments[0].entitlement_quantity),
fsp_code="1",
),
PaymentRecordData(
Expand All @@ -213,7 +292,6 @@ def test_sync_records(
record_code="2",
parent="2",
status="TRANSFERRED_TO_BENEFICIARY",
hope_status=Payment.STATUS_DISTRIBUTION_SUCCESS,
auth_code="2",
payout_amount=float(self.payments[1].entitlement_quantity) - 10.00,
fsp_code="2",
Expand All @@ -228,14 +306,53 @@ def test_sync_records(

pg_service.sync_records()
assert get_records_for_payment_instruction_mock.call_count == 1
self.pp.refresh_from_db()
self.payments[0].refresh_from_db()
self.payments[1].refresh_from_db()
assert self.payments[0].status == Payment.STATUS_DISTRIBUTION_SUCCESS
assert self.payments[0].status == Payment.STATUS_SENT_TO_PG
assert self.payments[0].fsp_auth_code == "1"
assert self.payments[0].delivered_quantity == self.payments[0].entitlement_quantity
assert self.payments[0].delivered_quantity is None
assert self.payments[1].status == Payment.STATUS_DISTRIBUTION_PARTIAL
assert self.payments[1].fsp_auth_code == "2"
assert self.payments[1].delivered_quantity == self.payments[1].entitlement_quantity - Decimal(10.00)
assert self.pp.is_reconciled is False

get_records_for_payment_instruction_mock.return_value = [
PaymentRecordData(
id=1,
remote_id=str(self.payments[0].id),
created="2023-10-10",
modified="2023-10-11",
record_code="1",
parent="1",
status="TRANSFERRED_TO_BENEFICIARY",
auth_code="1",
payout_amount=float(self.payments[0].entitlement_quantity),
fsp_code="1",
),
PaymentRecordData(
id=2,
remote_id=str(self.payments[1].id),
created="2023-10-10",
modified="2023-10-11",
record_code="2",
parent="2",
status="TRANSFERRED_TO_BENEFICIARY",
auth_code="2",
payout_amount=float(self.payments[1].entitlement_quantity) - 10.00,
fsp_code="2",
),
]

get_records_for_payment_instruction_mock.reset_mock()
pg_service.sync_records()
assert get_records_for_payment_instruction_mock.call_count == 1
self.payments[0].refresh_from_db()
self.payments[1].refresh_from_db()
assert self.payments[0].status == Payment.STATUS_DISTRIBUTION_SUCCESS
assert self.payments[0].delivered_quantity == self.payments[0].entitlement_quantity
assert self.payments[1].status == Payment.STATUS_DISTRIBUTION_PARTIAL
assert self.payments[1].delivered_quantity == self.payments[1].entitlement_quantity - Decimal(10.00)

# pp is reconciled at this point
get_records_for_payment_instruction_mock.reset_mock()
Expand All @@ -251,22 +368,19 @@ def test_get_hope_status(self) -> None:
record_code="1",
parent="1",
status="TRANSFERRED_TO_BENEFICIARY",
hope_status=Payment.STATUS_DISTRIBUTION_SUCCESS,
auth_code="1",
payout_amount=float(self.payments[0].entitlement_quantity),
fsp_code="1",
)
self.assertEqual(p.get_hope_status(self.payments[0].entitlement_quantity), Payment.STATUS_DISTRIBUTION_SUCCESS)
self.assertEqual(p.get_hope_status(Decimal(1000000.00)), Payment.STATUS_DISTRIBUTION_PARTIAL)

with self.assertRaisesMessage(PaymentGatewayAPI.PaymentGatewayAPIException, "Invalid delivered_quantity"):
p.payout_amount = None # type: ignore
p.get_hope_status(Decimal(1000000.00))
p.payout_amount = None
self.assertEqual(p.get_hope_status(Decimal(1000000.00)), Payment.STATUS_ERROR)

with self.assertRaisesMessage(PaymentGatewayAPI.PaymentGatewayAPIException, "Invalid Payment status"):
p.payout_amount = float(self.payments[0].entitlement_quantity)
p.status = "NOT EXISTING STATUS"
p.get_hope_status(Decimal(1000000.00))
p.payout_amount = float(self.payments[0].entitlement_quantity)
p.status = "NOT EXISTING STATUS"
self.assertEqual(p.get_hope_status(Decimal(1000000.00)), Payment.STATUS_ERROR)

@mock.patch(
"hct_mis_api.apps.payment.services.payment_gateway.PaymentGatewayAPI.add_records_to_payment_instruction"
Expand Down
2 changes: 1 addition & 1 deletion backend/hct_mis_api/apps/payment/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def get_payment_plan_object(cash_or_payment_plan_id: str) -> Union["PaymentPlan"


def get_payment_delivered_quantity_status_and_value(
delivered_quantity: Union[int, float, str], entitlement_quantity: Decimal
delivered_quantity: Optional[Union[int, float, str]], entitlement_quantity: Decimal
) -> typing.Tuple[str, Optional[Decimal]]:
"""
* Fully Delivered (entitled quantity = delivered quantity) [int, float, str]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ export function PaymentsTableRow({
if (status === PaymentStatus.TransactionErroneous) {
return <RoutedBox>UNSUCCESSFUL</RoutedBox>;
}
if (status === PaymentStatus.ManuallyCancelled) {
return <RoutedBox>CANCELLED</RoutedBox>;
}
if (deliveredQuantity === null) {
return <></>;
}
Expand Down

0 comments on commit 775271d

Please sign in to comment.