Index: main/waeup.ikoba/trunk/src/waeup/ikoba/ftesting.zcml
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/ftesting.zcml	(revision 12059)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/ftesting.zcml	(revision 12060)
@@ -14,4 +14,8 @@
   <ikoba:datacenter
       path="../../../parts/test/datacenter" />
+
+  <!-- Where can the Paypal config be found? -->
+  <ikoba:paypalconf
+      path="../../../etc/paypal-testing.conf" />
 
   <!-- Typical functional testing security setup -->
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py	(revision 12059)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py	(revision 12060)
@@ -1137,4 +1137,13 @@
         )
 
+
+class IPayPalConfig(Interface):
+    path = Path(
+        title = u'Path',
+        description = u"Path to config file for PayPal REST API.",
+        required = True,
+        )
+
+
 #
 # Asynchronous job handling and related
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/meta.zcml
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/meta.zcml	(revision 12059)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/meta.zcml	(revision 12060)
@@ -17,4 +17,10 @@
       />
 
+  <meta:directive
+      namespace="http://namespaces.waeup.org/ikoba"
+      name="paypalconf"
+      schema=".zcml.IPayPalConfig"
+      handler=".zcml.paypal_conf"
+      />
 
 </configure>
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/README-paypal.rst
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/README-paypal.rst	(revision 12060)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/README-paypal.rst	(revision 12060)
@@ -0,0 +1,68 @@
+Support for PayPal(tm) Payments
+*******************************
+
+Ikoba generally supports paying things via PayPal. For this, we use
+the REST-API provided by PayPal.
+
+To make that work, you have to (1) obtain app credentials from PayPal
+and (2) make that credentials known to ikoba.
+
+
+Getting Credentials from PayPal
++++++++++++++++++++++++++++++++
+
+1) Create a general PayPal account
+
+   Browse https://www.paypal.com and create a new account. If you plan
+   to provide services or goods payed via PayPal. you should of course
+   create a business account. You can, however, start with a personal
+   account as it is upgradeable at a later point in time.
+
+   For only testing ikoba PayPal support, a personal account is
+   sufficient.
+
+2) Register as a developer at PayPal
+
+   Go to https://developer.paypal.com and log in with the credentials
+   obtained in step 1. This will create some fake users/merchants for
+   you, which are *not* suitable for use with the PayPal REST API.
+
+3) Create a REST-API compatible application
+
+   After logging in on https://developer.paypal.com/ go to the
+   dashboard and create a new app by clicking the respective button.
+
+Afterwards, in the developer dashboard under "My apps" you can find
+your new set of client IDs and secrets for both, sandbox and live use.
+
+
+Setting PayPal Client ID, secret, etc.
+++++++++++++++++++++++++++++++++++++++
+
+To make the credentials known to Ikoba, put them into configuration
+files like this::
+
+  # paypal.conf
+
+  [rest-client]
+  id = <YOUR-VERY-OWN-PAYPAL-REST-CLIENT-ID>
+  secret = <YOUR-VERY-OWN-PAYPAL-REST-CLIENT-SECRET>
+  # valid values: sandbox, live
+  mode = sandbox
+
+Out of the box, we expect these files to be in the following _two_
+locations:
+
+  path/to/ikoba/install/etc/paypal.conf
+  path/to/ikoba/install/etc/paypal-testing.conf
+
+Obviously, the second file is used for (automated) testing.
+
+.. note:: As these credentials are bound to
+          persons/companies/organisations, we do not share them via
+          version control.
+
+For developers: The credentials given via ZCML and config files are
+used on startup to initialize the PayPal SDK. Therefore, it should not
+be neccessary to care for the credentials when creating Payment
+objects after the Grok instance started.
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/paypal.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/paypal.py	(revision 12060)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/paypal.py	(revision 12060)
@@ -0,0 +1,137 @@
+## $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
+##
+"""Support for PayPal payments.
+"""
+import ConfigParser
+import paypalrestsdk
+from zope.component import queryUtility
+from waeup.ikoba.interfaces import IPayPalConfig
+
+
+def parse_paypal_config(path):
+    """Get paypal credentials from config file.
+
+    Returns a dict with following keys set:
+
+      ``client_id``, ``client_secret``, ``mode``.
+
+    Please note, that values might be `None`.
+    """
+    parser = ConfigParser.SafeConfigParser(
+        {'id': None, 'secret': None, 'mode': 'sandbox', }
+        )
+    parser.readfp(open(path))
+    return {
+        'client_id': parser.get('rest-client', 'id', None),
+        'client_secret': parser.get('rest-client', 'secret', None),
+        'mode': parser.get('rest-client', 'mode', 'sandbox')
+        }
+
+
+def get_paypal_config_file_path():
+    """Get the path of the configuration file (if any).
+
+    If no such file is registered, we get `None`.
+    """
+    util = queryUtility(IPayPalConfig)
+    if util is None:
+        return None
+    return util['path']
+
+
+def configure_sdk(path):
+    """Configure the PayPal SDK with values from config in `path`.
+
+    Parse paypal configuration from file in `path` and set
+    configuration in SDK accordingly.
+
+    Returns a dict with values from config file and path set.
+
+    This function will normally be called during start-up and only
+    this one time.
+
+    It is neccessary to authorize later calls to other part of the
+    PayPal API.
+    """
+    conf = parse_paypal_config(path)
+    paypalrestsdk.configure({
+        'mode': conf['mode'],
+        'client_id': conf['client_id'],
+        'client_secret': conf['client_secret'],
+        })
+    conf['path'] = path
+    return conf
+
+
+def get_access_token():
+    """Get an access token for further calls.
+    """
+    conf = parse_paypal_config(get_paypal_config_file_path())
+    api = paypalrestsdk.set_config(
+        mode=conf['mode'],  # sandbox or live
+        client_id=conf['client_id'],
+        client_secret=conf['client_secret'],
+        )
+    return api.get_access_token()
+
+
+def get_payment():
+    """Construct a payment.
+
+    You have to `create()` the payment yourself.
+
+    XXX: Just some sampledata yet.
+    """
+    payment = paypalrestsdk.Payment(
+        {
+            "intent": "sale",
+            "payer": {
+                "payment_method": "credit_card",
+                "funding_instruments": [
+                    {
+                        "credit_card": {
+                            "type": "visa",
+                            "number": "4417119669820331",
+                            "expire_month": "11",
+                            "expire_year": "2018",
+                            "cvv2": "874",
+                            "first_name": "Joe",
+                            "last_name": "Shopper",
+                            "billing_address": {
+                                "line1": "52 N Main ST",
+                                "city": "Johnstown",
+                                "state": "OH",
+                                "postal_code": "43210",
+                                "country_code": "US"}
+                            }
+                        }
+                    ]},
+            "transactions": [{
+                    "amount": {
+                        "total": "7.47",
+                        "currency": "USD",
+                        "details": {
+                            "subtotal": "7.41",
+                            "tax": "0.03",
+                            "shipping": "0.03"}},
+                    "description": ("This is the payment "
+                                    "transaction description.")
+                    }]
+            }
+        )
+    return payment
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests.py	(revision 12059)
+++ 	(revision )
@@ -1,58 +1,0 @@
-## $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
-##
-"""
-Tests for payments.
-"""
-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.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())
-            )
-        self.assertTrue(
-            verifyClass(
-                IOnlinePayment, OnlinePayment)
-            )
-        self.assertTrue(
-            verifyObject(
-                IOnlinePayment, OnlinePayment())
-            )
-        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/__init__.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/__init__.py	(revision 12060)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/__init__.py	(revision 12060)
@@ -0,0 +1,1 @@
+# Make this a package.
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 12060)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_interfaces.py	(revision 12060)
@@ -0,0 +1,59 @@
+## $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
+##
+"""
+Tests for payments.
+"""
+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.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())
+            )
+        self.assertTrue(
+            verifyClass(
+                IOnlinePayment, OnlinePayment)
+            )
+        self.assertTrue(
+            verifyObject(
+                IOnlinePayment, OnlinePayment())
+            )
+        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_paypal.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal.py	(revision 12060)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_paypal.py	(revision 12060)
@@ -0,0 +1,137 @@
+## $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 os
+import shutil
+import tempfile
+import unittest
+import paypalrestsdk
+from zope.component import getGlobalSiteManager, queryUtility
+from waeup.ikoba.interfaces import IPayPalConfig
+from waeup.ikoba.payments.paypal import (
+    get_paypal_config_file_path, parse_paypal_config, get_access_token,
+    configure_sdk, get_payment,
+    )
+from waeup.ikoba.testing import (
+    FunctionalLayer, FunctionalTestCase,
+    )
+
+#
+# PayPal test config
+#
+
+EXTERNAL_PAYPAL_TESTS = False
+
+#
+# End of PayPal test config
+#
+
+
+def external_paypal_test(func):
+    """A decorator that can block test functions.
+    """
+    if not EXTERNAL_PAYPAL_TESTS:
+        myself = __file__
+        if myself.endswith('.pyc'):
+            myself = myself[:-2]
+        print "WARNING: external paypal tests are skipped!"
+        print "WARNING: edit %s to enable them." % myself
+        return
+    return func
+
+
+class HelperTests(unittest.TestCase):
+
+    def setUp(self):
+        self.workdir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        shutil.rmtree(self.workdir)
+        # unregister any remaining utils registered during tests
+        util = queryUtility(IPayPalConfig)
+        if util is not None:
+            getGlobalSiteManager().unregisterUtility(util, IPayPalConfig)
+
+    def test_get_paypal_config_file_path(self):
+        # we can get a config file path (if registered)
+        path = os.path.join(self.workdir, 'sample.cfg')
+        getGlobalSiteManager().registerUtility(
+            {'path': path, }, IPayPalConfig, '')
+        result = get_paypal_config_file_path()
+        assert result == path
+
+    def test_get_paypal_config_file_path_no_file(self):
+        # without IPayPalConfig util set, we get `None`
+        result = get_paypal_config_file_path()
+        assert result is None
+
+    def test_parse_paypal_config(self):
+        # we can parse paypal configs
+        path = os.path.join(self.workdir, 'sample.cfg')
+        open(path, 'w').write(
+            "[rest-client]\n"
+            "id = myid\n"
+            "secret = mysecret\n"
+            "mode = live\n")
+        result = parse_paypal_config(path)
+        assert result['client_id'] == u'myid'
+        assert result['client_secret'] == u'mysecret'
+        assert result['mode'] == 'live'
+
+    def test_parse_paypal_config_defaults(self):
+        # we have fallback values (defaults) in place
+        path = os.path.join(self.workdir, 'sample.cfg')
+        open(path, 'w').write(
+            "[rest-client]\n"
+            )
+        result = parse_paypal_config(path)
+        assert result['client_id'] is None
+        assert result['client_secret'] is None
+        assert result['mode'] == "sandbox"
+
+    def test_configure_sdk(self):
+        # we can configure the paypal sdk
+        path = os.path.join(self.workdir, 'sample.cfg')
+        open(path, 'w').write(
+            "[rest-client]\n"
+            "id = my-special-id\n"
+            )
+        result = configure_sdk(path)
+        assert result['client_id'] == 'my-special-id'
+        assert result['client_secret'] is None
+        assert result['mode'] == "sandbox"
+        assert paypalrestsdk.api.__api__.mode == 'sandbox'
+        assert paypalrestsdk.api.__api__.client_id == 'my-special-id'
+
+
+class FunctionalPaypalTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    @external_paypal_test
+    def test_get_access_token(self):
+        # we can get an access token
+        result = get_access_token()
+        assert isinstance(result, unicode)
+
+    @external_paypal_test
+    def test_get_payment(self):
+        # we can construct (paypal-)payment objects
+        payment = get_payment()
+        assert isinstance(payment, paypalrestsdk.Payment)
+        result = payment.create()
+        assert result is True
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/tests/sample-datacenter.zcml
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/tests/sample-datacenter.zcml	(revision 12060)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/tests/sample-datacenter.zcml	(revision 12060)
@@ -0,0 +1,13 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:ikoba="http://namespaces.waeup.org/ikoba"
+   i18n_domain="waeup.ikoba"
+   package="waeup.ikoba.tests"
+   >
+  <include package="waeup.ikoba" file="meta.zcml" />
+
+  <!-- Sample for the 'datacenter' ZCML directive: -->
+  <ikoba:datacenter
+      path="/some/path" />
+
+</configure>
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/tests/test_zcml.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/tests/test_zcml.py	(revision 12060)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/tests/test_zcml.py	(revision 12060)
@@ -0,0 +1,120 @@
+import os
+import tempfile
+import shutil
+import unittest
+from waeup.ikoba import tests
+from waeup.ikoba.interfaces import IDataCenterConfig, IPayPalConfig
+from zope.component import getUtility, queryUtility, getGlobalSiteManager
+from zope.configuration.exceptions import ConfigurationError
+from zope.configuration.xmlconfig import file as zcfile
+from zope.configuration.xmlconfig import string as zcstring
+
+
+def get_zcml_conf(xml_code):
+    """Get a working ZCML configuration file with `xml_code` embedded.
+
+    The returned ZCML declares an ikoba namespace for local ZCML
+    extensions and includes the `waeup.ikoba.meta` declarations before
+    embedding `xml_code`.
+
+    The returned string can be 'executed' with `string` or `file` from
+    `zope.configuration.xmlconfig.
+    """
+    config = (
+        '<configure '
+        '   xmlns="http://namespaces.zope.org/zope"'
+        '   xmlns:ikoba="http://namespaces.waeup.org/ikoba"'
+        '   i18n_domain="waeup.ikoba"'
+        '   package="waeup.ikoba.tests"'
+        '>'
+        '<include package="waeup.ikoba" file="meta.zcml" />'
+        ''
+        '%s'
+        ''
+        '</configure>'
+        ) % xml_code
+    return config
+
+
+class ZCMLTests(unittest.TestCase):
+
+    def setUp(self):
+        self.workdir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        shutil.rmtree(self.workdir)
+        # unregister any remaining utils registered during tests
+        gsm = getGlobalSiteManager()
+        for iface in (IDataCenterConfig, IPayPalConfig):
+            conf = queryUtility(iface)
+            if conf is None:
+                continue
+            gsm.unregisterUtility(conf, iface)
+
+    def create_fake_paypal_configs(self):
+        # create two regular files in workdir
+        self.conf1_path = os.path.join(self.workdir, 'paypal1.conf')
+        self.conf2_path = os.path.join(self.workdir, 'paypal2.conf')
+        open(self.conf1_path, 'w').write('[rest-client]\n')
+        open(self.conf2_path, 'w').write('[rest-client]\n')
+
+    def test_datacenter_config_directive(self):
+        # the 'datacenter' directive can be used in ZCML
+        assert queryUtility(IDataCenterConfig) is None
+        zcfile("sample-datacenter.zcml", tests)  # 'execute' the ZCML
+        result = getUtility(IDataCenterConfig)
+        assert result == {'path': u'/some/path'}
+
+    def test_datacenter_config_singleton(self):
+        # we can define only one datacenter config
+        config = get_zcml_conf(
+            '<ikoba:datacenter path="/some/path" />'
+            '<ikoba:datacenter path="/other/path" />'
+            )
+        assert queryUtility(IDataCenterConfig) is None
+        self.assertRaises(
+            ConfigurationError, zcstring, config)
+
+    def test_paypal_config_directive(self):
+        # the 'paypalconf' ZCML directive works
+        assert queryUtility(IPayPalConfig) is None
+        self.create_fake_paypal_configs()
+        config = get_zcml_conf(
+            '<ikoba:paypalconf path="%s" />' % self.conf1_path
+            )
+        zcstring(config)  # execute ZCML
+        result = getUtility(IPayPalConfig)
+        assert result == {
+            'path': self.conf1_path,
+            'client_id': None,
+            'client_secret': None,
+            'mode': 'sandbox'
+            }
+
+    def test_paypal_config_singleton(self):
+        # we can define only one paypal config
+        assert queryUtility(IPayPalConfig) is None
+        self.create_fake_paypal_configs()
+        config = get_zcml_conf(
+            '<ikoba:paypalconf path="%s" />'
+            '<ikoba:paypalconf path="%s" />'
+            ) % (self.conf1_path, self.conf2_path)
+        assert queryUtility(IPayPalConfig) is None
+        self.assertRaises(
+            ConfigurationError, zcstring, config)
+
+    def test_paypal_config_no_such_file(self):
+        # a set paypal config file must exist
+        config = get_zcml_conf(
+            '<ikoba:paypalconf path="/not/eXiStInG/path" />')
+        self.assertRaises(
+            ConfigurationError, zcstring, config)
+
+    def test_paypal_config_not_a_regular_file(self):
+        # a paypal config file must be a regular file
+        dirpath = os.path.join(self.workdir, 'mydir')
+        os.mkdir(dirpath)
+        config = get_zcml_conf(
+            '<ikoba:paypalconf path="%s" />' % dirpath)
+        self.assertRaises(
+            ConfigurationError, zcstring, config)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/utils/tests/test_utils.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/utils/tests/test_utils.py	(revision 12059)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/utils/tests/test_utils.py	(revision 12060)
@@ -29,5 +29,4 @@
 
     def setUp(self):
