Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_container.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_container.py	(revision 12311)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_container.py	(revision 12311)
@@ -0,0 +1,49 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Test the payments/container module.
+"""
+from zope.interface.verify import verifyClass, verifyObject
+from waeup.ikoba.payments.interfaces import IPaymentsContainer
+from waeup.ikoba.payments.container import PaymentsContainer
+from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase)
+
+
+class PaymentsContainerTestCase(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_interfaces(self):
+        # Make sure the correct interfaces are implemented.
+        self.assertTrue(
+            verifyClass(
+                IPaymentsContainer, PaymentsContainer)
+            )
+        self.assertTrue(
+            verifyObject(
+                IPaymentsContainer, PaymentsContainer())
+            )
+        return
+
+    def test_base(self):
+        # We cannot call the fundamental methods of a base in that case
+        container = PaymentsContainer()
+        self.assertRaises(
+            NotImplementedError, container.archive)
+        self.assertRaises(
+            NotImplementedError, container.clear)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_currencies.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_currencies.py	(revision 12311)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_currencies.py	(revision 12311)
@@ -0,0 +1,49 @@
+import unittest
+from zope.i18nmessageid.message import Message as i18nMessage
+from zope.interface.verify import verifyObject
+from zope.schema.interfaces import IVocabularyTokenized, ITerm
+from waeup.ikoba.payments.currencies import (
+    get_decimal_units, ISO_4217_CURRENCIES, ISO_4217_CURRENCIES_VOCAB,
+    )
+
+
+class CurrenciesTests(unittest.TestCase):
+
+    def test_get_decimal_units(self):
+        # we can get the number of decimals for a currency code
+        assert get_decimal_units('USD') == 2
+        assert get_decimal_units('JPY') == 0
+
+    def test_iso_currencies_no_linebreak(self):
+        # sanity check: make sure we didn't compile linebreaks
+        for key, value in ISO_4217_CURRENCIES.items():
+            assert '\n' not in key
+            assert '\n' not in value[0]
+            assert '\n' not in value[1]
+
+    def test_iso_currencies_types(self):
+        # sanity check: make sure we have the correct types in all tuples
+        for key, value in ISO_4217_CURRENCIES.items():
+            assert isinstance(value[1], i18nMessage)
+            assert isinstance(value[2], int)
+            assert isinstance(value[3], str)
+
+    def test_currencies_vocab_tokenized(self):
+        # we can get ISO currencies as a source suitable for forms etc.
+        verifyObject(IVocabularyTokenized, ISO_4217_CURRENCIES_VOCAB)
+
+    def test_currencies_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = ISO_4217_CURRENCIES_VOCAB.getTerm('USD')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'US Dollar')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_currencies_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = ISO_4217_CURRENCIES_VOCAB.getTerm('USD')
+        assert ITerm.providedBy(result)
+        assert result.token == result.value
+        assert result.value == 'USD'
+        assert isinstance(result.token, str)
+        assert isinstance(result.value, str)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_interfaces.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_interfaces.py	(revision 12288)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_interfaces.py	(revision 12311)
@@ -17,11 +17,7 @@
 ##
 """
