# -*- coding: utf-8 -*- ## $Id: test_payment.py 12813 2015-03-23 15:56:55Z uli $ ## ## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## import datetime import decimal import re import unittest from decimal import Decimal from zope.component import ( getUtilitiesFor, getSiteManager, queryUtility, getGlobalSiteManager, ) from zope.component.hooks import setSite from zope.interface import implements from zope.interface.verify import verifyClass, verifyObject from waeup.ikoba.payments.interfaces import ( IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED, IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister, IPayableFinder, IPayerFinder, ) from waeup.ikoba.app import Company from waeup.ikoba.payments.payment import ( Payment, get_payment_providers, PaymentItem, format_payment_item_values, get_payment, get_payments_from_payer_id, find_payable_from_payable_id, find_payer_from_payer_id, get_payments_from_payable_id, format_amount, ) from waeup.ikoba.payments.testing import ( FakePayer, FakePayable, FAKE_PAYMENT_ITEMS ) from waeup.ikoba.testing import FunctionalLayer, FunctionalTestCase class HelperTests(unittest.TestCase): def tearDown(self): # unregister any IPaymentGatewayServices sm = getSiteManager(None) for name, util in getUtilitiesFor(IPaymentGatewayService): sm.unregisterUtility(util, name=name) def test_get_payment_providers_no_providers(self): # we can get a dict of all payment providers result = get_payment_providers() assert isinstance(result, dict) assert result == {} def test_get_payment_providers(self): # we get any payment providers registered sm = getSiteManager(None) class FakeUtil(object): implements(IPaymentGatewayService) fake_util = FakeUtil() sm.registerUtility(fake_util, name=u'some_name') result = get_payment_providers() assert isinstance(result, dict) assert result.keys() == ['some_name', ] assert result['some_name'] is fake_util def test_format_payment_item_values(self): # we can format lists of payment item values result = format_payment_item_values( [(u'Item 1', 'USD', decimal.Decimal("12.123")), (u'Item 2', 'USD', decimal.Decimal("12.002")), ], 'USD') self.assertEqual( result, [(u'Item 1', 'USD 12.12'), (u'Item 2', 'USD 12.00'), (u'Total', 'USD 24.12')] ) def test_format_payment_item_values_req_single_currency(self): # we require one currency for all items, yet. self.assertRaises( ValueError, format_payment_item_values, [(u'Item 1', 'USD', decimal.Decimal("12.12")), (u'Item 2', 'EUR', decimal.Decimal("50")), ], 'USD') def test_format_amount(self): # we can make amounts readable D = decimal.Decimal self.assertEqual(format_amount(D("0"), 'USD'), u"US$ 0.00") self.assertEqual(format_amount(D("0.1"), 'EUR'), u"€ 0.10") self.assertEqual(format_amount(D("-1.2"), 'NGN'), u"₦ -1.20") self.assertEqual(format_amount(D("1234.5"), 'YEN'), u"YEN 1,234.50") class FunctionalHelperTests(FunctionalTestCase): layer = FunctionalLayer def test_services_lister_is_registered(self): # a lister of gateway services is registered on startup util = queryUtility(IPaymentGatewayServicesLister) assert util is not None def test_services_are_really_listed(self): # we can really get locally registered gateways when calling util = queryUtility(IPaymentGatewayServicesLister) assert len(util()) > 0 def test_get_payment(self): # we can lookup payments. self.getRootFolder()['app'] = Company() app = self.getRootFolder()['app'] setSite(app) p1 = Payment(FakePayer(), FakePayable()) app['payments']['1'] = p1 p_id = p1.payment_id result = get_payment(p_id) self.assertTrue(result is p1) self.assertTrue(get_payment('not-valid') is None) def test_get_payments_from_payer_id(self): # we can lookup payments from payer ids. self.getRootFolder()['app'] = Company() app = self.getRootFolder()['app'] setSite(app) p1 = Payment(FakePayer(), FakePayable()) app['payments']['1'] = p1 result = get_payments_from_payer_id('PAYER_01') self.assertTrue(result[0] is p1) self.assertTrue(get_payments_from_payer_id('not-valid') is None) def test_get_payments_from_payable_id(self): # we can lookup payments from payable ids. self.getRootFolder()['app'] = Company() app = self.getRootFolder()['app'] setSite(app) p1 = Payment(FakePayer(), FakePayable()) app['payments']['1'] = p1 result = get_payments_from_payable_id('PAYABLE_01') self.assertTrue(result[0] is p1) self.assertTrue(get_payments_from_payable_id('not-valid') is None) def test_find_payable_from_payable_id(self): # we can find payables. obj1 = object() obj2 = object() class FakeFinder(object): valid = {'id1': obj1, 'id3': obj2} def get_payable_by_id(self, the_id): return self.valid.get(the_id) finder1 = FakeFinder() finder1.valid = {'id1': obj1} finder2 = FakeFinder() finder2.valid = {'id2': obj2} gsm = getGlobalSiteManager() try: gsm.registerUtility(finder1, provided=IPayableFinder, name='f1') gsm.registerUtility(finder2, provided=IPayableFinder, name='f2') result1 = find_payable_from_payable_id('id1') result2 = find_payable_from_payable_id('id2') result3 = find_payable_from_payable_id('id3') finally: gsm.unregisterUtility(finder1, IPayableFinder) gsm.unregisterUtility(finder2, IPayableFinder) self.assertTrue(result1 is obj1) self.assertTrue(result2 is obj2) self.assertTrue(result3 is None) def test_find_payer_from_payer_id(self): # we can find payables. obj1 = object() obj2 = object() class FakeFinder(object): valid = {'id1': obj1, 'id3': obj2} def get_payer_by_id(self, the_id): return self.valid.get(the_id) finder1 = FakeFinder() finder1.valid = {'id1': obj1} finder2 = FakeFinder() finder2.valid = {'id2': obj2} gsm = getGlobalSiteManager() try: gsm.registerUtility(finder1, provided=IPayerFinder, name='f1') gsm.registerUtility(finder2, provided=IPayerFinder, name='f2') result1 = find_payer_from_payer_id('id1') result2 = find_payer_from_payer_id('id2') result3 = find_payer_from_payer_id('id3') finally: gsm.unregisterUtility(finder1, IPayerFinder) gsm.unregisterUtility(finder2, IPayerFinder) self.assertTrue(result1 is obj1) self.assertTrue(result2 is obj2) self.assertTrue(result3 is None) class PaymentTests(FunctionalTestCase): layer = FunctionalLayer def setUp(self): super(PaymentTests, self).setUp() self.payer = FakePayer() self.payable = FakePayable() def test_iface(self): # Payments fullfill any interface contracts obj = Payment(self.payer, self.payable) verifyClass(IPayment, Payment) verifyObject(IPayment, obj) def test_initial_values(self): # important attributes are set initially payer = self.payer payer.payer_id = u'PAYER_ID' payable = self.payable payable.payable_id = u'PAYABLE_ID' payable.title = u'PAYABLE-TITLE' payable.currency = 'NGN' payment = Payment(payer, payable) assert payment.payer_id == u'PAYER_ID' assert payment.payable_id == u'PAYABLE_ID' assert payment.title == u'PAYABLE-TITLE' assert payment.currency == 'NGN' assert isinstance(payment.creation_date, datetime.datetime) assert payment.payment_date is None def test_payment_id_unique(self): # we get unique payment ids p1 = Payment(self.payer, self.payable) p2 = Payment(self.payer, self.payable) id1, id2 = p1.payment_id, p2.payment_id assert id1 != id2 def test_payment_id_format(self): # payment ids have a special format: "<32 hex digits>" id1 = Payment(self.payer, self.payable).payment_id assert isinstance(id1, basestring) assert re.match('[0-9a-f]{32}', id1) def test_initial_state_is_unpaid(self): # the initial state of payments is p1 = Payment(self.payer, self.payable) assert p1.state == STATE_UNPAID def test_approve(self): # we can approve payments p1 = Payment(self.payer, self.payable) p1.approve() assert p1.state == STATE_PAID assert p1.payment_date is not None assert isinstance(p1.payment_date, datetime.datetime) def test_approve_datetime_given(self): # we can give a datetime p1 = Payment(self.payer, self.payable) some_datetime = datetime.datetime(2014, 1, 1, 0, 0, 0) p1.approve(payment_date=some_datetime) assert p1.payment_date == some_datetime def test_approve_datetime_automatic(self): # if we do not give a datetime, current one will be used current = datetime.datetime.utcnow() p1 = Payment(self.payer, self.payable) p1.approve() assert p1.payment_date >= current def test_mark_failed(self): # we can mark payments as failed p1 = Payment(self.payer, self.payable) p1.mark_failed() assert p1.state == STATE_FAILED def test_amount(self): # the amount of a payment is the sum of amounts of its items payable = self.payable payable.payment_items[0].amount = decimal.Decimal("12.25") payable.payment_items[1].amount = decimal.Decimal("0.5") p1 = Payment(self.payer, self.payable) assert p1.amount == decimal.Decimal("12.75") def test_amount_negative(self): # we can sum up negative numbers payable = self.payable payable.payment_items[0].amount = decimal.Decimal("2.21") payable.payment_items[1].amount = decimal.Decimal("-3.23") p1 = Payment(self.payer, payable) assert p1.amount == decimal.Decimal("-1.02") def test_amount_empty(self): # the amount of zero items is None. payable = FakePayable(payment_items=()) p1 = Payment(self.payer, payable) self.assertEqual(p1.amount, decimal.Decimal("0.00")) def test_amount_changed_after_init(self): # amount is dynamic: changes to payment items are reflected payable = FakePayable(payment_items=()) p1 = Payment(self.payer, payable) assert p1.amount == decimal.Decimal("0.00") p1.payment_items = (PaymentItem(amount=decimal.Decimal("5.55")), ) assert p1.amount == decimal.Decimal("5.55") p1.payment_items = ( PaymentItem(amount=decimal.Decimal("5.55")), PaymentItem(amount=decimal.Decimal("1.11")), ) assert p1.amount == decimal.Decimal("6.66") def test_payment_items(self): # we can get payment items from a payment payable = FakePayable(payment_items=FAKE_PAYMENT_ITEMS) p1 = Payment(self.payer, payable) assert isinstance(p1.payment_items, tuple) def test_payment_items_number(self): # payment item values are respected payable = FakePayable() item = PaymentItem(amount=decimal.Decimal("9.99")) payable.payment_items = [item, ] payment = Payment(self.payer, payable) assert len(payment.payment_items) == 1 assert payment.payment_items[0] is item class PaymentItemTests(unittest.TestCase): def test_iface(self): # PaymentItems fullfill any interface contracts obj = PaymentItem() verifyClass(IPaymentItem, PaymentItem) verifyObject(IPaymentItem, obj) def test_to_string(self): # we can turn default PaymentItems into strings obj = PaymentItem() self.assertEqual( obj.to_string(), u"(u'', u'0.00')") def test_to_string_none_values(self): # 'None' values are properly represented by to_string() obj = PaymentItem() obj.item_id = None self.assertEqual( obj.to_string(), u"(u'', u'0.00')") def test_to_string_enocded_values(self): # to_string() copes with encoded strings obj = PaymentItem() obj.title = u'ümläut' self.assertEqual( obj.to_string(), u"(u'ümläut', u'0.00')") def test_repr(self): # we can get a proper representation of PaymentItem obj = PaymentItem() self.assertEqual( repr(obj), "PaymentItem(title=u'', amount=Decimal('0.00'))") def test_repr_can_be_evaled(self): # we can eval() representations obj = PaymentItem(title=u'My Title', amount=decimal.Decimal("1.99")) representation = repr(obj) self.assertEqual( representation, "PaymentItem(title=u'My Title', amount=Decimal('1.99'))" ) new_obj = eval(representation) assert new_obj is not obj assert new_obj.title == obj.title == u"My Title" assert new_obj.amount == obj.amount == Decimal("1.99")