-        self.max_pmem = psutil.phymem_usage().total
         self.max_vmem = psutil.virtual_memory().total
         self.max_smem = psutil.swap_memory().total
@@ -124,43 +123,2 @@
         utils.SYSTEM_MAX_LOAD['virt-mem'] = -(sys.maxint)  # negative int
         assert utils.expensive_actions_allowed() == False
-
-    def test_expensive_actions_allowed_physmem_none(self):
-        # unset physmem maximum values make KofUtils ignore physmem values
-        utils = self.get_cleared_util()
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = None
-        assert utils.expensive_actions_allowed() == True
-        # even not-set values won't block us
-        del utils.SYSTEM_MAX_LOAD['phys-mem']
-        assert utils.expensive_actions_allowed() == True
-
-    @unittest.skipIf(
-        psutil.phymem_usage().percent >= 99.99,
-        reason="System physmem use over 99%. Cannot set higher allowed value.")
-    def test_expensive_actions_allowed_physmem_ok(self):
-        # We can react to high physmem values
-        max_mem = psutil.phymem_usage().total
-        utils = self.get_cleared_util()
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = 99.99          # positive float
-        assert utils.expensive_actions_allowed() == True
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = -0.01          # negative float
-        assert utils.expensive_actions_allowed() == True
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = max_mem        # positive int
-        assert utils.expensive_actions_allowed() == True
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = -1             # negative int
-        assert utils.expensive_actions_allowed() == True
-
-    @unittest.skipIf(
-        not psutil.phymem_usage().percent,
-        reason="Can test physmem behavior only if actually using some")
-    def test_expensive_actions_allowed_physmem_too_much(self):
-        # We can react if too much physmem is used
-        max_mem = psutil.phymem_usage().total
-        utils = self.get_cleared_util()
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = 0.0            # positive float
-        assert utils.expensive_actions_allowed() == False
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = -100.0         # negative float
-        assert utils.expensive_actions_allowed() == False
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = 0              # positive int
-        assert utils.expensive_actions_allowed() == False
-        utils.SYSTEM_MAX_LOAD['phys-mem'] = -(max_mem)     # negative int
-        assert utils.expensive_actions_allowed() == False
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/utils/utils.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/utils/utils.py	(revision 12059)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/utils/utils.py	(revision 12060)
@@ -125,9 +125,8 @@
     #: value. `cpu-load`, of course, accepts float values only.
     #: `swap-mem` = Swap Memory, `virt-mem` = Virtual Memory,
