Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/catalog.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/catalog.py	(revision 12550)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/catalog.py	(revision 12671)
@@ -32,5 +32,4 @@
     payment_id = grok.index.Field(attribute='payment_id')
     payer_id = grok.index.Field(attribute='payer_id')
-    payed_item_id = grok.index.Field(attribute='payed_item_id')
     state = grok.index.Field(attribute='state')
     amount = grok.index.Field(attribute='amount')
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/demo_provider.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/demo_provider.py	(revision 12671)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/demo_provider.py	(revision 12671)
@@ -0,0 +1,25 @@
+import grok
+from waeup.ikoba.interfaces import MessageFactory as _
+from waeup.ikoba.payments.interfaces import IPaymentGatewayService
+from waeup.ikoba.payments.payment import Payment
+
+
+class DemoCreditcardPaymentService(grok.GlobalUtility):
+    """A demo payment gateway service.
+
+    This one supports credit card payments.
+    """
+    grok.implements(IPaymentGatewayService)
+    grok.name('demo_creditcard')
+
+    title = _(u'Demo Creditcard')
+
+    def create_payment(self, payer, payment_item_list=[],  payee=None):
+        """Create a payment.
+        """
+        payment = Payment()
+        payment.gateway_service = 'demo_creditcard'  # must be grok.name above
+        payment.payer_id = payer.payer_id
+        for item in payment_item_list:
+            payment.add_payment_item(item)
+        return payment
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/interfaces.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/interfaces.py	(revision 12550)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/interfaces.py	(revision 12671)
@@ -20,5 +20,7 @@
 from zope import schema
 from zope.component import getUtilitiesFor
-from zope.interface import Interface
+from zope.container.interfaces import IContainer
+from zope.container.constraints import contains
+from zope.interface import Interface, Attribute
 from waeup.ikoba.interfaces import (
     IIkobaObject, SimpleIkobaVocabulary, ContextualDictSourceFactoryBase)
@@ -86,5 +88,5 @@
         )
 
-    def create_payment(payer, payment_item,  payee):
+    def create_payment(payer, payment_item_list,  payee):
         """Create a payment.
 
@@ -101,4 +103,14 @@
 
 
+class IPaymentGatewayServicesLister(Interface):
+    """A utility that lists all valid payment gateways.
+
+    This is a subset of the available payment methods, as some might
+    be disabled in some site.
+
+    Register your own lister in customized sites!
+    """
+
+
 class PaymentCategorySource(ContextualDictSourceFactoryBase):
     """A payment category source delivers all categories of payments.
@@ -115,5 +127,39 @@
 
 
-class IPayment(IIkobaObject):
+class ICreditCard(Interface):
+    """A credit card.
+
+    A credit card is connected to a Payer.
+    """
+    credit_card_id = schema.TextLine(
+        title=u'Internal Credit Card ID',
+        required=True,
+        )
+
+
+class IPaymentItem(Interface):
+    """Something to sell.
+    """
+    item_id = schema.TextLine(
+        title=u'Payment Item ID',
+        required=True,
+        )
+
+    title = schema.TextLine(
+        title=u'Title',
+        description=u'A short title of the good sold.',
+        required=True,
+        default=u'Unnamed'
+        )
+
+    amount = schema.Decimal(
+        title=u'Amount',
+        description=u'Total amount, includung any taxes, fees, etc.',
+        required=True,
+        default=decimal.Decimal('0.00'),
+        )
+
+
+class IPayment(IContainer):
     """A base representation of payments.
 
@@ -139,4 +185,6 @@
     we mark the payment 'failed'.
     """
+    contains(IPaymentItem)
+
     payment_id = schema.TextLine(
         title=u'Payment Identifier',
@@ -151,9 +199,9 @@
         )
 
-    payed_item_id = schema.TextLine(
-        title=u'Payed Item ID',
-        default=None,
-        required=True,
-        )
+    payee_id = schema.TextLine(
+        title=u'Payee',
+        default=None,
+        required=False,
+    )
 
     gateway_service = schema.Choice(
@@ -184,12 +232,12 @@
         )
 
-    amount = schema.Decimal(
-        title=_(u'Amount'),
-        description=_(
-            'The overall sum payed, including all taxes fees, etc.'),
-        default=decimal.Decimal("0.00"),
-        required=True,
-        readonly=False,
-        )
+    currency = schema.Choice(
+        title=u'Currency',
+        source=ISO_4217_CURRENCIES_VOCAB,
+        required=True,
+        default='USD',
+        )
+
+    amount = Attribute("Sum of amounts of items contained")
 
     def approve():
