diff --git a/payu/__init__.py b/payu/__init__.py index da1eab0..88caf4f 100644 --- a/payu/__init__.py +++ b/payu/__init__.py @@ -15,4 +15,4 @@ # __author__ = 'Presslabs' -__version__ = '1.0.0-rc2' +__version__ = '1.0.0' diff --git a/payu/conf.py b/payu/conf.py index 583f58d..d734953 100644 --- a/payu/conf.py +++ b/payu/conf.py @@ -29,7 +29,8 @@ 'https://secure.payu.ro/order/alu/v3') PAYU_IDN_URL = getattr(settings, 'PAYU_IDN_URL', 'https://secure.payu.ro/order/idn.php') - +PAYU_IOS_URL = getattr(settings, 'PAYU_IOS_URL', + 'https://secure.payu.ro/order/ios.php') PAYU_TOKENS_URL = getattr(settings, 'PAYU_TOKENS_URL', 'https://secure.payu.ro/order/tokens/') diff --git a/payu/models.py b/payu/models.py index 65cba18..4b6d540 100644 --- a/payu/models.py +++ b/payu/models.py @@ -13,11 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import time import hmac -import hashlib -from datetime import datetime from collections import OrderedDict +from datetime import datetime import pytz import requests diff --git a/payu/utils.py b/payu/utils.py new file mode 100644 index 0000000..f464f95 --- /dev/null +++ b/payu/utils.py @@ -0,0 +1,45 @@ +import hmac +from collections import OrderedDict +from xml.etree import ElementTree + +import requests + +from payu.conf import PAYU_IOS_URL, PAYU_MERCHANT, PAYU_MERCHANT_KEY + + +def _signature(payload, merchant_key): + confirmation_hash = "".join(["%s%s" % (len(str(payload[field])), + payload[field]) + for field in payload]) + return hmac.new(merchant_key, confirmation_hash).hexdigest() + + +def _build_payload(merchant, merchant_key, ref_no_ext): + payload = OrderedDict([ + ('MERCHANT', merchant), + ('REFNOEXT', ref_no_ext) + ]) + payload["HASH"] = _signature(payload, merchant_key) + + return payload + + +def get_instant_order_status(ipn): + """ + :param ipn: A PayUIPN object containing the REFNOEXT attribute. + :return: A dict containing details about the status of the order. + """ + payload = _build_payload(PAYU_MERCHANT, PAYU_MERCHANT_KEY, ipn.REFNOEXT) + + response = requests.post(PAYU_IOS_URL, data=payload) + + try: + parsed_response = ElementTree.fromstring(response.content) + except ElementTree.ParseError: + return {'error': "Couldn't parse the XML result from PayU.", + 'result': response.content} + + if parsed_response.tag == 'Error': + return {'error': parsed_response.text.strip()} + + return {child.tag: (child.text or '').strip() for child in parsed_response} diff --git a/tests/test_ios.py b/tests/test_ios.py new file mode 100644 index 0000000..e264797 --- /dev/null +++ b/tests/test_ios.py @@ -0,0 +1,80 @@ +from collections import OrderedDict + +import pytest +from mock import MagicMock, patch +from django_dynamic_fixture import G + +from payu.models import PayUIPN +from payu.utils import _signature, _build_payload, get_instant_order_status + + +def test_ios_signature(): + # this data is taken from the PayU documentation + + payload = OrderedDict([ + ('MERCHANT', 'PAYUDEMO'), + ('REFNOEXT', 'EPAY10425') + ]) + + signature = _signature(payload, merchant_key='1231234567890123') + + assert signature == '6cb19f366fd9709b078b593b1736a4ea' + + +def test_ios_payload(): + # this data is taken from the PayU documentation + + payload = _build_payload(merchant='PAYUDEMO', + ref_no_ext='EPAY10425', + merchant_key='1231234567890123') + + assert payload == OrderedDict([ + ('MERCHANT', 'PAYUDEMO'), + ('REFNOEXT', 'EPAY10425'), + ('HASH', '6cb19f366fd9709b078b593b1736a4ea') + ]) + + +@pytest.mark.django_db +@patch('payu.utils.requests') +def test_ios_get_instant_order_status(mocked_requests): + response_content = """ + + 2017-07-12 12:16:20 + 45459240 + b3045b90-d11f-4c78-9f6b-79f54317df36 + TEST + Visa/MasterCard/Eurocard + adc135ddd73f0f4fe3eb9d26b84c662d + """ + + mocked_requests.post.return_value = MagicMock(status_code=200, content=response_content) + + ipn = G(PayUIPN) + + status = get_instant_order_status(ipn) + + assert status == {'HASH': 'adc135ddd73f0f4fe3eb9d26b84c662d', + 'PAYMETHOD': 'Visa/MasterCard/Eurocard', + 'REFNO': '45459240', + 'ORDER_STATUS': 'TEST', + 'ORDER_DATE': '2017-07-12 12:16:20', + 'REFNOEXT': 'b3045b90-d11f-4c78-9f6b-79f54317df36'} + + +@pytest.mark.django_db +@pytest.mark.parametrize('payu_response, expected_result', [ + ('Limit calls for IOS exceeded!', + {'error': 'Limit calls for IOS exceeded!'}), + ('Access not permitted', {'error': "Couldn't parse the XML result from PayU.", + 'result': 'Access not permitted'}), +]) +@patch('payu.utils.requests') +def test_ios_get_instant_order_status_error(mocked_requests, payu_response, expected_result): + mocked_requests.post.return_value = MagicMock(status_code=200, content=payu_response) + + ipn = G(PayUIPN) + + result = get_instant_order_status(ipn) + + assert result == expected_result