-    #: `phys-mem` = Physical Memory, `cpu-load` = CPU load in percent.
+    #: `cpu-load` = CPU load in percent.
     SYSTEM_MAX_LOAD = {
         'swap-mem': None,
         'virt-mem': None,
-        'phys-mem': None,
         'cpu-load': 100.0,
         }
@@ -247,5 +246,4 @@
             ('swap-mem', psutil.swap_memory),
             ('virt-mem', psutil.virtual_memory),
-            ('phys-mem', psutil.phymem_usage),
             ):
             max_val = max_values.get(key, None)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/zcml.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/zcml.py	(revision 12059)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/zcml.py	(revision 12060)
@@ -16,6 +16,10 @@
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##
+import os
 from zope.component.zcml import handler
-from waeup.ikoba.interfaces import IDataCenterConfig
+from zope.configuration.exceptions import ConfigurationError
+from waeup.ikoba.interfaces import IDataCenterConfig, IPayPalConfig
+from waeup.ikoba.payments.paypal import configure_sdk
+
 
 def data_center_conf(context, path):
@@ -44,7 +48,47 @@
     """
     context.action(
-        discriminator = ('utility', IDataCenterConfig, ''),
-        callable = handler,
-        args = ('registerUtility',
-                {'path':path}, IDataCenterConfig, '')
+        discriminator=('utility', IDataCenterConfig, ''),
+        callable=handler,
+        args=('registerUtility',
+              {'path': path}, IDataCenterConfig, '')
         )
+
+
+def paypal_handler(path):
+    """ZCML handler that registers paypal configuration.
+
+    We expect paypal credentials written down in a config file. The
+    path to this config file can be set with the ZCML `paypalconf`
+    directive, which is handled here and looks like this::
+
+      <ikoba:paypalconf path='/some/path/to/paypal.conf' />
+
+    This handler requires the given path to exist and to be a file.
+
+    If the file exists and is readable, a dict with ``path`` and other
+    values read from the configuration file is registered as a global
+    unnamed utility for the `IPayPalConfig` interface.
+
+    See :func:`waeup.ikoba.payments.paypal.configure_sdk for details
+    of the set dict.
+    """
+    if not os.path.exists(path):
+        raise ConfigurationError("No such file: %s" % path)
+    if not os.path.isfile(path):
+        raise ConfigurationError("Not a regular file: %s" % path)
+    config_dict = configure_sdk(path)
+    return handler(
+        'registerUtility', config_dict, IPayPalConfig, '')
+
+
+def paypal_conf(context, path):
+    """Handler for ZCML paypalconf directive.
+
+    This handler registers the `paypal_handler` above to perform the
+    real configuration action.
+    """
+    context.action(
+        discriminator=('utility', IPayPalConfig, ''),
+        callable=paypal_handler,
+        args=(path, )
+        )
