source: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_payment.py @ 12753

Last change on this file since 12753 was 12753, checked in by Henrik Bettermann, 10 years ago

Add helper function get_payments_from_payer_id.

  • Property svn:keywords set to Id
File size: 11.9 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: test_payment.py 12753 2015-03-12 11:02:24Z henrik $
3##
4## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2 of the License, or
8## (at your option) any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program; if not, write to the Free Software
17## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18##
19import datetime
20import decimal
21import re
22import unittest
23from zope.component import (
24    getUtilitiesFor, getSiteManager, queryUtility, getGlobalSiteManager,
25    )
26from zope.component.hooks import setSite
27from zope.interface import implements, implementer
28from zope.interface.verify import verifyClass, verifyObject
29from waeup.ikoba.payments.interfaces import (
30    IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED,
31    IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister,
32    IPayableFinder, IPayerFinder, IPayable, IPayer,
33    )
34from waeup.ikoba.app import Company
35from waeup.ikoba.payments.payment import (
36    Payment, get_payment_providers, PaymentItem, format_payment_item_values,
37    get_payment, get_payments_from_payer_id, find_payable_from_payable_id,
38    find_payer_from_payer_id, format_amount,
39    )
40from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase)
41
42
43@implementer(IPayer)
44class FakePayer(object):
45
46    def __init__(
47        self, payer_id=u'PAYER_01', first_name=u'Anna', last_name='Tester'):
48        self.payer_id = payer_id
49        self.first_name = first_name
50        self.last_name = last_name
51
52
53FAKE_PAYMENT_ITEMS = (
54    PaymentItem(u'ITEM1', u'Item title 1', decimal.Decimal("1.00")),
55    PaymentItem(u'ITEM2', u'Item title 2', decimal.Decimal("2.2")),
56    )
57
58
59@implementer(IPayable)
60class FakePayable(object):
61
62    payable_id = u'id1'
63    items = (
64        (u'item 1', decimal.Decimal("1.00")),
65        (u'item 2', decimal.Decimal("2.12")),
66        )
67
68    def __init__(self, payable_id=u'PAYABLE_01', title=u'title',
69                 currency=u'USD', payment_items=FAKE_PAYMENT_ITEMS):
70        self.payable_id = payable_id
71        self.title = title
72        self.currency = currency
73        self.payment_items = payment_items
74
75
76class HelperTests(unittest.TestCase):
77
78    def tearDown(self):
79        # unregister any IPaymentGatewayServices
80        sm = getSiteManager(None)
81        for name, util in getUtilitiesFor(IPaymentGatewayService):
82            sm.unregisterUtility(util, name=name)
83
84    def test_get_payment_providers_no_providers(self):
85        # we can get a dict of all payment providers
86        result = get_payment_providers()
87        assert isinstance(result, dict)
88        assert result == {}
89
90    def test_get_payment_providers(self):
91        # we get any payment providers registered
92        sm = getSiteManager(None)
93
94        class FakeUtil(object):
95            implements(IPaymentGatewayService)
96
97        fake_util = FakeUtil()
98        sm.registerUtility(fake_util, name=u'some_name')
99        result = get_payment_providers()
100        assert isinstance(result, dict)
101        assert result.keys() == ['some_name', ]
102        assert result['some_name'] is fake_util
103
104    def test_format_payment_item_values(self):
105        # we can format lists of payment item values
106        result = format_payment_item_values(
107            [(u'Item 1', 'USD', decimal.Decimal("12.123")),
108             (u'Item 2', 'USD', decimal.Decimal("12.002")),
109             ], 'USD')
110        self.assertEqual(
111            result, [(u'Item 1', 'USD 12.12'),
112                     (u'Item 2', 'USD 12.00'),
113                     (u'Total', 'USD 24.12')]
114            )
115
116    def test_format_payment_item_values_req_single_currency(self):
117        # we require one currency for all items, yet.
118        self.assertRaises(
119            ValueError, format_payment_item_values,
120            [(u'Item 1', 'USD', decimal.Decimal("12.12")),
121             (u'Item 2', 'EUR', decimal.Decimal("50")),
122             ],
123            'USD')
124
125    def test_format_amount(self):
126        # we can make amounts readable
127        D = decimal.Decimal
128        self.assertEqual(format_amount(D("0"), 'USD'), u"US$ 0.00")
129        self.assertEqual(format_amount(D("0.1"), 'EUR'), u"€ 0.10")
130        self.assertEqual(format_amount(D("-1.2"), 'NGN'), u"₦ -1.20")
131        self.assertEqual(format_amount(D("1234.5"), 'YEN'), u"YEN 1,234.50")
132
133
134class FunctionalHelperTests(FunctionalTestCase):
135
136    layer = FunctionalLayer
137
138    def test_services_lister_is_registered(self):
139        # a lister of gateway services is registered on startup
140        util = queryUtility(IPaymentGatewayServicesLister)
141        assert util is not None
142
143    def test_services_are_really_listed(self):
144        # we can really get locally registered gateways when calling
145        util = queryUtility(IPaymentGatewayServicesLister)
146        assert len(util()) > 0
147
148    def test_get_payment(self):
149        # we can lookup payments.
150        self.getRootFolder()['app'] = Company()
151        app = self.getRootFolder()['app']
152        setSite(app)
153        p1 = Payment(FakePayer(), FakePayable())
154        app['payments']['1'] = p1
155        p_id = p1.payment_id
156        result = get_payment(p_id)
157        self.assertTrue(result is p1)
158        self.assertTrue(get_payment('not-valid') is None)
159
160    def test_get_payments_from_payer_id(self):
161        # we can lookup payments.
162        self.getRootFolder()['app'] = Company()
163        app = self.getRootFolder()['app']
164        setSite(app)
165        p1 = Payment(FakePayer(), FakePayable())
166        app['payments']['1'] = p1
167        p_id = p1.payment_id
168        result = get_payments_from_payer_id('PAYER_01')
169        self.assertTrue(result[0] is p1)
170        self.assertTrue(get_payments_from_payer_id('not-valid') is None)
171
172    def test_find_payable_from_payable_id(self):
173        # we can find payables.
174        obj1 = object()
175        obj2 = object()
176
177        class FakeFinder(object):
178            valid = {'id1': obj1, 'id3': obj2}
179
180            def get_payable_by_id(self, the_id):
181                return self.valid.get(the_id)
182
183        finder1 = FakeFinder()
184        finder1.valid = {'id1': obj1}
185        finder2 = FakeFinder()
186        finder2.valid = {'id2': obj2}
187        gsm = getGlobalSiteManager()
188        try:
189            gsm.registerUtility(finder1, provided=IPayableFinder, name='f1')
190            gsm.registerUtility(finder2, provided=IPayableFinder, name='f2')
191            result1 = find_payable_from_payable_id('id1')
192            result2 = find_payable_from_payable_id('id2')
193            result3 = find_payable_from_payable_id('id3')
194        finally:
195            gsm.unregisterUtility(finder1, IPayableFinder)
196            gsm.unregisterUtility(finder2, IPayableFinder)
197        self.assertTrue(result1 is obj1)
198        self.assertTrue(result2 is obj2)
199        self.assertTrue(result3 is None)
200
201    def test_find_payer_from_payer_id(self):
202        # we can find payables.
203        obj1 = object()
204        obj2 = object()
205
206        class FakeFinder(object):
207            valid = {'id1': obj1, 'id3': obj2}
208
209            def get_payer_by_id(self, the_id):
210                return self.valid.get(the_id)
211
212        finder1 = FakeFinder()
213        finder1.valid = {'id1': obj1}
214        finder2 = FakeFinder()
215        finder2.valid = {'id2': obj2}
216        gsm = getGlobalSiteManager()
217        try:
218            gsm.registerUtility(finder1, provided=IPayerFinder, name='f1')
219            gsm.registerUtility(finder2, provided=IPayerFinder, name='f2')
220            result1 = find_payer_from_payer_id('id1')
221            result2 = find_payer_from_payer_id('id2')
222            result3 = find_payer_from_payer_id('id3')
223        finally:
224            gsm.unregisterUtility(finder1, IPayerFinder)
225            gsm.unregisterUtility(finder2, IPayerFinder)
226        self.assertTrue(result1 is obj1)
227        self.assertTrue(result2 is obj2)
228        self.assertTrue(result3 is None)
229
230
231class PaymentTests(FunctionalTestCase):
232
233    layer = FunctionalLayer
234
235    def setUp(self):
236        super(PaymentTests, self).setUp()
237        self.payer = FakePayer()
238        self.payable = FakePayable()
239
240    def test_iface(self):
241        # Payments fullfill any interface contracts
242        obj = Payment(self.payer, self.payable)
243        verifyClass(IPayment, Payment)
244        verifyObject(IPayment, obj)
245
246    def test_initial_values(self):
247        # important attributes are set initially
248        payer = self.payer
249        payer.payer_id = u'PAYER_ID'
250        payable = self.payable
251        payable.payable_id = u'PAYABLE_ID'
252        payable.title = u'PAYABLE-TITLE'
253        payable.currency = 'NGN'
254        payment = Payment(payer, payable)
255        assert payment.payer_id == u'PAYER_ID'
256        assert payment.payable_id == u'PAYABLE_ID'
257        assert payment.title == u'PAYABLE-TITLE'
258        assert payment.currency == 'NGN'
259        assert isinstance(payment.creation_date, datetime.datetime)
260        assert payment.payment_date is None
261
262    def test_payment_id_unique(self):
263        # we get unique payment ids
264        p1 = Payment(self.payer, self.payable)
265        p2 = Payment(self.payer, self.payable)
266        id1, id2 = p1.payment_id, p2.payment_id
267        assert id1 != id2
268
269    def test_payment_id_format(self):
270        # payment ids have a special format: "PAY_<32 hex digits>"
271        id1 = Payment(self.payer, self.payable).payment_id
272        assert isinstance(id1, basestring)
273        assert re.match('PAY_[0-9a-f]{32}', id1)
274
275    def test_initial_state_is_unpaid(self):
276        # the initial state of payments is <unpaid>
277        p1 = Payment(self.payer, self.payable)
278        assert p1.state == STATE_UNPAID
279
280    def test_approve(self):
281        # we can approve payments
282        p1 = Payment(self.payer, self.payable)
283        p1.approve()
284        assert p1.state == STATE_PAID
285        assert p1.payment_date is not None
286        assert isinstance(p1.payment_date, datetime.datetime)
287
288    def test_approve_datetime_given(self):
289        # we can give a datetime
290        p1 = Payment(self.payer, self.payable)
291        some_datetime = datetime.datetime(2014, 1, 1, 0, 0, 0)
292        p1.approve(payment_date=some_datetime)
293        assert p1.payment_date == some_datetime
294
295    def test_approve_datetime_automatic(self):
296        # if we do not give a datetime, current one will be used
297        current = datetime.datetime.utcnow()
298        p1 = Payment(self.payer, self.payable)
299        p1.approve()
300        assert p1.payment_date >= current
301
302    def test_mark_failed(self):
303        # we can mark payments as failed
304        p1 = Payment(self.payer, self.payable)
305        p1.mark_failed()
306        assert p1.state == STATE_FAILED
307
308    def test_amount(self):
309        # the amount of a payment is the sum of amounts of its items
310        payable = self.payable
311        payable.payment_items[0].amount = decimal.Decimal("12.25")
312        payable.payment_items[1].amount = decimal.Decimal("0.5")
313        p1 = Payment(self.payer, self.payable)
314        assert p1.amount == decimal.Decimal("12.75")
315
316    def test_amount_negative(self):
317        # we can sum up negative numbers
318        payable = self.payable
319        payable.payment_items[0].amount = decimal.Decimal("2.21")
320        payable.payment_items[1].amount = decimal.Decimal("-3.23")
321        p1 = Payment(self.payer, payable)
322        assert p1.amount == decimal.Decimal("-1.02")
323
324    def test_amount_empty(self):
325        # the amount of zero items is None.
326        payable = FakePayable(payment_items=())
327        p1 = Payment(self.payer, payable)
328        self.assertEqual(p1.amount, decimal.Decimal("0.00"))
329
330
331class PaymentItemTests(unittest.TestCase):
332
333    def test_iface(self):
334        # PaymentItems fullfill any interface contracts
335        obj = PaymentItem()
336        verifyClass(IPaymentItem, PaymentItem)
337        verifyObject(IPaymentItem, obj)
Note: See TracBrowser for help on using the repository browser.