@@ -208,51 +256,9 @@
         """
 
-
-class IOnlinePayment(IPayment):
-    """A payment via payment gateways.
-
-    """
-
-    ac = schema.TextLine(
-        title=_(u'Activation Code'),
-        default=None,
-        required=False,
-        readonly=False,
-        )
-
-    r_amount_approved = schema.Float(
-        title=_(u'Response Amount Approved'),
-        default=0.0,
-        required=False,
-        readonly=False,
-        )
-
-    r_code = schema.TextLine(
-        title=_(u'Response Code'),
-        default=None,
-        required=False,
-        readonly=False,
-        )
-
-    r_desc = schema.TextLine(
-        title=_(u'Response Description'),
-        default=None,
-        required=False,
-        readonly=False,
-        )
-
-    def approve():
-        "Approve an online payment and set to paid."
-
-
-class ICreditCard(Interface):
-    """A credit card.
-
-    A credit card is connected to a Payer.
-    """
-    credit_card_id = schema.TextLine(
-        title=u'Internal Credit Card ID',
-        required=True,
-        )
+    def add_payment_item(item):
+        """Payments contain payment items.
+
+        Add one
+        """
 
 
@@ -283,32 +289,2 @@
         required=True
         )
-
-
-class IPaymentItem(Interface):
-    """Something to sell.
-    """
-    item_id = schema.TextLine(
-        title=u'Payment Item ID',
-        required=True,
-        )
-
-    title = schema.TextLine(
-        title=u'Title',
-        description=u'A short title of the good sold.',
-        required=True,
-        default=u'Unnamed'
-        )
-
-    amount = schema.Decimal(
-        title=u'Amount',
-        description=u'Total amount, includung any taxes, fees, etc.',
-        required=True,
-        default=decimal.Decimal('0.00'),
-        )
-
-    currency = schema.Choice(
-        title=u'Currency',
-        source=ISO_4217_CURRENCIES_VOCAB,
-        required=True,
-        default='USD',
-        )
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/payment.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/payment.py	(revision 12550)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/payment.py	(revision 12671)
@@ -28,5 +28,6 @@
 from waeup.ikoba.payments.interfaces import (
     IPayment, STATE_UNPAID, STATE_FAILED, STATE_PAID,
-    IPaymentGatewayService, IPayer, IPaymentItem,
+    IPaymentGatewayService, IPayer, IPaymentItem, IPayee,
+    IPaymentGatewayServicesLister,
     )
 from waeup.ikoba.utils.logger import Logger
@@ -34,9 +35,18 @@
 
 def get_payment_providers():
-    """Get all services of payment gateways registered.
+    """Get all payment providers registered.
     """
     return dict(
         getUtilitiesFor(IPaymentGatewayService)
-        )
+    )
+
+
+class PaymentGatewayServicesLister(grok.GlobalUtility):
+    grok.implements(IPaymentGatewayServicesLister)
+
+    def __call__(self):
+        """Get all services of payment gateways registered.
+        """
+        return get_payment_providers()
 
 
@@ -49,5 +59,6 @@
 
 
-class Payment(grok.Model, Logger):
+@attrs_to_fields
+class Payment(grok.Container, Logger):
     """This is a payment.
     """
@@ -59,4 +70,15 @@
     logger_format_str = '"%(asctime)s","%(user)s",%(message)s'
 
+    @property
+    def amount(self):
+        """The amount of a payment.
+
+        Equals the sum of items contained.
+        """
+        return sum(
+            [item.amount for item in self.values()],
+            decimal.Decimal("0.00")  # default value
+        )
+
     def __init__(self):
         super(Payment, self).__init__()
@@ -64,8 +86,4 @@
         self.payment_date = None
         self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex)
-        self.gateway_service = None
-        self.amount = decimal.Decimal("0.00")
-        self.payed_item_id = None
-        self.payer_id = None
         self.state = STATE_UNPAID
         return
@@ -95,4 +113,27 @@
         notify(grok.ObjectModifiedEvent(self))
 
+    def add_payment_item(self, item):
+        """Add `item`
+
+        Returns the key under which the `item` was stored. Please do
+        not make anby assumptions about the key. It will be a
+        string. That is all we can tell.
+
+        """
+        cnt = 0
+        while str(cnt) in self:
+            cnt += 1
+        self[str(cnt)] = item
+        return str(cnt)
+
+
+@attrs_to_fields
+class Payer(object):
+    """A Payment is for testing.
+
+    It cannot be stored in ZODB.
+    """
+    grok.implements(IPayer)
+
 
 @attrs_to_fields
@@ -100,2 +141,14 @@
 
     grok.implements(IPaymentItem)
