source: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal.py @ 17397

Last change on this file since 17397 was 12741, checked in by uli, 10 years ago

Merge changes from uli-payments back into trunk.

  • Property svn:keywords set to Id
File size: 40.7 KB
Line 
1## $Id: test_paypal.py 12741 2015-03-12 05:29:43Z uli $
2##
3## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19import decimal
20import os
21import re
22import shutil
23import tempfile
24import unittest
25import paypalrestsdk
26from zope.component import (
27    getGlobalSiteManager, queryUtility,
28    )
29from zope.component.hooks import setSite, clearSite
30from zope.i18nmessageid.message import Message as i18nMessage
31from zope.interface import implements
32from zope.interface.verify import verifyObject, verifyClass
33from zope.schema.interfaces import IVocabularyTokenized, ITerm
34from waeup.ikoba.interfaces import IPayPalConfig
35from waeup.ikoba.payments.interfaces import (
36    IPaymentGatewayService, IPayer, IPayee, IPaymentItem,
37    )
38from waeup.ikoba.payments.paypal import (
39    get_paypal_config_file_path, parse_paypal_config, get_access_token,
40    configure_sdk, get_payment, Payer, PayerInfo, ShippingAddress,
41    ADDRESS_TYPES_VOCAB, IShippingAddress, Address, IAddress, to_dict,
42    CreditCard, CREDIT_CARD_TYPES_VOCAB, CREDIT_CARD_STATUS_VOCAB,
43    ICreditCard, ICreditCardToken, CreditCardToken, FundingInstrument,
44    AmountDetails, IAmount, Amount, IItem, Item, STOCK_KEEPING_UNITS_VOCAB,
45    IItemList, ItemList, IPaymentOptions, PaymentOptions, ITransaction,
46    Transaction, PAYMENT_OPTION_METHODS_VOCAB, PayPalCreditCardService,
47    PayPalRegularPaymentService,
48    )
49from waeup.ikoba.testing import (
50    FunctionalLayer, FunctionalTestCase,
51    )
52
53
54#
55# PayPal test config
56#
57
58EXTERNAL_PAYPAL_TESTS = False
59
60#
61# End of PayPal test config
62#
63
64
65def external_paypal_test(func):
66    """A decorator that can block test functions.
67    """
68    if not EXTERNAL_PAYPAL_TESTS:
69        myself = __file__
70        if myself.endswith('.pyc'):
71            myself = myself[:-2]
72        print "WARNING: external paypal tests are skipped!"
73        print "WARNING: edit %s to enable them." % myself
74        return
75    return func
76
77
78class HelperTests(unittest.TestCase):
79
80    def setUp(self):
81        self.workdir = tempfile.mkdtemp()
82
83    def tearDown(self):
84        shutil.rmtree(self.workdir)
85        # unregister any remaining utils registered during tests
86        util = queryUtility(IPayPalConfig)
87        if util is not None:
88            getGlobalSiteManager().unregisterUtility(util, IPayPalConfig)
89
90    def test_get_paypal_config_file_path(self):
91        # we can get a config file path (if registered)
92        path = os.path.join(self.workdir, 'sample.cfg')
93        getGlobalSiteManager().registerUtility(
94            {'path': path, }, IPayPalConfig, '')
95        result = get_paypal_config_file_path()
96        assert result == path
97
98    def test_get_paypal_config_file_path_no_file(self):
99        # without IPayPalConfig util set, we get `None`
100        result = get_paypal_config_file_path()
101        assert result is None
102
103    def test_parse_paypal_config(self):
104        # we can parse paypal configs
105        path = os.path.join(self.workdir, 'sample.cfg')
106        open(path, 'w').write(
107            "[rest-client]\n"
108            "id = myid\n"
109            "secret = mysecret\n"
110            "mode = live\n")
111        result = parse_paypal_config(path)
112        assert result['client_id'] == u'myid'
113        assert result['client_secret'] == u'mysecret'
114        assert result['mode'] == 'live'
115
116    def test_parse_paypal_config_defaults(self):
117        # we have fallback values (defaults) in place
118        path = os.path.join(self.workdir, 'sample.cfg')
119        open(path, 'w').write(
120            "[rest-client]\n"
121            )
122        result = parse_paypal_config(path)
123        assert result['client_id'] is None
124        assert result['client_secret'] is None
125        assert result['mode'] == "sandbox"
126
127    def test_configure_sdk(self):
128        # we can configure the paypal sdk
129        path = os.path.join(self.workdir, 'sample.cfg')
130        open(path, 'w').write(
131            "[rest-client]\n"
132            "id = my-special-id\n"
133            )
134        result = configure_sdk(path)
135        assert result['client_id'] == 'my-special-id'
136        assert result['client_secret'] is None
137        assert result['mode'] == "sandbox"
138        assert paypalrestsdk.api.__api__.mode == 'sandbox'
139        assert paypalrestsdk.api.__api__.client_id == 'my-special-id'
140
141    def test_get_payment_invalid_intent(self):
142        # only 'sale' is currently allowed
143        self.assertRaises(
144            ValueError, get_payment, intent='invalid')
145        self.assertRaises(
146            ValueError, get_payment, intent='order')
147
148    def test_to_dict(self):
149        # we can turn objects into dicts
150        class C1(object):
151            a = 1
152            b = "somestring"
153            c = u"unicodestring"
154            d = None
155        obj = C1()
156        self.assertEqual(
157            to_dict(obj),
158            {'a': u"1",
159             'b': u"somestring",
160             'c': u"unicodestring",
161             }
162            )
163
164    def test_to_dict_map(self):
165        # we can map attribute names
166        class C1(object):
167            a = 1
168            b = "somestring"
169        obj = C1()
170        self.assertEqual(
171            to_dict(obj, name_map={'a': 'replaced_a'}),
172            {'replaced_a': u"1",
173             'b': u"somestring",
174             })
175
176    def test_to_dict_lists(self):
177        # to_dict can handle lists
178        class C1(object):
179            a = 1
180
181            def to_dict(self):
182                return to_dict(self)
183
184        obj1 = C1()
185        obj2 = C1()
186        obj2.a = 2
187        obj3 = C1()
188        obj3.a = 3
189        obj1.foo = [obj2, obj3]
190        self.assertEqual(
191            to_dict(obj1),
192            {
193                'a': u'1',
194                'foo': [
195                    {'a': u'2'},
196                    {'a': u'3'},
197                    ]
198                }
199            )
200
201    def test_to_dict_decimals(self):
202        # decimals are converted to numbers with 2 decimals
203        class C1(object):
204            a = decimal.Decimal("0.1")
205        self.assertEqual(
206            to_dict(C1()),
207            {
208                'a': u"0.10",
209                }
210            )
211
212
213class PayerTests(unittest.TestCase):
214
215    def test_create(self):
216        # we can create payer objects
217        payer = Payer(payment_method='paypal')
218        assert payer.payment_method == 'paypal'
219        assert payer.funding_instruments == []
220        assert payer.payer_info is None
221        assert payer.status is None
222
223    def test_init_invalid_payment_meth(self):
224        # we must provide a valid payment method
225        payer = Payer(payment_method='paypal')
226        assert payer is not None
227        payer = Payer(payment_method='credit_card')
228        assert payer is not None
229        self.assertRaises(
230            ValueError, Payer, payment_method='invalid')
231
232    def test_init_invalid_payer_state(self):
233        # only certain values are allowed as payer states
234        payer = Payer(payment_method='paypal', status='VERIFIED')
235        assert payer is not None
236        payer = Payer(payment_method='paypal', status='UNVERIFIED')
237        assert payer is not None
238        self.assertRaises(
239            ValueError, Payer, payment_method='paypal', status='InVaLiD')
240
241
242class PayerInfoTests(unittest.TestCase):
243
244    def test_create(self):
245        # we can create payer infos
246        info = PayerInfo()
247        assert info.email == ''
248        assert info.first_name == ''
249        assert info.last_name == ''
250        assert info.payer_id == ''
251        assert info.phone == ''
252        assert info.shipping_address is None
253        assert info.tax_id_type == ''
254        assert info.tax_id == ''
255
256    def test_init_invalid_tax_id_type(self):
257        # onyl certain tax id types are allowed
258        info = PayerInfo(tax_id_type='BR_CPF')
259        assert info is not None
260        info = PayerInfo(tax_id_type='BR_CNPJ')
261        assert info is not None
262        self.assertRaises(
263            ValueError, PayerInfo, tax_id_type='INVALID_TYPE')
264
265
266class CreditCardTests(unittest.TestCase):
267
268    def test_iface(self):
269        # we fullfill any interface contracts
270        credit_card = CreditCard(
271            number=u"12345678",
272            credit_card_type="visa",
273            expire_month=4,
274            expire_year=2012,
275            )
276        verifyClass(ICreditCard, CreditCard)
277        verifyObject(ICreditCard, credit_card)
278
279    def test_create(self):
280        # we can create CreditCard objects
281        credit_card = CreditCard(
282            number=u"12345678",
283            credit_card_type="visa",
284            expire_month=4,
285            expire_year=2012,
286            )
287        assert credit_card.paypal_id is None
288        assert credit_card.external_customer_id is not None
289        assert credit_card.number == u"12345678"
290        assert credit_card.credit_card_type == "visa"
291        assert credit_card.expire_month == 4
292        assert credit_card.expire_year == 2012
293        assert credit_card.cvv2 is None
294        assert credit_card.first_name is None
295        assert credit_card.last_name is None
296        assert credit_card.billing_address is None
297        assert credit_card.state is None
298        assert credit_card.paypal_valid_until is None
299
300    def test_external_customer_id_given(self):
301        # we do not override given curstomer ids
302        credit_card = CreditCard(
303            number=u"12345678",
304            credit_card_type="visa",
305            expire_month=4,
306            expire_year=2012,
307            external_customer_id=u'MySpecialPayerId',
308            )
309        assert credit_card.external_customer_id == u'MySpecialPayerId'
310
311    def test_external_customer_id_not_given(self):
312        # in case no customer id is given, we generate one
313        credit_card = CreditCard(
314            number=u"12345678",
315            credit_card_type="visa",
316            expire_month=4,
317            expire_year=2012,
318            )
319        # our customer ids contain a leading 'PAYER_' and 32 hex digits
320        assert re.match(
321            'PAYER_[0-9a-f]{32}$', credit_card.external_customer_id)
322
323    def test_number_is_checked(self):
324        # we do not accept invalid numbers
325        self.assertRaises(
326            ValueError, CreditCard,
327            number=u"not-a-number",
328            credit_card_type="visa",
329            expire_month=4,
330            expire_year=2012,
331            )
332
333    def test_to_str(self):
334        # we can turn CreditCard objects into dicts.
335        addr = Address(
336            line1=u"52 N Main ST",
337            city=u"Johnstown",
338            state=u"OH",
339            postal_code=u"43210",
340            country_code=u"US")
341        credit_card = CreditCard(
342            credit_card_type=u"visa",
343            external_customer_id=u"PAYER_0123456789012345678901",
344            number=u"4417119669820331",
345            expire_month=11,
346            expire_year=2018,
347            cvv2=u"874",
348            first_name=u"Joe",
349            last_name=u"Shopper",
350            billing_address=addr)
351        self.assertEqual(
352            credit_card.to_dict(),
353            {
354                "type": u"visa",
355                "number": u"4417119669820331",
356                "external_customer_id": u"PAYER_0123456789012345678901",
357                "expire_month": u"11",
358                "expire_year": u"2018",
359                "cvv2": u"874",
360                "first_name": u"Joe",
361                "last_name": u"Shopper",
362                "billing_address": {
363                    "line1": u"52 N Main ST",
364                    "city": u"Johnstown",
365                    "state": u"OH",
366                    "postal_code": u"43210",
367                    "country_code": u"US"}
368                }
369            )
370
371
372class CreditCardTokenTests(unittest.TestCase):
373
374    def test_iface(self):
375        # we fullfill any interface contracts
376        token = CreditCardToken(
377            credit_card_id=u"12345678",
378            )
379        verifyClass(ICreditCardToken, CreditCardToken)
380        verifyObject(ICreditCardToken, token)
381
382    def test_create(self):
383        # we can create CreditCardToken objects
384        token = CreditCardToken(
385            credit_card_id=u"12345678",
386            )
387        assert token.credit_card_id == u"12345678"
388        assert token.external_customer_id is None
389        assert token.credit_card_type is None
390        assert token.expire_month is None
391        assert token.expire_year is None
392        assert token.last4 is None
393
394    def test_customer_id_given(self):
395        # we do not override given customer ids
396        token = CreditCardToken(
397            credit_card_id=u"12345678",
398            external_customer_id=u'MySpecialPayerId',
399            )
400        assert token.external_customer_id == u'MySpecialPayerId'
401
402    def test_to_str(self):
403        # we can turn CreditCardToken objects into dicts.
404        token = CreditCardToken(
405            credit_card_type=u"visa",
406            external_customer_id=u"PAYER_0123456789012345678901",
407            credit_card_id=u"12345678",
408            last4="8901",
409            expire_month=11,
410            expire_year=2018,
411            )
412        self.assertEqual(
413            token.to_dict(),
414            {
415                "credit_card_id": u"12345678",
416                "external_customer_id": u"PAYER_0123456789012345678901",
417                "last4": u"8901",
418                "type": u"visa",
419                "expire_month": u"11",
420                "expire_year": u"2018",
421                }
422            )
423
424
425class FundingInstrumentTests(unittest.TestCase):
426
427    def test_create(self):
428        # we can create FundingInstrument objects
429        token = CreditCardToken(
430            credit_card_id=u"12345678",
431            )
432        instr = FundingInstrument(credit_card_token=token)
433        assert instr.credit_card_token is token
434
435    def test_require_credit_card_or_token(self):
436        # we require a credit card object or a token.
437        credit_card = CreditCard(
438            number=u"12345678",
439            credit_card_type="visa",
440            expire_month=4,
441            expire_year=2012,
442            )
443        token = CreditCardToken(
444            credit_card_id=u"12345678",
445            )
446        self.assertRaises(
447            ValueError, FundingInstrument,
448            credit_card=credit_card,
449            credit_card_token=token
450            )
451        self.assertRaises(
452            ValueError, FundingInstrument
453            )
454
455    def test_to_dict(self):
456        # we can turn Funding instruments into dicts
457        token = CreditCardToken(
458            credit_card_type=u"visa",
459            external_customer_id=u"PAYER_0123456789012345678901",
460            credit_card_id=u"12345678",
461            last4="8901",
462            expire_month=11,
463            expire_year=2018,
464            )
465        instr = FundingInstrument(credit_card_token=token)
466        result = instr.to_dict()
467        self.assertEqual(
468            result,
469            {
470                "credit_card_token": {
471                    "credit_card_id": u"12345678",
472                    "external_customer_id": u"PAYER_0123456789012345678901",
473                    "last4": u"8901",
474                    "type": u"visa",
475                    "expire_month": u"11",
476                    "expire_year": u"2018",
477                    }
478                }
479            )
480
481
482class AddressTests(unittest.TestCase):
483
484    def test_iface(self):
485        # we fullfill any interface contracts
486        addr = Address(
487            line1=u'Address Line 1',
488            city=u'Somecity',
489            country_code=u'AT',
490            )
491        verifyClass(IAddress, Address)
492        verifyObject(IAddress, addr)
493
494    def test_create(self):
495        # we can create addresses
496        addr = Address(
497            line1=u'Honey Street 1',
498            city=u'Beartown',
499            country_code=u'GB',
500            )
501        assert addr.line1 == u'Honey Street 1'
502        assert addr.line2 is None
503        assert addr.city == u'Beartown'
504        assert addr.country_code == u'GB'
505        assert addr.postal_code is None
506        assert addr.state is None
507        assert addr.phone is None
508
509    def test_to_dict(self):
510        # we can turn addresses into dicts
511        addr = Address(
512            line1=u'Honey Street 1',
513            city=u'Beartown',
514            country_code=u'GB',
515            )
516        self.assertEqual(
517            addr.to_dict(),
518            {
519                'line1': u'Honey Street 1',
520                'city': u'Beartown',
521                'country_code': u'GB',
522                }
523            )
524        addr.line2 = u"Behind little tree"
525        self.assertEqual(
526            addr.to_dict(),
527            {
528                'line1': u'Honey Street 1',
529                'line2': u'Behind little tree',
530                'city': u'Beartown',
531                'country_code': u'GB',
532                }
533            )
534
535
536class ShippingAddressTests(unittest.TestCase):
537
538    def test_iface(self):
539        # we fullfill any interface contracts
540        addr = ShippingAddress(
541            recipient_name=u'Foo Bar',
542            line1=u'Address Line 1',
543            city=u'Somecity',
544            country_code=u'AT',
545            )
546        verifyClass(IShippingAddress, ShippingAddress)
547        verifyObject(IShippingAddress, addr)
548
549    def test_create(self):
550        # we can create shipping addresses
551        addr = ShippingAddress(
552            recipient_name=u'Rob Receiver',
553            line1=u'Honey Street 1',
554            city=u'Beartown',
555            country_code=u'GB',
556            )
557        assert addr.recipient_name == u'Rob Receiver'
558        assert addr.type == u'residential'
559        assert addr.line1 == u'Honey Street 1'
560        assert addr.line2 is None
561        assert addr.city == u'Beartown'
562        assert addr.country_code == u'GB'
563        assert addr.postal_code is None
564        assert addr.state is None
565        assert addr.phone is None
566
567    def test_to_dict(self):
568        # we can turn shipping addresses into dicts
569        addr = ShippingAddress(
570            recipient_name=u'Rob Receiver',
571            line1=u'Honey Street 1',
572            city=u'Beartown',
573            country_code=u'GB',
574            )
575        self.assertEqual(
576            addr.to_dict(),
577            {
578                'recipient_name': u'Rob Receiver',
579                'type': u'residential',
580                'line1': u'Honey Street 1',
581                'city': u'Beartown',
582                'country_code': u'GB',
583                }
584            )
585        addr.line2 = u"Behind little tree"
586        self.assertEqual(
587            addr.to_dict(),
588            {
589                'recipient_name': u'Rob Receiver',
590                'type': u'residential',
591                'line1': u'Honey Street 1',
592                'line2': u'Behind little tree',
593                'city': u'Beartown',
594                'country_code': u'GB',
595                }
596            )
597
598
599class AmountDetailsTests(unittest.TestCase):
600
601    def test_create(self):
602        # we can create AmountDetail objects
603        details = AmountDetails()
604        assert details.shipping is None
605        assert details.subtotal is None
606        assert details.tax is None
607        assert details.fee is None
608        assert details.handling_fee is None
609        assert details.insurance is None
610        assert details.shipping_discount is None
611
612    def test_to_dict(self):
613        # we can turn AmountDetails into a dict
614        details = AmountDetails(
615            shipping=decimal.Decimal("0.10"),
616            tax=decimal.Decimal("0.30"),
617            fee=decimal.Decimal("0.40"),
618            handling_fee=decimal.Decimal("0.50"),
619            insurance=decimal.Decimal("0.60"),
620            shipping_discount=decimal.Decimal("0.70")
621            )
622        self.assertEqual(
623            details.to_dict(),
624            {
625                'shipping': u"0.10",
626                'subtotal': u"1.20",
627                'tax': u"0.30",
628                'fee': u"0.40",
629                'handling_fee': u"0.50",
630                'insurance': u"0.60",
631                'shipping_discount': u"0.70"
632                }
633            )
634
635    def test_subtotal_all_none(self):
636        # if all items are none, also subtotal is none
637        details = AmountDetails(
638            shipping=None, tax=None, fee=None, handling_fee=None,
639            insurance=None, shipping_discount=None,
640            )
641        assert details.subtotal is None
642        details.shipping_discount = decimal.Decimal("1.00")
643        assert details.subtotal == decimal.Decimal("-1.00")
644
645    def test_subtotal_sum(self):
646        # subtotal sums up correctly
647        details = AmountDetails(
648            shipping=decimal.Decimal("0.05"),
649            tax=decimal.Decimal("0.40"),
650            fee=decimal.Decimal("3.00"),
651            handling_fee=decimal.Decimal("20.00"),
652            insurance=decimal.Decimal("100.00"),
653            shipping_discount=None
654            )
655        self.assertEqual(details.subtotal, decimal.Decimal("123.45"))
656        details.shipping_discount = decimal.Decimal("0.00")
657        self.assertEqual(details.subtotal, decimal.Decimal("123.45"))
658        details.shipping_discount = decimal.Decimal("23.45")
659        self.assertEqual(details.subtotal, decimal.Decimal("100.00"))
660
661
662class AmountTests(unittest.TestCase):
663
664    def test_iface(self):
665        # we fullfill any interface contracts.
666        amount = Amount()
667        verifyClass(IAmount, Amount)
668        verifyObject(IAmount, amount)
669
670    def test_create(self):
671        # we can create amount objects
672        details = AmountDetails(
673            shipping=decimal.Decimal("0.05"),
674            tax=decimal.Decimal("0.40"),
675            fee=decimal.Decimal("3.00"),
676            handling_fee=decimal.Decimal("20.00"),
677            insurance=decimal.Decimal("100.00"),
678            shipping_discount=None
679            )
680        amount = Amount(
681            total=decimal.Decimal("12.12"),
682            currency="USD",
683            details=details
684            )
685        assert amount.total == decimal.Decimal("12.12")
686        assert amount.currency == "USD"
687        assert amount.details is details
688
689    def test_to_dict(self):
690        # we can turn Amount objects into dicts
691        self.maxDiff = None
692        details = AmountDetails(
693            shipping=decimal.Decimal("0.05"),
694            tax=decimal.Decimal("0.40"),
695            fee=decimal.Decimal("3.00"),
696            handling_fee=decimal.Decimal("20.00"),
697            insurance=decimal.Decimal("100.00"),
698            shipping_discount=None
699            )
700        amount = Amount(
701            total=decimal.Decimal("12.12"),
702            currency="USD",
703            details=details
704            )
705        self.assertEqual(
706            amount.to_dict(),
707            {
708                'total': u'12.12',
709                'currency': u'USD',
710                'details': {
711                    'shipping': u'0.05',
712                    'subtotal': u'123.45',
713                    'tax': u'0.40',
714                    'fee': u'3.00',
715                    'handling_fee': u'20.00',
716                    'insurance': u'100.00',
717                    }
718                }
719            )
720
721
722class ItemTests(unittest.TestCase):
723
724    def test_iface(self):
725        # we fullfill all interface contracts
726        item = Item(name=u"Splendid Item")
727        verifyClass(IItem, Item)
728        verifyObject(IItem, item)
729
730    def test_create(self):
731        # we can create Item objects
732        item = Item(
733            quantity=3,
734            name=u"Splendid Thing",
735            price=decimal.Decimal("1.1"),
736            currency="USD",
737            sku="pcs",
738            description=u"Soo splendid!",
739            tax=decimal.Decimal("0.1"),
740            )
741        assert item.quantity == 3
742        assert item.name == u"Splendid Thing"
743        assert item.price == decimal.Decimal("1.1")
744        assert item.currency == "USD"
745        assert item.sku == "pcs"
746        assert item.description == u"Soo splendid!"
747        assert item.tax == decimal.Decimal("0.1")
748
749    def test_to_dict(self):
750        # we can turn Item objects into dicts
751        item = Item(
752            quantity=3,
753            name=u"Splendid Thing",
754            price=decimal.Decimal("1.1"),
755            currency="USD",
756            sku="pcs",
757            description=u"Soo splendid!",
758            tax=decimal.Decimal("0.1"),
759            )
760        self.assertEqual(
761            item.to_dict(),
762            {
763                "quantity": u"3",
764                "name": u"Splendid Thing",
765                "price": u"1.10",
766                "currency": u"USD",
767                "sku": u"pcs",
768                "description": u"Soo splendid!",
769                "tax": u"0.10"
770                }
771            )
772
773
774class ItemListTests(unittest.TestCase):
775
776    def test_iface(self):
777        # we fullfill all interface contracts
778        item_list = ItemList()
779        verifyClass(IItemList, ItemList)
780        verifyObject(IItemList, item_list)
781
782    def test_create_minimal(self):
783        # we can create ItemLists with a minimum of params
784        item_list = ItemList()
785        assert item_list.shipping_address is None
786        assert item_list.items == []
787
788    def test_create(self):
789        # we can create ItemLists
790        item1 = Item(
791            name=u"Splendid Thing",
792            )
793        item2 = Item(
794            name=u"Other Splendid Thing",
795            )
796        addr = ShippingAddress(
797            recipient_name=u'Rob Receiver',
798            line1=u'Honey Street 1',
799            city=u'Beartown',
800            country_code=u'GB',
801            )
802        item_list = ItemList(
803            shipping_address=addr,
804            items=[item1, item2])
805        assert item_list.shipping_address is addr
806        assert item_list.items == [item1, item2]
807
808    def test_to_dict(self):
809        # we can turn ITemLists into dicts
810        item = Item(
811            quantity=3,
812            name=u"Splendid Thing",
813            price=decimal.Decimal("1.1"),
814            currency="USD",
815            sku="pcs",
816            description=u"Soo splendid!",
817            tax=decimal.Decimal("0.1"),
818            )
819        addr = ShippingAddress(
820            recipient_name=u'Rob Receiver',
821            line1=u'Honey Street 1',
822            city=u'Beartown',
823            country_code=u'GB',
824            )
825        item_list = ItemList(items=[item, ], shipping_address=addr)
826        self.assertEqual(
827            item_list.to_dict(),
828            {
829                "items": [
830                    {
831                        "quantity": u"3",
832                        "name": u"Splendid Thing",
833                        "price": u"1.10",
834                        "currency": u"USD",
835                        "sku": u"pcs",
836                        "description": u"Soo splendid!",
837                        "tax": u"0.10"
838                        }
839                    ],
840                "shipping_address":
841                {
842                    'recipient_name': u'Rob Receiver',
843                    'type': u'residential',
844                    'line1': u'Honey Street 1',
845                    'city': u'Beartown',
846                    'country_code': u'GB',
847                    }
848                }
849            )
850
851
852class PaymentOptionsTests(unittest.TestCase):
853
854    def test_iface(self):
855        # we fullfill all interface contracts
856        opts = PaymentOptions()
857        verifyClass(IPaymentOptions, PaymentOptions)
858        verifyObject(IPaymentOptions, opts)
859
860    def test_create(self):
861        # we can create PaymentOptions objects
862        opts = PaymentOptions()
863        assert opts.allowed_payment_method is None
864
865    def test_allowed_payment_method_checked_in_init(self):
866        # any value apart from None, INSTANT... is rejected in __init__
867        self.assertRaises(
868            ValueError,
869            PaymentOptions, allowed_payment_method='NoTvAlID')
870
871    def test_to_dict(self):
872        # we can turn PaymentOptions into dicts
873        opts = PaymentOptions(
874            allowed_payment_method="INSTANT_FUNDING_SOURCE")
875        self.assertEqual(
876            opts.to_dict(),
877            {
878                'allowed_payment_method': "INSTANT_FUNDING_SOURCE",
879                }
880            )
881
882
883class TransactionTests(unittest.TestCase):
884
885    def test_iface(self):
886        # we fullfill all interface contracts
887        amount = Amount()
888        transaction = Transaction(amount=amount)
889        verifyClass(ITransaction, Transaction)
890        verifyObject(ITransaction, transaction)
891
892    def test_create(self):
893        # we can create transacions
894        amount = Amount()
895        transaction = Transaction(amount=amount)
896        assert transaction.amount is amount
897        assert transaction.description is None
898        assert transaction.item_list is None
899        assert transaction.related_resources == []
900        assert transaction.invoice_number is None
901        assert transaction.custom is None
902        assert transaction.soft_descriptor is None
903        assert transaction.payment_options is None
904
905    def test_to_dict(self):
906        # we can turn Transaction objects into dicts
907        transaction = Transaction(
908            amount=Amount(),
909            description=u"My description",
910            item_list=ItemList(),
911            related_resources=[],
912            invoice_number=u"12345",
913            custom=u"Some custom remark",
914            soft_descriptor=u"softdescriptor?",
915            payment_options=PaymentOptions(
916                allowed_payment_method="INSTANT_FUNDING_SOURCE"),
917            )
918        self.assertEqual(
919            transaction.to_dict(), {
920                'amount': {'currency': u'USD', 'total': u'0.00'},
921                'custom': u'Some custom remark',
922                'description': u'My description',
923                'invoice_number': u'12345',
924                'item_list': {
925                    'items': []
926                    },
927                'payment_options': {
928                    'allowed_payment_method': u'INSTANT_FUNDING_SOURCE'
929                    },
930                'related_resources': [],
931                'soft_descriptor': u'softdescriptor?'
932                }
933            )
934
935
936class AddressTypesVocabTests(unittest.TestCase):
937
938    def test_address_types_vocab_tokenized(self):
939        # we can get a countries source suitable for forms etc.
940        verifyObject(IVocabularyTokenized, ADDRESS_TYPES_VOCAB)
941
942    def test_address_types_vocab_i18nized(self):
943        # vocab titles are i18nized
944        result = ADDRESS_TYPES_VOCAB.getTerm('residential')
945        assert ITerm.providedBy(result)
946        self.assertEqual(result.title, u'residential')
947        assert isinstance(result.title, i18nMessage)
948
949    def test_address_types_vocab_tokens_are_string(self):
950        # vocab tokens are simple strings
951        result = ADDRESS_TYPES_VOCAB.getTerm('residential')
952        assert ITerm.providedBy(result)
953        assert result.token == result.value
954        assert result.value == 'residential'
955        assert isinstance(result.token, str)
956        assert isinstance(result.value, str)
957
958
959class CreditCardTypesVocabTests(unittest.TestCase):
960
961    def test_credit_card_types_vocab_tokenized(self):
962        # we can get a countries source suitable for forms etc.
963        verifyObject(IVocabularyTokenized, CREDIT_CARD_TYPES_VOCAB)
964
965    def test_credit_cards_types_vocab_i18nized(self):
966        # vocab titles are i18nized
967        result = CREDIT_CARD_TYPES_VOCAB.getTerm('visa')
968        assert ITerm.providedBy(result)
969        self.assertEqual(result.title, u'visa')
970        assert isinstance(result.title, i18nMessage)
971
972    def test_credit_cards_types_vocab_tokens_are_string(self):
973        # vocab tokens are simple strings
974        result = CREDIT_CARD_TYPES_VOCAB.getTerm('visa')
975        assert ITerm.providedBy(result)
976        assert result.token == result.value
977        assert result.value == 'visa'
978        assert isinstance(result.token, str)
979        assert isinstance(result.value, str)
980
981
982class CreditcardstatusVocabTests(unittest.TestCase):
983
984    def test_credit_card_status_vocab_tokenized(self):
985        # we can get a countries source suitable for forms etc.
986        verifyObject(IVocabularyTokenized, CREDIT_CARD_STATUS_VOCAB)
987
988    def test_credit_cards_status_vocab_i18nized(self):
989        # vocab titles are i18nized
990        result = CREDIT_CARD_STATUS_VOCAB.getTerm('ok')
991        assert ITerm.providedBy(result)
992        self.assertEqual(result.title, u'ok')
993        assert isinstance(result.title, i18nMessage)
994
995    def test_credit_cards_status_vocab_tokens_are_string(self):
996        # vocab tokens are simple strings
997        result = CREDIT_CARD_STATUS_VOCAB.getTerm('expired')
998        assert ITerm.providedBy(result)
999        assert result.token == result.value
1000        assert result.value == 'expired'
1001        assert isinstance(result.token, str)
1002        assert isinstance(result.value, str)
1003
1004
1005class StockKeepingUnitsVocabTests(unittest.TestCase):
1006
1007    def test_sku_vocab_tokenized(self):
1008        # we can get a tokenzed vocab for stock keeping units
1009        verifyObject(IVocabularyTokenized, STOCK_KEEPING_UNITS_VOCAB)
1010
1011    def test_sku_vocab_i18nized(self):
1012        # vocab titles are i18nized
1013        result = STOCK_KEEPING_UNITS_VOCAB.getTerm('pcs')
1014        assert ITerm.providedBy(result)
1015        self.assertEqual(result.title, u'pieces')
1016        assert isinstance(result.title, i18nMessage)
1017
1018    def test_sku_vocab_tokens_are_string(self):
1019        # vocab tokens are simple strings
1020        result = STOCK_KEEPING_UNITS_VOCAB.getTerm('pcs')
1021        assert ITerm.providedBy(result)
1022        assert result.token == result.value
1023        assert result.value == 'pcs'
1024        assert isinstance(result.token, str)
1025        assert isinstance(result.value, str)
1026
1027
1028class PaymentOptionMethodsVocabTests(unittest.TestCase):
1029
1030    def test_payment_option_methods_vocab_tokenized(self):
1031        # we can get a countries source suitable for forms etc.
1032        verifyObject(IVocabularyTokenized, PAYMENT_OPTION_METHODS_VOCAB)
1033
1034    def test_payment_option_methods_vocab_i18nized(self):
1035        # vocab titles are i18nized
1036        result = PAYMENT_OPTION_METHODS_VOCAB.getTerm('INSTANT_FUNDING_SOURCE')
1037        assert ITerm.providedBy(result)
1038        self.assertEqual(result.title, u'INSTANT_FUNDING_SOURCE')
1039        assert isinstance(result.title, i18nMessage)
1040
1041    def test_payment_option_methods_vocab_tokens_are_string(self):
1042        # vocab tokens are simple strings
1043        result = PAYMENT_OPTION_METHODS_VOCAB.getTerm('INSTANT_FUNDING_SOURCE')
1044        assert ITerm.providedBy(result)
1045        assert result.token == result.value
1046        assert result.value == 'INSTANT_FUNDING_SOURCE'
1047        assert isinstance(result.token, str)
1048        assert isinstance(result.value, str)
1049
1050
1051class FakePayer(object):
1052
1053    implements(IPayer)
1054
1055    payer_id = 'PAYER-123'
1056    first_name = u'Joe'
1057    last_name = u'Shopper'
1058
1059
1060class FakePayee(object):
1061
1062    implements(IPayee)
1063
1064    payee_id = 'PAYEE-456'
1065
1066
1067class FakePaymentItem(object):
1068
1069    implements(IPaymentItem)
1070
1071    title = u'Fake Item'
1072    item_id = 'BILL-123456'
1073    amount = decimal.Decimal("12.10")
1074    currency = 'EUR'
1075
1076
1077# Make sure all fake classes (and objects made of them) are up-to-date
1078verifyClass(IPayer, FakePayer)
1079verifyObject(IPayer, FakePayer())
1080verifyClass(IPayee, FakePayee)
1081verifyObject(IPayee, FakePayee())
1082verifyClass(IPaymentItem, FakePaymentItem)
1083verifyObject(IPaymentItem, FakePaymentItem())
1084
1085
1086class PayPalCreditCardServiceTests(FunctionalTestCase):
1087
1088    layer = FunctionalLayer
1089
1090    def tearDown(self):
1091        super(PayPalCreditCardServiceTests, self).tearDown()
1092        clearSite()
1093
1094    def create_site(self):
1095        # create a simple site in root.
1096        class MyApp(grok.Application, grok.Container):
1097            pass
1098        site = MyApp()
1099        self.getRootFolder()['app'] = site
1100        setSite(site)
1101        return site
1102
1103    def get_credit_card(self):
1104        # get a valid credit card
1105        credit_card = CreditCard(
1106            credit_card_type=u"visa",
1107            external_customer_id=u"PAYER_0123456789012345678901",
1108            number=u"4417119669820331",
1109            expire_month=11,
1110            expire_year=2018,
1111            cvv2=u"874",
1112            first_name=u"Joe",
1113            last_name=u"Shopper",
1114            )
1115        return credit_card
1116
1117    def test_iface(self):
1118        # we fullfill all interface contracts
1119        service = PayPalCreditCardService()
1120        verifyClass(IPaymentGatewayService, PayPalCreditCardService)
1121        verifyObject(IPaymentGatewayService, service)
1122
1123    def test_get_credit_card_no_site(self):
1124        # we get simply None if no site is registered
1125        service = PayPalCreditCardService()
1126        assert service.get_credit_card('not-a-valid-payer-id') is None
1127
1128    def test_get_credit_card_site_not_a_container(self):
1129        # we get simply None if site is not a container
1130        site = grok.Application()  # does not provide IContainer
1131        self.getRootFolder()['app'] = site
1132        setSite(site)
1133        service = PayPalCreditCardService()
1134        assert service.get_credit_card('not-a-valid-payer-id') is None
1135
1136    def test_get_credit_card_no_container(self):
1137        # we get simply None if no 'creditcards' container is in site
1138        self.create_site()
1139        service = PayPalCreditCardService()
1140        assert service.get_credit_card('not-a-valid-payer-id') is None
1141
1142    def test_get_credit_card(self):
1143        # we can get a credit card, if one was stored
1144        class MyCard(object):
1145            myid = 'CARD1'
1146
1147            def __eq__(self, obj):
1148                return self.myid == obj.myid
1149
1150        site = self.create_site()
1151        site['creditcards'] = grok.Container()
1152        card = MyCard()
1153        card.myid = 'CHANGED CARD ID'
1154        site['creditcards'][u'CARD1'] = card
1155        service = PayPalCreditCardService()
1156        assert service.get_credit_card(u'CARD1') == card
1157        assert service.get_credit_card(u'CARD2') is None
1158
1159    @external_paypal_test
1160    def test_store_credit_card(self):
1161        # we can (and must) store credit card data online.
1162        site = self.create_site()
1163        assert 'creditcards' not in site
1164        service = PayPalCreditCardService()
1165        credit_card = self.get_credit_card()
1166        result = service.store_credit_card(credit_card)
1167        # a missing creditcards container is created on-the-fly
1168        assert 'creditcards' in site
1169        assert ICreditCardToken.providedBy(result)
1170        assert result.external_customer_id in site['creditcards']
1171        assert site['creditcards'][result.external_customer_id] == result
1172
1173    @external_paypal_test
1174    def test_store_credit_card_invalid(self):
1175        # an exception is raised with invalid credit cards.
1176        self.create_site()
1177        service = PayPalCreditCardService()
1178        credit_card = CreditCard(
1179            number=u"12345678",
1180            credit_card_type="visa",
1181            expire_month=4,
1182            expire_year=2012,
1183            )
1184        self.assertRaises(
1185            IOError, service.store_credit_card, credit_card)
1186
1187    def test_create_payment_no_credictard(self):
1188        # trying to create a payment without credit card raises an exception
1189        service = PayPalCreditCardService()
1190        exception = None
1191        try:
1192            service.create_payment(
1193                payer=FakePayer(),
1194                payment_item=FakePaymentItem(),
1195                payee=FakePayee()
1196                )
1197        except (ValueError, ) as err:
1198            exception = err
1199        assert exception.message == 'Payer PAYER-123 has no credit card.'
1200
1201    @external_paypal_test
1202    def test_create_payment(self):
1203        # we can actually create payments
1204        service = PayPalCreditCardService()
1205        self.create_site()
1206        credit_card = self.get_credit_card()
1207        result = service.store_credit_card(credit_card)
1208        payer = FakePayer()
1209        payer.payer_id = result.external_customer_id
1210        payment_item = FakePaymentItem()
1211        payment = service.create_payment(
1212            payer=payer, payment_item=payment_item)
1213        result = payment.create()
1214        assert result is True
1215
1216
1217class FunctionalPaypalTests(FunctionalTestCase):
1218
1219    layer = FunctionalLayer
1220
1221    @external_paypal_test
1222    def test_get_access_token(self):
1223        # we can get an access token
1224        result = get_access_token()
1225        assert isinstance(result, unicode)
1226
1227    @external_paypal_test
1228    def test_get_payment(self):
1229        # we can construct (paypal-)payment objects
1230        payment = get_payment()
1231        assert isinstance(payment, paypalrestsdk.Payment)
1232        result = payment.create()
1233        assert result is True
1234
1235    def test_paypal_services_registered(self):
1236        # the PayPal gateway services are all registered
1237        creditcard_service = queryUtility(
1238            IPaymentGatewayService, name=u'paypal_creditcard')
1239        assert creditcard_service is not None
1240        assert isinstance(creditcard_service, PayPalCreditCardService)
1241        paypal_regular_service = queryUtility(
1242            IPaymentGatewayService, name=u'paypal_regular')
1243        assert paypal_regular_service is not None
1244        assert isinstance(paypal_regular_service, PayPalRegularPaymentService)
Note: See TracBrowser for help on using the repository browser.