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

Last change on this file 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
RevLine 
[12027]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##
[12325]18import grok
[12311]19import decimal
[12013]20import os
[12311]21import re
[12013]22import shutil
23import tempfile
24import unittest
[12029]25import paypalrestsdk
[12311]26from zope.component import (
[12325]27    getGlobalSiteManager, queryUtility,
[12311]28    )
[12325]29from zope.component.hooks import setSite, clearSite
[12311]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
[12013]34from waeup.ikoba.interfaces import IPayPalConfig
[12311]35from waeup.ikoba.payments.interfaces import (
[12326]36    IPaymentGatewayService, IPayer, IPayee, IPaymentItem,
[12311]37    )
[12013]38from waeup.ikoba.payments.paypal import (
39    get_paypal_config_file_path, parse_paypal_config, get_access_token,
[12311]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,
[12013]48    )
49from waeup.ikoba.testing import (
50    FunctionalLayer, FunctionalTestCase,
51    )
52
[12311]53
[12043]54#
55# PayPal test config
56#
[12013]57
[12043]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
[12013]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
[12029]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'
[12013]140
[12311]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')
[12029]147
[12311]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
[12496]288        assert credit_card.external_customer_id is not None
[12311]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
[12496]300    def test_external_customer_id_given(self):
301        # we do not override given curstomer ids
[12311]302        credit_card = CreditCard(
303            number=u"12345678",
304            credit_card_type="visa",
305            expire_month=4,
306            expire_year=2012,
[12496]307            external_customer_id=u'MySpecialPayerId',
[12311]308            )
[12496]309        assert credit_card.external_customer_id == u'MySpecialPayerId'
[12311]310
[12496]311    def test_external_customer_id_not_given(self):
312        # in case no customer id is given, we generate one
[12311]313        credit_card = CreditCard(
314            number=u"12345678",
315            credit_card_type="visa",
316            expire_month=4,
317            expire_year=2012,
318            )
[12496]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)
[12311]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",
[12496]343            external_customer_id=u"PAYER_0123456789012345678901",
[12311]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",
[12496]356                "external_customer_id": u"PAYER_0123456789012345678901",
[12311]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"
[12497]388        assert token.external_customer_id is None
[12311]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
[12497]394    def test_customer_id_given(self):
395        # we do not override given customer ids
[12311]396        token = CreditCardToken(
397            credit_card_id=u"12345678",
[12497]398            external_customer_id=u'MySpecialPayerId',
[12311]399            )
[12497]400        assert token.external_customer_id == u'MySpecialPayerId'
[12311]401
402    def test_to_str(self):
403        # we can turn CreditCardToken objects into dicts.
404        token = CreditCardToken(
405            credit_card_type=u"visa",
[12497]406            external_customer_id=u"PAYER_0123456789012345678901",
[12311]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",
[12497]416                "external_customer_id": u"PAYER_0123456789012345678901",
[12311]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",
[12497]459            external_customer_id=u"PAYER_0123456789012345678901",
[12311]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",
[12497]472                    "external_customer_id": u"PAYER_0123456789012345678901",
[12311]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'
[12320]1056    first_name = u'Joe'
1057    last_name = u'Shopper'
[12311]1058
1059
1060class FakePayee(object):
1061
[12322]1062    implements(IPayee)
[12311]1063
[12322]1064    payee_id = 'PAYEE-456'
[12311]1065
1066
1067class FakePaymentItem(object):
1068
1069    implements(IPaymentItem)
1070
[12320]1071    title = u'Fake Item'
[12311]1072    item_id = 'BILL-123456'
1073    amount = decimal.Decimal("12.10")
1074    currency = 'EUR'
1075
1076
[12320]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
[12325]1086class PayPalCreditCardServiceTests(FunctionalTestCase):
[12311]1087
[12325]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
[12453]1103    def get_credit_card(self):
1104        # get a valid credit card
1105        credit_card = CreditCard(
1106            credit_card_type=u"visa",
[12498]1107            external_customer_id=u"PAYER_0123456789012345678901",
[12453]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
[12311]1117    def test_iface(self):
1118        # we fullfill all interface contracts
1119        service = PayPalCreditCardService()
1120        verifyClass(IPaymentGatewayService, PayPalCreditCardService)
1121        verifyObject(IPaymentGatewayService, service)
1122
[12325]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
[12453]1144        class MyCard(object):
[12325]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()
[12453]1153        card.myid = 'CHANGED CARD ID'
[12325]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
[12454]1159    @external_paypal_test
1160    def test_store_credit_card(self):
1161        # we can (and must) store credit card data online.
[12453]1162        site = self.create_site()
1163        assert 'creditcards' not in site
1164        service = PayPalCreditCardService()
1165        credit_card = self.get_credit_card()
[12454]1166        result = service.store_credit_card(credit_card)
1167        # a missing creditcards container is created on-the-fly
[12453]1168        assert 'creditcards' in site
[12454]1169        assert ICreditCardToken.providedBy(result)
[12498]1170        assert result.external_customer_id in site['creditcards']
1171        assert site['creditcards'][result.external_customer_id] == result
[12453]1172
[12454]1173    @external_paypal_test
1174    def test_store_credit_card_invalid(self):
1175        # an exception is raised with invalid credit cards.
[12741]1176        self.create_site()
[12454]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
[12326]1187    def test_create_payment_no_credictard(self):
1188        # trying to create a payment without credit card raises an exception
[12311]1189        service = PayPalCreditCardService()
[12326]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.'
[12311]1200
[12494]1201    @external_paypal_test
1202    def test_create_payment(self):
1203        # we can actually create payments
1204        service = PayPalCreditCardService()
[12741]1205        self.create_site()
[12494]1206        credit_card = self.get_credit_card()
1207        result = service.store_credit_card(credit_card)
1208        payer = FakePayer()
[12498]1209        payer.payer_id = result.external_customer_id
[12494]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
[12311]1215
[12494]1216
[12013]1217class FunctionalPaypalTests(FunctionalTestCase):
1218
1219    layer = FunctionalLayer
1220
[12043]1221    @external_paypal_test
[12013]1222    def test_get_access_token(self):
1223        # we can get an access token
1224        result = get_access_token()
1225        assert isinstance(result, unicode)
[12043]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
[12311]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.