Skip to content

Commit

Permalink
moneygram
Browse files Browse the repository at this point in the history
  • Loading branch information
domdinicola committed Oct 4, 2024
1 parent 2705cf6 commit af0a140
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 91 deletions.
3 changes: 3 additions & 0 deletions src/hope_payment_gateway/apps/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
admin.site.site_header = "Payment Gateway"


admin.site.site_header = "Payment Gateway"


@admin.register(User)
class UserAdminPlus(UserAdminPlus):
pass
Expand Down
138 changes: 78 additions & 60 deletions src/hope_payment_gateway/apps/fsp/moneygram/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
import phonenumbers
import requests
from phonenumbers import NumberParseException
from urllib3.connectionpool import HTTPSConnectionPool
from requests.exceptions import ConnectionError

from hope_payment_gateway.apps.core.models import Singleton
from hope_payment_gateway.apps.gateway.flows import PaymentRecordFlow
from hope_payment_gateway.apps.gateway.models import PaymentRecord

logger = logging.getLogger(__name__)

Expand All @@ -20,44 +18,69 @@
"WILL_CALL": "WILL_CALL",
"DIRECT_TO_ACCT": "DIRECT_TO_ACCT",
"BANK_DEPOSIT": "DIRECT_TO_ACCT",
"WILLCALL_TO": "WILLCALL_TO",
"2_HOUR": "2_HOUR",
"OVERNIGHT": "OVERNIGHT",
"OVERNIGHT2ANY": "OVERNIGHT2ANY",
"24_HOUR": "24_HOUR",
"CARD_DEPOSIT": "CARD_DEPOSIT",
"HOME_DELIVERY": "HOME_DELIVERY",
}


class PayloadMissingKey(Exception):
pass


class InvalidToken(Exception):
pass


class MoneyGramClient(metaclass=Singleton):
token = ""
expires_in = None
token_response = None

def __init__(self):
self.get_token()
self.set_token()

def get_token(self):
def set_token(self):
url = settings.MONEYGRAM_HOST + "/oauth/accesstoken?grant_type=client_credentials"
credentials = f"{settings.MONEYGRAM_CLIENT_ID}:{settings.MONEYGRAM_CLIENT_SECRET}"
encoded_credentials = base64.b64encode(credentials.encode("utf-8")).decode("utf-8")
headers = {"Content-Type": "application/json", "Authorization": "Basic " + encoded_credentials}

try:
response = requests.get(url, headers=headers)
except HTTPSConnectionPool:
parsed_response = json.loads(response.text)
except ConnectionError:
self.token = None
self.token_response = None
else:
if response.status_code == 200:
parsed_response = json.loads(response.text)
self.token = parsed_response["access_token"]
self.expires_in = parsed_response["expires_in"]
else:
logger.warning("Invalid token")
self.token = None
self.token_response = response
error = parsed_response["error"]
raise InvalidToken(f"{error['category']}: {error['message']} [{error['code']}]")

def prepare_transaction(self, hope_payload):
def get_headers(self, request_id):
return {
"Content-Type": "application/json",
"X-MG-ClientRequestId": request_id,
"content-type": "application/json",
"Authorization": "Bearer " + self.token,
}

@staticmethod
def get_basic_payload():
return {
"targetAudience": "AGENT_FACING",
"agentPartnerId": settings.MONEYGRAM_PARTNER_ID,
"userLanguage": "en-US",
}

def prepare_transaction(self, hope_payload):
raw_phone_no = hope_payload.get("phone_no", "N/A")
try:
phone_no = phonenumbers.parse(raw_phone_no, None)
Expand All @@ -77,8 +100,8 @@ def prepare_transaction(self, hope_payload):
]:
if not (key in hope_payload.keys() and hope_payload[key]):
raise PayloadMissingKey("InvalidPayload: {} is missing in the payload".format(key))

return {
transaction_id = hope_payload["payment_record_code"]
payload = {
"targetAudience": "AGENT_FACING",
"agentPartnerId": settings.MONEYGRAM_PARTNER_ID,
"userLanguage": "en-US",
Expand Down Expand Up @@ -124,58 +147,53 @@ def prepare_transaction(self, hope_payload):
}
},
}
return transaction_id, payload

def create_transaction(self, hope_payload):

if self.token:
endpoint = "/disbursement/v1/transactions"
transaction_id, payload = self.prepare_transaction(hope_payload)
return self.perform_request(endpoint, transaction_id, payload)

url = settings.MONEYGRAM_HOST + "/disbursement/v1/transactions"
payload = self.prepare_transaction(hope_payload)
headers = {
"Content-Type": "application/json",
"X-MG-ClientRequestId": hope_payload["payment_record_code"],
"Authorization": "Bearer " + self.token,
}

response = self.perform_request(url, headers, payload)
self.transaction_callback(hope_payload, response.json())
return response

else:
return self.token_response

def perform_request(self, url, headers, payload=None):
try:
response = requests.post(url, json=payload, headers=headers)

