Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

202443_Payment_Module_Configure_Mobile_money_delivery_mechanisms_to_send_payment_records_to_WU_through_Payment_Gateway #4020

Merged
23 changes: 23 additions & 0 deletions backend/hct_mis_api/apps/payment/migrations/0137_migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.25 on 2024-07-10 14:23

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('payment', '0136_migration'),
]

operations = [
migrations.AlterField(
model_name='payment',
name='status',
field=models.CharField(choices=[('Distribution Successful', 'Distribution Successful'), ('Not Distributed', 'Not Distributed'), ('Transaction Successful', 'Transaction Successful'), ('Transaction Erroneous', 'Transaction Erroneous'), ('Force failed', 'Force failed'), ('Partially Distributed', 'Partially Distributed'), ('Pending', 'Pending'), ('Sent to Payment Gateway', 'Sent to Payment Gateway'), ('Sent to FSP', 'Sent to FSP'), ('Manually Cancelled', 'Manually Cancelled')], default='Pending', max_length=255),
),
migrations.AlterField(
model_name='paymentrecord',
name='status',
field=models.CharField(choices=[('Distribution Successful', 'Distribution Successful'), ('Not Distributed', 'Not Distributed'), ('Transaction Successful', 'Transaction Successful'), ('Transaction Erroneous', 'Transaction Erroneous'), ('Force failed', 'Force failed'), ('Partially Distributed', 'Partially Distributed'), ('Pending', 'Pending'), ('Sent to Payment Gateway', 'Sent to Payment Gateway'), ('Sent to FSP', 'Sent to FSP'), ('Manually Cancelled', 'Manually Cancelled')], default='Pending', max_length=255),
),
]
39 changes: 24 additions & 15 deletions backend/hct_mis_api/apps/payment/services/payment_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ class Meta:
]


class PaymentPayloadSerializer(serializers.Serializer):
amount = serializers.DecimalField(max_digits=12, decimal_places=2, required=True)
phone_no = serializers.CharField(required=False)
last_name = serializers.CharField(required=False)
first_name = serializers.CharField(required=False)
full_name = serializers.CharField(required=False)
destination_currency = serializers.CharField(required=True)
service_provider_code = serializers.CharField(required=False)


class PaymentSerializer(ReadOnlyModelSerializer):
remote_id = serializers.CharField(source="id")
record_code = serializers.CharField(source="unicef_id")
Expand All @@ -115,21 +125,20 @@ def get_extra_data(self, obj: Payment) -> Dict:
return {}

def get_payload(self, obj: Payment) -> Dict:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use TypedDict here to create some kind of contract

"""
amount: int # 120000
phone_no: str # "78933211"
last_name: str # "Arabic"
first_name: str # "Angelina"
destination_currency: str # "USD"
"""
return {
"amount": obj.entitlement_quantity,
"phone_no": str(obj.collector.phone_no),
"last_name": obj.collector.family_name,
"first_name": obj.collector.given_name,
"full_name": obj.full_name,
"destination_currency": obj.currency,
}
payload = PaymentPayloadSerializer(
data={
"amount": obj.entitlement_quantity,
"phone_no": str(obj.collector.phone_no),
"last_name": obj.collector.family_name,
"first_name": obj.collector.given_name,
"full_name": obj.full_name,
"destination_currency": obj.currency,
"service_provider_code": obj.collector.flex_fields.get("service_provider_code", ""),
}
)
if not payload.is_valid():
raise serializers.ValidationError(payload.errors)
return payload.data

class Meta:
model = Payment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import pytest
from pytz import utc
from rest_framework.exceptions import ValidationError

from hct_mis_api.apps.account.fixtures import UserFactory
from hct_mis_api.apps.core.base_test_case import APITestCase
Expand Down Expand Up @@ -76,7 +77,12 @@ def setUpTestData(cls) -> None:
)
cls.payments = []
for _ in range(2):
collector = IndividualFactory(household=None)
collector = IndividualFactory(
household=None,
flex_fields={
"service_provider_code": "123456789",
},
)
hoh = IndividualFactory(household=None)
hh = HouseholdFactory(head_of_household=hoh)
IndividualRoleInHouseholdFactory(household=hh, individual=hoh, role=ROLE_PRIMARY)
Expand Down Expand Up @@ -411,3 +417,44 @@ def test_add_records_to_payment_instructions_error(self, add_records_to_payment_
self.assertEqual(self.payments[1].status, Payment.STATUS_ERROR)
self.assertEqual(self.payments[0].reason_for_unsuccessful_payment, "Error")
self.assertEqual(self.payments[1].reason_for_unsuccessful_payment, "Error")

@mock.patch("hct_mis_api.apps.payment.services.payment_gateway.PaymentGatewayAPI._post")
def test_api_add_records_to_payment_instruction(self, post_mock: Any) -> None:
post_mock.return_value = {
"remote_id": "123",
"records": {
"1": self.payments[0].id,
},
"errors": None,
}
PaymentGatewayAPI().add_records_to_payment_instruction([self.payments[0]], "123")
post_mock.assert_called_once_with(
"payment_instructions/123/add_records/",
[
{
"remote_id": str(self.payments[0].id),
"record_code": self.payments[0].unicef_id,
"payload": {
"amount": str(self.payments[0].entitlement_quantity),
"phone_no": str(self.payments[0].collector.phone_no),
"last_name": self.payments[0].collector.family_name,
"first_name": self.payments[0].collector.given_name,
"full_name": self.payments[0].collector.full_name,
"destination_currency": self.payments[0].currency,
"service_provider_code": self.payments[0].collector.flex_fields["service_provider_code"],
},
"extra_data": {},
}
],
validate_response=True,
)

@mock.patch("hct_mis_api.apps.payment.services.payment_gateway.PaymentGatewayAPI._post")
def test_api_add_records_to_payment_instruction_validation_error(self, post_mock: Any) -> None:
payment = self.payments[0]
payment.entitlement_quantity = None
payment.save()
with self.assertRaisesMessage(
ValidationError, "{'amount': [ErrorDetail(string='This field may not be null.', code='null')]}"
):
PaymentGatewayAPI().add_records_to_payment_instruction([payment], "123")
Loading