+
+    def __init__(self):
+        super(PaymentItem, self).__init__()
+
+
+@attrs_to_fields
+class Payee(object):
+    """Someone being paid.
+
+    This is for testing only and cannot be stored in ZODB.
+    """
+    grok.implements(IPayee)
Index: main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_demo_provider.py
===================================================================
--- main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_demo_provider.py	(revision 12671)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_demo_provider.py	(revision 12671)
@@ -0,0 +1,61 @@
+import decimal
+from zope.interface.verify import verifyClass, verifyObject
+from waeup.ikoba.testing import (
+    FunctionalLayer, FunctionalTestCase,
+    )
+from waeup.ikoba.payments.interfaces import (
+    IPaymentGatewayService, IPayment, STATE_UNPAID,
+    )
+from waeup.ikoba.payments.payment import Payer, PaymentItem, Payee
+from waeup.ikoba.payments.demo_provider import (
+    DemoCreditcardPaymentService,
+    )
+
+
+class DemoCreditcarPaymentServiceTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_iface(self):
+        # we fullfill all interface contracts
+        service = DemoCreditcardPaymentService()
+        verifyClass(IPaymentGatewayService, DemoCreditcardPaymentService)
+        verifyObject(IPaymentGatewayService, service)
+
+    def test_create_payment(self):
+        # we can get payments from payment gateways
+        service = DemoCreditcardPaymentService()
+        payer, payment_item, payee = Payer(), PaymentItem(), Payee()
+        payer.payer_id = u'SOME_PAYER_ID'
+        result = service.create_payment(payer, [], payee)
+        assert IPayment.providedBy(result)
+        assert result.gateway_service == u'demo_creditcard'
+        assert result.state == STATE_UNPAID
+        assert len(result) == 0  # no items stored
+
+    def test_create_payment_honors_payment_item(self):
+        # we inspect payment items and take their values
+        service = DemoCreditcardPaymentService()
+        payer, payment_item, payee = Payer(), PaymentItem(), Payee()
+        payment_item.item_id = u'SOME_ITEM_ID'
+        payer.payer_id = u'SOME_PAYER_ID'
+        payment_item.amount = decimal.Decimal("300.99")
+        result = service.create_payment(payer, [payment_item], payee)
+        self.assertEqual(result.amount, payment_item.amount)
+        assert len(result) == 1
+
+    def test_create_payment_honors_payer(self):
+        # we inspect payers when creating their payments
+        service = DemoCreditcardPaymentService()
+        payer, payment_item, payee = Payer(), PaymentItem(), Payee()
+        payment_item.item_id = u'SOME_ITEM_ID'
+        payer.payer_id = u'SOME_PAYER_ID'
+        result = service.create_payment(payer, [payment_item], payee)
+        assert result.payer_id == payer.payer_id
+
+    def test_get_service_by_utility_name(self):
+        # we can get the demo credit card service by its utility name
+        from zope.component import queryUtility
+        service = queryUtility(
+            IPaymentGatewayService, name="demo_creditcard")
+        assert service is not None
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 12550)
+++ main/waeup.ikoba/trunk/src/waeup/ikoba/payments/tests/test_payment.py	(revision 12671)
@@ -17,16 +17,18 @@
 ##
 import datetime
+import decimal
 import re
 import unittest
-from zope.component import getUtilitiesFor, getSiteManager
+from zope.component import getUtilitiesFor, getSiteManager, queryUtility
 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, IPaymentItem
+    IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister,
     )
 from waeup.ikoba.payments.payment import (
     Payment, get_payment_providers, PaymentItem,
     )
+from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase)
 
 
@@ -58,4 +60,19 @@
         assert result.keys() == ['some_name', ]
         assert result['some_name'] is fake_util
+
+
+class FunctionalHelperTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_services_lister_is_registered(self):
+        # a lister of gateway services is registered on startup
+        util = queryUtility(IPaymentGatewayServicesLister)
+        assert util is not None
+
+    def test_services_are_really_listed(self):
+        # we can really get locally registered gateways when calling
+        util = queryUtility(IPaymentGatewayServicesLister)
+        assert len(util()) > 0
 
 
@@ -113,4 +130,51 @@
         assert p1.state == STATE_FAILED
 
+    def test_add_payment_item(self):
+        # we can add payment items
+        p1 = Payment()
+        item1 = PaymentItem()
+        result = p1.add_payment_item(item1)
+        assert len(p1) == 1  # do not make assumptions about result content
+        assert isinstance(result, basestring)
+
+    def test_add_payment_item_multiple(self):
+        # we can add several items
+        p1 = Payment()
+        item1 = PaymentItem()
+        item2 = PaymentItem()
+        result1 = p1.add_payment_item(item1)
+        result2 = p1.add_payment_item(item2)
+        assert len(p1) == 2  # do not make assumptions about result content
+        assert isinstance(result1, basestring)
+        assert isinstance(result2, basestring)
+
+    def test_amount(self):
+        # the amount of a payment is the sum of amounts of its items
+        p1 = Payment()
+        item1 = PaymentItem()
+        item2 = PaymentItem()
+        p1.add_payment_item(item1)
+        p1.add_payment_item(item2)
+        item1.amount = decimal.Decimal("12.25")
+        item2.amount = decimal.Decimal("0.5")
+        assert p1.amount == decimal.Decimal("12.75")
+
+    def test_amount_negative(self):
+        # we can sum up negative numbers
+        p1 = Payment()
+        item1 = PaymentItem()
+        item2 = PaymentItem()
+        p1.add_payment_item(item1)
+        p1.add_payment_item(item2)
+        item1.amount = decimal.Decimal("2.21")
+        item2.amount = decimal.Decimal("-3.23")
+        assert p1.amount == decimal.Decimal("-1.02")
+
+    def test_amount_empty(self):
+        # the amount of zero items is 0.00.
+        p1 = Payment()
+        assert p1.amount == decimal.Decimal("0.00")
+        assert isinstance(p1.amount, decimal.Decimal)
+
 
 class PaymentItemTests(unittest.TestCase):