if response.status_code == 200:
parsed_response = json.dumps(json.loads(response.text), indent=2)
print(parsed_response)
else:
print("Request failed with status code:", response.status_code)
print(json.dumps(json.loads(response.text), indent=2))
def prepare_quote(self, hope_payload: dict):

except (requests.exceptions.RequestException, requests.exceptions.MissingSchema) as e:
print("An error occurred:", e)
response = dict

return response

def transaction_callback(self, hope_payload, response):
record_code = hope_payload["payment_record_code"]
pr = PaymentRecord.objects.get(record_code=record_code)
pr.fsp_code = response["referenceNumber"]
pr.success = True
pr.payout_amount = response["receiveAmount"]["amount"]["value"]
pr.extra_data.update(
transaction_id = hope_payload["payment_record_code"]
payload = self.get_basic_payload()
payload.update(
{
"fee": response["receiveAmount"]["fees"]["value"],
"fee_currency": response["receiveAmount"]["fees"]["currencyCode"],
"taxes": response["receiveAmount"]["taxes"]["value"],
"taxes_currency": response["receiveAmount"]["taxes"]["currencyCode"],
"expectedPayoutDate": response["expectedPayoutDate"],
"transactionId": response["transactionId"],
"destinationCountryCode": hope_payload["destination_country"],
"serviceOptionCode": hope_payload.get("delivery_services_code", None),
"beneficiaryTypeCode": "Consumer",
"sendAmount": {"currencyCode": hope_payload["origination_currency"], "value": hope_payload["amount"]},
}
)
flow = PaymentRecordFlow(pr)
flow.store()
return transaction_id, payload

def quote(self, hope_payload):

endpoint = "/disbursement/v1/transactions/quote"
transaction_id, payload = self.prepare_quote(hope_payload)
return self.perform_request(endpoint, transaction_id, payload)

def perform_request(self, endpoint, transaction_id, payload):
url = settings.MONEYGRAM_HOST + endpoint
headers = self.get_headers(transaction_id)
for _ in range(2):
try:
response = requests.post(url, json=payload, headers=headers)
break
except (requests.exceptions.RequestException, requests.exceptions.MissingSchema) as e:
print("An error occurred:", e)
response = dict
break
except Exception as e:
print("Token Expired:", e)
self.set_token()

if response.status_code == 200:
parsed_response = json.dumps(json.loads(response.text), indent=2)
print(parsed_response)
else:
print("Request failed with status code:", response.status_code)
print(json.dumps(json.loads(response.text), indent=2))
return response
15 changes: 15 additions & 0 deletions src/hope_payment_gateway/apps/fsp/moneygram/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from hope_payment_gateway.apps.gateway.models import FinancialServiceProvider, FinancialServiceProviderConfig
from hope_payment_gateway.apps.gateway.registry import FSPProcessor


class WesternUnionHandler(FSPProcessor):

def get_configuration(self, config_key, delivery_mechanism):
wu = FinancialServiceProvider.objects.get(vision_vendor_number="1900723202")
try:
config = FinancialServiceProviderConfig.objects.get(
key=config_key, fsp=wu, delivery_mechanism__code=delivery_mechanism
).configuration
except FinancialServiceProviderConfig.DoesNotExist:
config = wu.configuration
return config
41 changes: 41 additions & 0 deletions src/hope_payment_gateway/apps/fsp/moneygram/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from rest_framework.response import Response
from rest_framework.status import HTTP_400_BAD_REQUEST
from viewflow.fsm import TransitionNotAllowed

from hope_payment_gateway.apps.fsp.moneygram.client import MoneyGramClient
from hope_payment_gateway.apps.gateway.flows import PaymentRecordFlow
from hope_payment_gateway.apps.gateway.models import PaymentRecord


def quote_transaction(payload):
client = MoneyGramClient()
response = client.quote(payload)
return response


def create_transaction(payload):
client = MoneyGramClient()
response = client.create_transaction(payload)
if response:
body = response.json()
record_code = payload["payment_record_code"]
pr = PaymentRecord.objects.get(record_code=record_code)
pr.fsp_code = body["referenceNumber"]
pr.success = True
pr.payout_amount = body["receiveAmount"]["amount"]["value"]
pr.extra_data.update(
{
"fee": body["receiveAmount"]["fees"]["value"],
"fee_currency": body["receiveAmount"]["fees"]["currencyCode"],
"taxes": body["receiveAmount"]["taxes"]["value"],
"taxes_currency": body["receiveAmount"]["taxes"]["currencyCode"],
"expectedPayoutDate": body["expectedPayoutDate"],
"transactionId": body["transactionId"],
}
)
try:
flow = PaymentRecordFlow(pr)
flow.store()
except TransitionNotAllowed as e:
response = Response({"transition_not_allowed": str(e)}, status=HTTP_400_BAD_REQUEST)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.
return response
Loading

0 comments on commit af0a140

Please sign in to comment.