-Tests for payments.
+Tests for payments interfaces.
 """
-from zope.interface.verify import verifyClass, verifyObject
-from waeup.ikoba.payments.interfaces import (
-    IPaymentsContainer, IOnlinePayment)
-from waeup.ikoba.payments.container import PaymentsContainer
-from waeup.ikoba.payments.payment import OnlinePayment
+from waeup.ikoba.payments.interfaces import PaymentGatewayServicesSource
 from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase)
 
@@ -31,29 +27,16 @@
     layer = FunctionalLayer
 
-    def test_interfaces(self):
-        # Make sure the correct interfaces are implemented.
-        self.assertTrue(
-            verifyClass(
-                IPaymentsContainer, PaymentsContainer)
-            )
-        self.assertTrue(
-            verifyObject(
-                IPaymentsContainer, PaymentsContainer())
-            )
-        self.assertTrue(
-            verifyClass(
-                IOnlinePayment, OnlinePayment)
-            )
-        self.assertTrue(
-            verifyObject(
-                IOnlinePayment, OnlinePayment())
-            )
-        return
+    def test_payment_gateway_services_source(self):
+        # the payment gateway services source provides a list of registered
+        # payment gateways
+        source = PaymentGatewayServicesSource()
+        services = list(source)
+        assert len(services) > 0
 
-    def test_base(self):
-        # We cannot call the fundamental methods of a base in that case
-        container = PaymentsContainer()
-        self.assertRaises(
-            NotImplementedError, container.archive)
-        self.assertRaises(
-            NotImplementedError, container.clear)
+    def test_payment_gateway_services_source_title(self):
+        # we can get titles from gateway sources
+        source = PaymentGatewayServicesSource()
+        service1 = list(source)[0]
+        title = source.factory.getTitle(service1)
+        assert title != service1
+        assert isinstance(title, basestring)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_payment.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_payment.py	(revision 12311)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_payment.py	(revision 12311)
@@ -0,0 +1,112 @@
+## $Id$
+##
+## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import datetime
+import re
+import unittest
+from zope.component import getUtilitiesFor, getSiteManager
+from zope.interface import implements
+from zope.interface.verify import verifyClass, verifyObject
+from waeup.ikoba.payments.interfaces import (
+    IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED,
+    IPaymentGatewayService,
+    )
+from waeup.ikoba.payments.payment import Payment, get_payment_providers
+
+
+class HelperTests(unittest.TestCase):
+
+    def tearDown(self):
+        # unregister any IPaymentGatewayServices
+        sm = getSiteManager(None)
+        for name, util in getUtilitiesFor(
+            IPaymentGatewayService):
+            sm.unregisterUtility(util, name=name)
+
+    def test_get_payment_providers_no_providers(self):
+        # we can get a dict of all payment providers
+        result = get_payment_providers()
+        assert isinstance(result, dict)
+        assert result == {}
+
+    def test_get_payment_providers(self):
+        # we get any payment providers registered
+        sm = getSiteManager(None)
+
+        class FakeUtil(object):
+            implements(IPaymentGatewayService)
+
+        fake_util = FakeUtil()
+        sm.registerUtility(fake_util, name=u'some_name')
+        result = get_payment_providers()
+        assert isinstance(result, dict)
+        assert result.keys() == ['some_name', ]
+        assert result['some_name'] is fake_util
+
+
+class PaymentTests(unittest.TestCase):
+
+    def test_iface(self):
+        # Payments fullfill any interface contracts
+        obj = Payment()
+        verifyClass(IPayment, Payment)
+        verifyObject(IPayment, obj)
+
+    def test_payment_id_unique(self):
+        # we get unique payment ids
+        p1, p2 = Payment(), Payment()
+        id1, id2 = p1.payment_id, p2.payment_id
+        assert id1 != id2
+
+    def test_payment_id_format(self):
+        # payment ids have a special format: "PAY_<32 hex digits>"
+        id1 = Payment().payment_id
+        assert isinstance(id1, basestring)
+        assert re.match('PAY_[0-9a-f]{32}', id1)
+
+    def test_initial_state_is_unpaid(self):
+        # the initial state of payments is <unpaid>
+        p1 = Payment()
+        assert p1.state == STATE_UNPAID
+
+    def test_approve(self):
+        # we can approve payments
+        p1 = Payment()
+        p1.approve()
+        assert p1.state == STATE_PAID
+        assert p1.payment_date is not None
+        assert isinstance(p1.payment_date, datetime.datetime)
+
+    def test_approve_datetime_given(self):
+        # we can give a datetime
+        p1 = Payment()
+        some_datetime = datetime.datetime(2014, 1, 1, 0, 0, 0)
+        p1.approve(payment_date=some_datetime)
+        assert p1.payment_date == some_datetime
+
+    def test_approve_datetime_automatic(self):
+        # if we do not give a datetime, current one will be used
+        current = datetime.datetime.utcnow()
+        p1 = Payment()
+        p1.approve()
+        assert p1.payment_date >= current
+
+    def test_mark_failed(self):
+        # we can mark payments as failed
+        p1 = Payment()
+        p1.mark_failed()
+        assert p1.state == STATE_FAILED
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal.py	(revision 12288)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal.py	(revision 12311)
@@ -16,18 +16,37 @@
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##
+import decimal
 import os
+import re
 import shutil
 import tempfile
 import unittest
 import paypalrestsdk
-from zope.component import getGlobalSiteManager, queryUtility
+from zope.component import (
+    getGlobalSiteManager, queryUtility, getUtilitiesFor,
+    )
+from zope.i18nmessageid.message import Message as i18nMessage
+from zope.interface import implements
+from zope.interface.verify import verifyObject, verifyClass
+from zope.schema.interfaces import IVocabularyTokenized, ITerm
 from waeup.ikoba.interfaces import IPayPalConfig
+from waeup.ikoba.payments.interfaces import (
+    IPaymentGatewayService, IPayer, IPayee, IPaymentItem, IPayment,
+    )
 from waeup.ikoba.payments.paypal import (
     get_paypal_config_file_path, parse_paypal_config, get_access_token,
-    configure_sdk, get_payment,
+    configure_sdk, get_payment, Payer, PayerInfo, ShippingAddress,
+    ADDRESS_TYPES_VOCAB, IShippingAddress, Address, IAddress, to_dict,
+    CreditCard, CREDIT_CARD_TYPES_VOCAB, CREDIT_CARD_STATUS_VOCAB,
+    ICreditCard, ICreditCardToken, CreditCardToken, FundingInstrument,
+    AmountDetails, IAmount, Amount, IItem, Item, STOCK_KEEPING_UNITS_VOCAB,
+    IItemList, ItemList, IPaymentOptions, PaymentOptions, ITransaction,
+    Transaction, PAYMENT_OPTION_METHODS_VOCAB, PayPalCreditCardService,
+    PayPalRegularPaymentService,
     )
 from waeup.ikoba.testing import (
     FunctionalLayer, FunctionalTestCase,
     )
+
 
 #
@@ -118,4 +137,954 @@
         assert paypalrestsdk.api.__api__.client_id == 'my-special-id'
 
+    def test_get_payment_invalid_intent(self):
+        # only 'sale' is currently allowed
+        self.assertRaises(
+            ValueError, get_payment, intent='invalid')
+        self.assertRaises(
+            ValueError, get_payment, intent='order')
+
+    def test_to_dict(self):
+        # we can turn objects into dicts
+        class C1(object):
+            a = 1
+            b = "somestring"
+            c = u"unicodestring"
+            d = None
+        obj = C1()
+        self.assertEqual(
+            to_dict(obj),
+            {'a': u"1",
+             'b': u"somestring",
+             'c': u"unicodestring",
+             }
+            )
+
+    def test_to_dict_map(self):
+        # we can map attribute names
+        class C1(object):
+            a = 1
+            b = "somestring"
+        obj = C1()
+        self.assertEqual(
+            to_dict(obj, name_map={'a': 'replaced_a'}),
+            {'replaced_a': u"1",
+             'b': u"somestring",
+             })
+
+    def test_to_dict_lists(self):
+        # to_dict can handle lists
+        class C1(object):
+            a = 1
+
+            def to_dict(self):
+                return to_dict(self)
+
+        obj1 = C1()
+        obj2 = C1()
+        obj2.a = 2
+        obj3 = C1()
+        obj3.a = 3
+        obj1.foo = [obj2, obj3]
+        self.assertEqual(
+            to_dict(obj1),
+            {
+                'a': u'1',
+                'foo': [
+                    {'a': u'2'},
+                    {'a': u'3'},
+                    ]
+                }
+            )
+
+    def test_to_dict_decimals(self):
+        # decimals are converted to numbers with 2 decimals
+        class C1(object):
+            a = decimal.Decimal("0.1")
+        self.assertEqual(
+            to_dict(C1()),
+            {
+                'a': u"0.10",
+                }
+            )
+
+
+class PayerTests(unittest.TestCase):
+
+    def test_create(self):
+        # we can create payer objects
+        payer = Payer(payment_method='paypal')
+        assert payer.payment_method == 'paypal'
+        assert payer.funding_instruments == []
+        assert payer.payer_info is None
+        assert payer.status is None
+
+    def test_init_invalid_payment_meth(self):
+        # we must provide a valid payment method
+        payer = Payer(payment_method='paypal')
+        assert payer is not None
+        payer = Payer(payment_method='credit_card')
+        assert payer is not None
+        self.assertRaises(
+            ValueError, Payer, payment_method='invalid')
+
+    def test_init_invalid_payer_state(self):
+        # only certain values are allowed as payer states
+        payer = Payer(payment_method='paypal', status='VERIFIED')
+        assert payer is not None
+        payer = Payer(payment_method='paypal', status='UNVERIFIED')
+        assert payer is not None
+        self.assertRaises(
+            ValueError, Payer, payment_method='paypal', status='InVaLiD')
+
+
+class PayerInfoTests(unittest.TestCase):
+
+    def test_create(self):
+        # we can create payer infos
+        info = PayerInfo()
+        assert info.email == ''
+        assert info.first_name == ''
+        assert info.last_name == ''
+        assert info.payer_id == ''
+        assert info.phone == ''
+        assert info.shipping_address is None
+        assert info.tax_id_type == ''
+        assert info.tax_id == ''
+
+    def test_init_invalid_tax_id_type(self):
+        # onyl certain tax id types are allowed
+        info = PayerInfo(tax_id_type='BR_CPF')
+        assert info is not None
+        info = PayerInfo(tax_id_type='BR_CNPJ')
+        assert info is not None
+        self.assertRaises(
+            ValueError, PayerInfo, tax_id_type='INVALID_TYPE')
+
+
+class CreditCardTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill any interface contracts
+        credit_card = CreditCard(
+            number=u"12345678",
+            credit_card_type="visa",
+            expire_month=4,
+            expire_year=2012,
+            )
+        verifyClass(ICreditCard, CreditCard)
+        verifyObject(ICreditCard, credit_card)
+
+    def test_create(self):
+        # we can create CreditCard objects
+        credit_card = CreditCard(
+            number=u"12345678",
+            credit_card_type="visa",
+            expire_month=4,
+            expire_year=2012,
+            )
+        assert credit_card.paypal_id is None
+        assert credit_card.payer_id is not None
+        assert credit_card.number == u"12345678"
+        assert credit_card.credit_card_type == "visa"
+        assert credit_card.expire_month == 4
+        assert credit_card.expire_year == 2012
+        assert credit_card.cvv2 is None
+        assert credit_card.first_name is None
+        assert credit_card.last_name is None
+        assert credit_card.billing_address is None
+        assert credit_card.state is None
+        assert credit_card.paypal_valid_until is None
+
+    def test_payer_id_given(self):
+        # we do not override given payer ids
+        credit_card = CreditCard(
+            number=u"12345678",
+            credit_card_type="visa",
+            expire_month=4,
+            expire_year=2012,
+            payer_id=u'MySpecialPayerId',
+            )
+        assert credit_card.payer_id == u'MySpecialPayerId'
+
+    def test_payer_id_not_given(self):
+        # in case no payer id is given, we generate one
+        credit_card = CreditCard(
+            number=u"12345678",
+            credit_card_type="visa",
+            expire_month=4,
+            expire_year=2012,
+            )
+        # our payer ids contain a leading 'PAYER_' and 32 hex digits
+        assert re.match('PAYER_[0-9a-f]{32}$', credit_card.payer_id)
+
+    def test_number_is_checked(self):
+        # we do not accept invalid numbers
+        self.assertRaises(
+            ValueError, CreditCard,
+            number=u"not-a-number",
+            credit_card_type="visa",
+            expire_month=4,
+            expire_year=2012,
+            )
+
+    def test_to_str(self):
+        # we can turn CreditCard objects into dicts.
+        addr = Address(
+            line1=u"52 N Main ST",
+            city=u"Johnstown",
+            state=u"OH",
+            postal_code=u"43210",
+            country_code=u"US")
+        credit_card = CreditCard(
+            credit_card_type=u"visa",
+            payer_id=u"PAYER_0123456789012345678901",
+            number=u"4417119669820331",
+            expire_month=11,
+            expire_year=2018,
+            cvv2=u"874",
+            first_name=u"Joe",
+            last_name=u"Shopper",
+            billing_address=addr)
+        self.assertEqual(
+            credit_card.to_dict(),
+            {
+                "type": u"visa",
+                "number": u"4417119669820331",
+                "payer_id": u"PAYER_0123456789012345678901",
+                "expire_month": u"11",
+                "expire_year": u"2018",
+                "cvv2": u"874",
+                "first_name": u"Joe",
+                "last_name": u"Shopper",
+                "billing_address": {
+                    "line1": u"52 N Main ST",
+                    "city": u"Johnstown",
+                    "state": u"OH",
+                    "postal_code": u"43210",
+                    "country_code": u"US"}
+                }
+            )
+
+
+class CreditCardTokenTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill any interface contracts
+        token = CreditCardToken(
+            credit_card_id=u"12345678",
+            )
+        verifyClass(ICreditCardToken, CreditCardToken)
+        verifyObject(ICreditCardToken, token)
+
+    def test_create(self):
+        # we can create CreditCardToken objects
+        token = CreditCardToken(
+            credit_card_id=u"12345678",
+            )
+        assert token.credit_card_id == u"12345678"
+        assert token.payer_id is None
+        assert token.credit_card_type is None
+        assert token.expire_month is None
+        assert token.expire_year is None
+        assert token.last4 is None
+
+    def test_payer_id_given(self):
+        # we do not override given payer ids
+        token = CreditCardToken(
+            credit_card_id=u"12345678",
+            payer_id=u'MySpecialPayerId',
+            )
+        assert token.payer_id == u'MySpecialPayerId'
+
+    def test_to_str(self):
+        # we can turn CreditCardToken objects into dicts.
+        token = CreditCardToken(
+            credit_card_type=u"visa",
+            payer_id=u"PAYER_0123456789012345678901",
+            credit_card_id=u"12345678",
+            last4="8901",
+            expire_month=11,
+            expire_year=2018,
+            )
+        self.assertEqual(
+            token.to_dict(),
+            {
+                "credit_card_id": u"12345678",
+                "payer_id": u"PAYER_0123456789012345678901",
+                "last4": u"8901",
+                "type": u"visa",
+                "expire_month": u"11",
+                "expire_year": u"2018",
+                }
+            )
+
+
+class FundingInstrumentTests(unittest.TestCase):
+
+    def test_create(self):
+        # we can create FundingInstrument objects
+        token = CreditCardToken(
+            credit_card_id=u"12345678",
+            )
+        instr = FundingInstrument(credit_card_token=token)
+        assert instr.credit_card_token is token
+
+    def test_require_credit_card_or_token(self):
+        # we require a credit card object or a token.
+        credit_card = CreditCard(
+            number=u"12345678",
+            credit_card_type="visa",
+            expire_month=4,
+            expire_year=2012,
+            )
+        token = CreditCardToken(
+            credit_card_id=u"12345678",
+            )
+        self.assertRaises(
+            ValueError, FundingInstrument,
+            credit_card=credit_card,
+            credit_card_token=token
+            )
+        self.assertRaises(
+            ValueError, FundingInstrument
+            )
+
+    def test_to_dict(self):
+        # we can turn Funding instruments into dicts
+        token = CreditCardToken(
+            credit_card_type=u"visa",
+            payer_id=u"PAYER_0123456789012345678901",
+            credit_card_id=u"12345678",
+            last4="8901",
+            expire_month=11,
+            expire_year=2018,
+            )
+        instr = FundingInstrument(credit_card_token=token)
+        result = instr.to_dict()
+        self.assertEqual(
+            result,
+            {
+                "credit_card_token": {
+                    "credit_card_id": u"12345678",
+                    "payer_id": u"PAYER_0123456789012345678901",
+                    "last4": u"8901",
+                    "type": u"visa",
+                    "expire_month": u"11",
+                    "expire_year": u"2018",
+                    }
+                }
+            )
+
+
+class AddressTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill any interface contracts
+        addr = Address(
+            line1=u'Address Line 1',
+            city=u'Somecity',
+            country_code=u'AT',
+            )
+        verifyClass(IAddress, Address)
+        verifyObject(IAddress, addr)
+
+    def test_create(self):
+        # we can create addresses
+        addr = Address(
+            line1=u'Honey Street 1',
+            city=u'Beartown',
+            country_code=u'GB',
+            )
+        assert addr.line1 == u'Honey Street 1'
+        assert addr.line2 is None
+        assert addr.city == u'Beartown'
+        assert addr.country_code == u'GB'
+        assert addr.postal_code is None
+        assert addr.state is None
+        assert addr.phone is None
+
+    def test_to_dict(self):
+        # we can turn addresses into dicts
+        addr = Address(
+            line1=u'Honey Street 1',
+            city=u'Beartown',
+            country_code=u'GB',
+            )
+        self.assertEqual(
+            addr.to_dict(),
+            {
+                'line1': u'Honey Street 1',
+                'city': u'Beartown',
+                'country_code': u'GB',
+                }
+            )
+        addr.line2 = u"Behind little tree"
+        self.assertEqual(
+            addr.to_dict(),
+            {
+                'line1': u'Honey Street 1',
+                'line2': u'Behind little tree',
+                'city': u'Beartown',
+                'country_code': u'GB',
+                }
+            )
+
+
+class ShippingAddressTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill any interface contracts
+        addr = ShippingAddress(
+            recipient_name=u'Foo Bar',
+            line1=u'Address Line 1',
+            city=u'Somecity',
+            country_code=u'AT',
+            )
+        verifyClass(IShippingAddress, ShippingAddress)
+        verifyObject(IShippingAddress, addr)
+
+    def test_create(self):
+        # we can create shipping addresses
+        addr = ShippingAddress(
+            recipient_name=u'Rob Receiver',
+            line1=u'Honey Street 1',
+            city=u'Beartown',
+            country_code=u'GB',
+            )
+        assert addr.recipient_name == u'Rob Receiver'
+        assert addr.type == u'residential'
+        assert addr.line1 == u'Honey Street 1'
+        assert addr.line2 is None
+        assert addr.city == u'Beartown'
+        assert addr.country_code == u'GB'
+        assert addr.postal_code is None
+        assert addr.state is None
+        assert addr.phone is None
+
+    def test_to_dict(self):
+        # we can turn shipping addresses into dicts
+        addr = ShippingAddress(
+            recipient_name=u'Rob Receiver',
+            line1=u'Honey Street 1',
+            city=u'Beartown',
+            country_code=u'GB',
+            )
+        self.assertEqual(
+            addr.to_dict(),
+            {
+                'recipient_name': u'Rob Receiver',
+                'type': u'residential',
+                'line1': u'Honey Street 1',
+                'city': u'Beartown',
+                'country_code': u'GB',
+                }
+            )
+        addr.line2 = u"Behind little tree"
+        self.assertEqual(
+            addr.to_dict(),
+            {
+                'recipient_name': u'Rob Receiver',
+                'type': u'residential',
+                'line1': u'Honey Street 1',
+                'line2': u'Behind little tree',
+                'city': u'Beartown',
+                'country_code': u'GB',
+                }
+            )
+
+
+class AmountDetailsTests(unittest.TestCase):
+
+    def test_create(self):
+        # we can create AmountDetail objects
+        details = AmountDetails()
+        assert details.shipping is None
+        assert details.subtotal is None
+        assert details.tax is None
+        assert details.fee is None
+        assert details.handling_fee is None
+        assert details.insurance is None
+        assert details.shipping_discount is None
+
+    def test_to_dict(self):
+        # we can turn AmountDetails into a dict
+        details = AmountDetails(
+            shipping=decimal.Decimal("0.10"),
+            tax=decimal.Decimal("0.30"),
+            fee=decimal.Decimal("0.40"),
+            handling_fee=decimal.Decimal("0.50"),
+            insurance=decimal.Decimal("0.60"),
+            shipping_discount=decimal.Decimal("0.70")
+            )
+        self.assertEqual(
+            details.to_dict(),
+            {
+                'shipping': u"0.10",
+                'subtotal': u"1.20",
+                'tax': u"0.30",
+                'fee': u"0.40",
+                'handling_fee': u"0.50",
+                'insurance': u"0.60",
+                'shipping_discount': u"0.70"
+                }
+            )
+
+    def test_subtotal_all_none(self):
+        # if all items are none, also subtotal is none
+        details = AmountDetails(
+            shipping=None, tax=None, fee=None, handling_fee=None,
+            insurance=None, shipping_discount=None,
+            )
+        assert details.subtotal is None
+        details.shipping_discount = decimal.Decimal("1.00")
+        assert details.subtotal == decimal.Decimal("-1.00")
+
+    def test_subtotal_sum(self):
+        # subtotal sums up correctly
+        details = AmountDetails(
+            shipping=decimal.Decimal("0.05"),
+            tax=decimal.Decimal("0.40"),
+            fee=decimal.Decimal("3.00"),
+            handling_fee=decimal.Decimal("20.00"),
+            insurance=decimal.Decimal("100.00"),
+            shipping_discount=None
+            )
+        self.assertEqual(details.subtotal, decimal.Decimal("123.45"))
+        details.shipping_discount = decimal.Decimal("0.00")
+        self.assertEqual(details.subtotal, decimal.Decimal("123.45"))
+        details.shipping_discount = decimal.Decimal("23.45")
+        self.assertEqual(details.subtotal, decimal.Decimal("100.00"))
+
+
+class AmountTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill any interface contracts.
+        amount = Amount()
+        verifyClass(IAmount, Amount)
+        verifyObject(IAmount, amount)
+
+    def test_create(self):
+        # we can create amount objects
+        details = AmountDetails(
+            shipping=decimal.Decimal("0.05"),
+            tax=decimal.Decimal("0.40"),
+            fee=decimal.Decimal("3.00"),
+            handling_fee=decimal.Decimal("20.00"),
+            insurance=decimal.Decimal("100.00"),
+            shipping_discount=None
+            )
+        amount = Amount(
+            total=decimal.Decimal("12.12"),
+            currency="USD",
+            details=details
+            )
+        assert amount.total == decimal.Decimal("12.12")
+        assert amount.currency == "USD"
+        assert amount.details is details
+
+    def test_to_dict(self):
+        # we can turn Amount objects into dicts
+        self.maxDiff = None
+        details = AmountDetails(
+            shipping=decimal.Decimal("0.05"),
+            tax=decimal.Decimal("0.40"),
+            fee=decimal.Decimal("3.00"),
+            handling_fee=decimal.Decimal("20.00"),
+            insurance=decimal.Decimal("100.00"),
+            shipping_discount=None
+            )
+        amount = Amount(
+            total=decimal.Decimal("12.12"),
+            currency="USD",
+            details=details
+            )
+        self.assertEqual(
+            amount.to_dict(),
+            {
+                'total': u'12.12',
+                'currency': u'USD',
+                'details': {
+                    'shipping': u'0.05',
+                    'subtotal': u'123.45',
+                    'tax': u'0.40',
+                    'fee': u'3.00',
+                    'handling_fee': u'20.00',
+                    'insurance': u'100.00',
+                    }
+                }
+            )
+
+
+class ItemTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill all interface contracts
+        item = Item(name=u"Splendid Item")
+        verifyClass(IItem, Item)
+        verifyObject(IItem, item)
+
+    def test_create(self):
+        # we can create Item objects
+        item = Item(
+            quantity=3,
+            name=u"Splendid Thing",
+            price=decimal.Decimal("1.1"),
+            currency="USD",
+            sku="pcs",
+            description=u"Soo splendid!",
+            tax=decimal.Decimal("0.1"),
+            )
+        assert item.quantity == 3
+        assert item.name == u"Splendid Thing"
+        assert item.price == decimal.Decimal("1.1")
+        assert item.currency == "USD"
+        assert item.sku == "pcs"
+        assert item.description == u"Soo splendid!"
+        assert item.tax == decimal.Decimal("0.1")
+
+    def test_to_dict(self):
+        # we can turn Item objects into dicts
+        item = Item(
+            quantity=3,
+            name=u"Splendid Thing",
+            price=decimal.Decimal("1.1"),
+            currency="USD",
+            sku="pcs",
+            description=u"Soo splendid!",
+            tax=decimal.Decimal("0.1"),
+            )
+        self.assertEqual(
+            item.to_dict(),
+            {
+                "quantity": u"3",
+                "name": u"Splendid Thing",
+                "price": u"1.10",
+                "currency": u"USD",
+                "sku": u"pcs",
+                "description": u"Soo splendid!",
+                "tax": u"0.10"
+                }
+            )
+
+
+class ItemListTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill all interface contracts
+        item_list = ItemList()
+        verifyClass(IItemList, ItemList)
+        verifyObject(IItemList, item_list)
+
+    def test_create_minimal(self):
+        # we can create ItemLists with a minimum of params
+        item_list = ItemList()
+        assert item_list.shipping_address is None
+        assert item_list.items == []
+
+    def test_create(self):
+        # we can create ItemLists
+        item1 = Item(
+            name=u"Splendid Thing",
+            )
+        item2 = Item(
+            name=u"Other Splendid Thing",
+            )
+        addr = ShippingAddress(
+            recipient_name=u'Rob Receiver',
+            line1=u'Honey Street 1',
+            city=u'Beartown',
+            country_code=u'GB',
+            )
+        item_list = ItemList(
+            shipping_address=addr,
+            items=[item1, item2])
+        assert item_list.shipping_address is addr
+        assert item_list.items == [item1, item2]
+
+    def test_to_dict(self):
+        # we can turn ITemLists into dicts
+        item = Item(
+            quantity=3,
+            name=u"Splendid Thing",
+            price=decimal.Decimal("1.1"),
+            currency="USD",
+            sku="pcs",
+            description=u"Soo splendid!",
+            tax=decimal.Decimal("0.1"),
+            )
+        addr = ShippingAddress(
+            recipient_name=u'Rob Receiver',
+            line1=u'Honey Street 1',
+            city=u'Beartown',
+            country_code=u'GB',
+            )
+        item_list = ItemList(items=[item, ], shipping_address=addr)
+        self.assertEqual(
+            item_list.to_dict(),
+            {
+                "items": [
+                    {
+                        "quantity": u"3",
+                        "name": u"Splendid Thing",
+                        "price": u"1.10",
+                        "currency": u"USD",
+                        "sku": u"pcs",
+                        "description": u"Soo splendid!",
+                        "tax": u"0.10"
+                        }
+                    ],
+                "shipping_address":
+                {
+                    'recipient_name': u'Rob Receiver',
+                    'type': u'residential',
+                    'line1': u'Honey Street 1',
+                    'city': u'Beartown',
+                    'country_code': u'GB',
+                    }
+                }
+            )
+
+
+class PaymentOptionsTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill all interface contracts
+        opts = PaymentOptions()
+        verifyClass(IPaymentOptions, PaymentOptions)
+        verifyObject(IPaymentOptions, opts)
+
+    def test_create(self):
+        # we can create PaymentOptions objects
+        opts = PaymentOptions()
+        assert opts.allowed_payment_method is None
+
+    def test_allowed_payment_method_checked_in_init(self):
+        # any value apart from None, INSTANT... is rejected in __init__
+        self.assertRaises(
+            ValueError,
+            PaymentOptions, allowed_payment_method='NoTvAlID')
+
+    def test_to_dict(self):
+        # we can turn PaymentOptions into dicts
+        opts = PaymentOptions(
+            allowed_payment_method="INSTANT_FUNDING_SOURCE")
+        self.assertEqual(
+            opts.to_dict(),
+            {
+                'allowed_payment_method': "INSTANT_FUNDING_SOURCE",
+                }
+            )
+
+
+class TransactionTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill all interface contracts
+        amount = Amount()
+        transaction = Transaction(amount=amount)
+        verifyClass(ITransaction, Transaction)
+        verifyObject(ITransaction, transaction)
+
+    def test_create(self):
+        # we can create transacions
+        amount = Amount()
+        transaction = Transaction(amount=amount)
+        assert transaction.amount is amount
+        assert transaction.description is None
+        assert transaction.item_list is None
+        assert transaction.related_resources == []
+        assert transaction.invoice_number is None
+        assert transaction.custom is None
+        assert transaction.soft_descriptor is None
+        assert transaction.payment_options is None
+
+    def test_to_dict(self):
+        # we can turn Transaction objects into dicts
+        transaction = Transaction(
+            amount=Amount(),
+            description=u"My description",
+            item_list=ItemList(),
+            related_resources=[],
+            invoice_number=u"12345",
+            custom=u"Some custom remark",
+            soft_descriptor=u"softdescriptor?",
+            payment_options=PaymentOptions(
+                allowed_payment_method="INSTANT_FUNDING_SOURCE"),
+            )
+        self.assertEqual(
+            transaction.to_dict(), {
+                'amount': {'currency': u'USD', 'total': u'0.00'},
+                'custom': u'Some custom remark',
+                'description': u'My description',
+                'invoice_number': u'12345',
+                'item_list': {
+                    'items': []
+                    },
+                'payment_options': {
+                    'allowed_payment_method': u'INSTANT_FUNDING_SOURCE'
+                    },
+                'related_resources': [],
+                'soft_descriptor': u'softdescriptor?'
+                }
+            )
+
+
+class AddressTypesVocabTests(unittest.TestCase):
+
+    def test_address_types_vocab_tokenized(self):
+        # we can get a countries source suitable for forms etc.
+        verifyObject(IVocabularyTokenized, ADDRESS_TYPES_VOCAB)
+
+    def test_address_types_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = ADDRESS_TYPES_VOCAB.getTerm('residential')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'residential')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_address_types_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = ADDRESS_TYPES_VOCAB.getTerm('residential')
+        assert ITerm.providedBy(result)
+        assert result.token == result.value
+        assert result.value == 'residential'
+        assert isinstance(result.token, str)
+        assert isinstance(result.value, str)
+
+
+class CreditCardTypesVocabTests(unittest.TestCase):
+
+    def test_credit_card_types_vocab_tokenized(self):
+        # we can get a countries source suitable for forms etc.
+        verifyObject(IVocabularyTokenized, CREDIT_CARD_TYPES_VOCAB)
+
+    def test_credit_cards_types_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = CREDIT_CARD_TYPES_VOCAB.getTerm('visa')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'visa')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_credit_cards_types_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = CREDIT_CARD_TYPES_VOCAB.getTerm('visa')
+        assert ITerm.providedBy(result)
+        assert result.token == result.value
+        assert result.value == 'visa'
+        assert isinstance(result.token, str)
+        assert isinstance(result.value, str)
+
+
+class CreditcardstatusVocabTests(unittest.TestCase):
+
+    def test_credit_card_status_vocab_tokenized(self):
+        # we can get a countries source suitable for forms etc.
+        verifyObject(IVocabularyTokenized, CREDIT_CARD_STATUS_VOCAB)
+
+    def test_credit_cards_status_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = CREDIT_CARD_STATUS_VOCAB.getTerm('ok')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'ok')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_credit_cards_status_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = CREDIT_CARD_STATUS_VOCAB.getTerm('expired')
+        assert ITerm.providedBy(result)
+        assert result.token == result.value
+        assert result.value == 'expired'
+        assert isinstance(result.token, str)
+        assert isinstance(result.value, str)
+
+
+class StockKeepingUnitsVocabTests(unittest.TestCase):
+
+    def test_sku_vocab_tokenized(self):
+        # we can get a tokenzed vocab for stock keeping units
+        verifyObject(IVocabularyTokenized, STOCK_KEEPING_UNITS_VOCAB)
+
+    def test_sku_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = STOCK_KEEPING_UNITS_VOCAB.getTerm('pcs')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'pieces')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_sku_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = STOCK_KEEPING_UNITS_VOCAB.getTerm('pcs')
+        assert ITerm.providedBy(result)
+        assert result.token == result.value
+        assert result.value == 'pcs'
+        assert isinstance(result.token, str)
+        assert isinstance(result.value, str)
+
+
+class PaymentOptionMethodsVocabTests(unittest.TestCase):
+
+    def test_payment_option_methods_vocab_tokenized(self):
+        # we can get a countries source suitable for forms etc.
+        verifyObject(IVocabularyTokenized, PAYMENT_OPTION_METHODS_VOCAB)
+
+    def test_payment_option_methods_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = PAYMENT_OPTION_METHODS_VOCAB.getTerm('INSTANT_FUNDING_SOURCE')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'INSTANT_FUNDING_SOURCE')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_payment_option_methods_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = PAYMENT_OPTION_METHODS_VOCAB.getTerm('INSTANT_FUNDING_SOURCE')
+        assert ITerm.providedBy(result)
+        assert result.token == result.value
+        assert result.value == 'INSTANT_FUNDING_SOURCE'
+        assert isinstance(result.token, str)
+        assert isinstance(result.value, str)
+
+
+class FakePayer(object):
+
+    implements(IPayer)
+
+    payer_id = 'PAYER-123'
+
+
+class FakePayee(object):
+
+     implements(IPayee)
+
+     payee_id = 'PAYEE-456'
+
+
+class FakePaymentItem(object):
+
+    implements(IPaymentItem)
+
+    item_id = 'BILL-123456'
+    amount = decimal.Decimal("12.10")
+    currency = 'EUR'
+
+
+class PayPalCreditCardServiceTests(unittest.TestCase):
+
+    def test_iface(self):
+        # we fullfill all interface contracts
+        service = PayPalCreditCardService()
+        verifyClass(IPaymentGatewayService, PayPalCreditCardService)
+        verifyObject(IPaymentGatewayService, service)
+
+    def DIStest_creditcard_service_can_create_payment(self):
+        # we can create IPayment objects with creditcard service
+        service = PayPalCreditCardService()
+        payment = service.create_payment(
+            payer=FakePayer(),
+            payment_item=FakePaymentItem(),
+            payee=FakePayee()
+            )
+        assert IPayment.providedBy(payment)
+
 
 class FunctionalPaypalTests(FunctionalTestCase):
@@ -136,2 +1105,13 @@
         result = payment.create()
         assert result is True
+
+    def test_paypal_services_registered(self):
+        # the PayPal gateway services are all registered
+        creditcard_service = queryUtility(
+            IPaymentGatewayService, name=u'paypal_creditcard')
+        assert creditcard_service is not None
+        assert isinstance(creditcard_service, PayPalCreditCardService)
+        paypal_regular_service = queryUtility(
+            IPaymentGatewayService, name=u'paypal_regular')
+        assert paypal_regular_service is not None
+        assert isinstance(paypal_regular_service, PayPalRegularPaymentService)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal_countries.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal_countries.py	(revision 12311)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal_countries.py	(revision 12311)
@@ -0,0 +1,38 @@
+import unittest
+from zope.i18nmessageid.message import Message as i18nMessage
+from zope.interface.verify import verifyObject
+from zope.schema.interfaces import IVocabularyTokenized, ITerm
+from waeup.ikoba.payments.paypal_countries import COUNTRIES, COUNTRIES_VOCAB
+
+
+class CountryTests(unittest.TestCase):
+
+    def test_countries_is_dict(self):
+        # COUNTRIES is available
+        assert isinstance(COUNTRIES, dict)
+
+    def test_countries_contains_i18n_messages(self):
+        # the COUNTRIES values are i18n messages
+        assert 'DE' in COUNTRIES.keys()
+        assert isinstance(COUNTRIES['DE'], i18nMessage)
+
+
+class CountriesVocabTests(unittest.TestCase):
+
+    def test_countries_vocab_tokenized(self):
+        # we can get a countries source suitable for forms etc.
+        verifyObject(IVocabularyTokenized, COUNTRIES_VOCAB)
+
+    def test_countries_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = COUNTRIES_VOCAB.getTerm('DE')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'GERMANY')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_countries_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = COUNTRIES_VOCAB.getTerm('DE')
+        assert ITerm.providedBy(result)
+        assert result.token == 'DE'
+        assert result.value == 'DE'
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal_currencies.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal_currencies.py	(revision 12311)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal_currencies.py	(revision 12311)
@@ -0,0 +1,63 @@
+# $Id$
+#
+# Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+import unittest
+from zope.i18nmessageid.message import Message as i18nMessage
+from zope.interface.verify import verifyObject
+from zope.schema.interfaces import IVocabularyTokenized, ITerm
+from waeup.ikoba.payments.paypal_currencies import (
+    CURRENCIES, CURRENCIES_VOCAB,
+    )
+
+
+class CurrencyTests(unittest.TestCase):
+
+    def test_currencies_is_dict(self):
+        # CURRENCIES is available
+        assert isinstance(CURRENCIES, dict)
+
+    def test_currencies_contains_i18n_messages(self):
+        # the CURRENCIES values are i18n messages
+        assert 'USD' in CURRENCIES.keys()
+        assert isinstance(CURRENCIES['USD'][0], i18nMessage)
+
+    def test_currencies_contain_domestic_infos(self):
+        # there is info about currencies valid domestic only avail.
+        CURRENCIES['USD'][1] is False
+        # there is info about whether the currency support decimals
+        CURRENCIES['USD'][2] is True
+
+
+class CurrenciesVocabTests(unittest.TestCase):
+
+    def test_currencies_vocab_tokenized(self):
+        # we can get a currencies source suitable for forms etc.
+        verifyObject(IVocabularyTokenized, CURRENCIES_VOCAB)
+
+    def test_currencies_vocab_i18nized(self):
+        # vocab titles are i18nized
+        result = CURRENCIES_VOCAB.getTerm('USD')
+        assert ITerm.providedBy(result)
+        self.assertEqual(result.title, u'United States dollar')
+        assert isinstance(result.title, i18nMessage)
+
+    def test_currencies_vocab_tokens_are_string(self):
+        # vocab tokens are simple strings
+        result = CURRENCIES_VOCAB.getTerm('USD')
+        assert ITerm.providedBy(result)
+        assert result.token == 'USD'
+        assert result.value == 'USD'
