Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/browser.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/browser.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/browser.py	(revision 12734)
@@ -43,5 +43,5 @@
 from waeup.ikoba.payments.payment import format_payment_item_values
 from waeup.ikoba.payments.interfaces import (
-    IPaymentGatewayServicesLister, IPaymentGatewayService, IPayer
+    IPaymentGatewayServicesLister, IPaymentGatewayService, IPayer, IPayable
     )
 from waeup.ikoba.widgets.hrefwidget import HREFDisplayWidget
@@ -56,5 +56,4 @@
 from waeup.ikoba.customers.catalog import search
 from waeup.ikoba.customers.workflow import PAYMENT_TRANSITIONS
-from waeup.ikoba.customers.contracts import payment_items_from_contract
 
 
@@ -1528,7 +1527,8 @@
                 return
             payer = IPayer(self.context)
-            payment_items = payment_items_from_contract(self.context)
-            payment = service.create_payment(payer, payment_items)
-            payment.payable_id = self.context.contract_id
+
+            payable = IPayable(self.context)
+            payment = service.create_payment(payer, payable)
+            service.store(payment)
             payment, view_name = service.next_step(payment.payment_id)
             url = self.url(payment, view_name)
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/contracts.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/contracts.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/customers/contracts.py	(revision 12734)
@@ -286,5 +286,5 @@
     @property
     def title(self):
-        return self.context.title
+        return self.context.title or u''
 
     @property
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/demo_provider.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/demo_provider.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/demo_provider.py	(revision 12734)
@@ -2,7 +2,9 @@
 from waeup.ikoba.interfaces import MessageFactory as _
 from waeup.ikoba.browser.layout import IkobaEditFormPage, action
-from waeup.ikoba.payments.interfaces import IPaymentGatewayService, IPayment
+from waeup.ikoba.payments.interfaces import (
+    IPaymentGatewayService, IPayment, IPayable, IPayer, IPayee,)
 from waeup.ikoba.payments.payment import (
-    Payment, get_payment, find_payable_from_payable_id)
+    Payment, get_payment, find_payable_from_payable_id,
+    PaymentProviderServiceBase)
 
 
@@ -10,5 +12,5 @@
 
 
-class DemoCreditcardPaymentService(grok.GlobalUtility):
+class DemoCreditcardPaymentService(PaymentProviderServiceBase):
     """A demo payment gateway service.
 
@@ -20,15 +22,15 @@
     title = _(u'Credit Card (Demo Payments)')
 
-    def create_payment(self, payer, payment_item_list=[],  payee=None):
+    def create_payment(self, payer, payable,  payee=None):
         """Create a payment.
         """
-        payment = Payment()
+        if not IPayer.providedBy(payer):
+            payer = IPayer(payer)
+        if not IPayable.providedBy(payable):
+            payable = IPayable(payable)
+        if (payee is not None) and (not IPayee.providedBy(payee)):
+            payee = IPayee(payee)
+        payment = Payment(payer, payable, payee)
         payment.gateway_service = 'demo_creditcard'  # must be grok.name above
-        payment.payer_id = payer.payer_id
-        # XXX: we should not have to store a payment before adding items
-        site = grok.getSite()
-        site['payments'][payment.payment_id] = payment
-        for item in payment_item_list:
-            payment.add_payment_item(item)
         return payment
 
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/interfaces.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/interfaces.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/interfaces.py	(revision 12734)
@@ -20,6 +20,4 @@
 from zope import schema
 from zope.component import getUtilitiesFor
-from zope.container.interfaces import IContainer
-from zope.container.constraints import contains
 from zope.interface import Interface, Attribute
 from waeup.ikoba.interfaces import (
@@ -88,9 +86,9 @@
         )
 
-    def create_payment(payer, payment_item_list, payee):
+    def create_payment(payer, payable, payee):
         """Create a payment.
 
         For all parameters we expect an object, that implements
-        `IPayer`, `IPaymentItem`, or `IPayee` respectively. If not,
+        `IPayer`, `IPayable`, or `IPayee` respectively. If not,
         then the given objects must be at least adaptable to the
         respective interface.
@@ -106,4 +104,8 @@
 
         May result in (None, None).
+        """
+
+    def store(payment):
+        """Store `payment` in site.
         """
 
@@ -232,5 +234,5 @@
 
 
-class IPayment(IContainer, IIkobaObject):
+class IPayment(IIkobaObject):
     """A base representation of payments.
 
@@ -256,9 +258,13 @@
     we mark the payment 'failed'.
     """
-    contains(IPaymentItem)
-
     payment_id = schema.TextLine(
         title=u'Payment Identifier',
         default=None,
+        required=True,
+        )
+
+    title = schema.TextLine(
+        title=u'Payment description.',
+        default=u'',
         required=True,
         )
@@ -333,10 +339,4 @@
         """
 
-    def add_payment_item(item):
-        """Payments contain payment items.
-
-        Add one
-        """
-
 
 class IPayer(Interface):
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/payment.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/payment.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/payment.py	(revision 12734)
@@ -101,5 +101,6 @@
 
 class PaymentProviderServiceBase(grok.GlobalUtility):
-
+    """Base for IPaymentGatewayServices.
+    """
     grok.baseclass()
     grok.implements(IPaymentGatewayService)
@@ -107,7 +108,16 @@
     title = u'Sample Credit Card Service'
 
-
-@attrs_to_fields
-class Payment(grok.Container, Logger):
+    def store(self, payment):
+        """Store `payment` in site.
+        """
+        site = grok.getSite()
+        payments = site['payments']
+        if payment.payment_id in payments:
+            del site['payments'][payment.payment_id]
+        site['payments'][payment.payment_id] = payment
+
+
+@attrs_to_fields
+class Payment(grok.Model, Logger):
     """This is a payment.
     """
@@ -119,21 +129,19 @@
     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):
+    def __init__(self, payer, payable, payee=None):
         super(Payment, self).__init__()
+        item_amounts = [decimal.Decimal("0.00"), ]
+        item_amounts += [item.amount for item in payable.payment_items]
+        self.amount = sum(item_amounts)
+        self.payer_id = payer.payer_id
+        self.payable_id = payable.payable_id
+        self.title = payable.title
         self.creation_date = datetime.utcnow()
         self.payment_date = None
         self.payment_id = u'PAY_' + unicode(uuid.uuid4().hex)
         self.state = STATE_UNPAID
+        self.currency = payable.currency
+        if payee is not None:
+            self.payee_id = payee.payee_id
         return
 
@@ -162,18 +170,4 @@
         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
@@ -187,10 +181,14 @@
 
 @attrs_to_fields
-class PaymentItem(grok.Model):
+class PaymentItem(object):
 
     grok.implements(IPaymentItem)
 
-    def __init__(self):
+    def __init__(
+            self, item_id=u"0", title=u"", amount=decimal.Decimal("0.00")):
         super(PaymentItem, self).__init__()
+        self.item_id = item_id
+        self.title = title
+        self.amount = amount
 
 
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/paypal.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/paypal.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/paypal.py	(revision 12734)
@@ -35,4 +35,5 @@
     IPayment, IPaymentGatewayService, IPayer, IPaymentItem, IPayee,
     )
+from waeup.ikoba.payments.payment import PaymentProviderServiceBase
 from waeup.ikoba.payments.paypal_countries import COUNTRIES_VOCAB
 from waeup.ikoba.payments.paypal_currencies import CURRENCIES_VOCAB
@@ -1141,5 +1142,5 @@
 
 
-class PayPalCreditCardService(grok.GlobalUtility):
+class PayPalCreditCardService(PaymentProviderServiceBase):
     grok.implements(IPaymentGatewayService)
     grok.name('paypal_creditcard')
@@ -1220,5 +1221,5 @@
 
 
-class PayPalRegularPaymentService(grok.GlobalUtility):
+class PayPalRegularPaymentService(PaymentProviderServiceBase):
     grok.implements(IPaymentGatewayService)
     grok.name('paypal_regular')
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_demo_provider.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_demo_provider.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_demo_provider.py	(revision 12734)
@@ -1,3 +1,2 @@
-import decimal
 from zope.component import queryUtility
 from zope.component.hooks import setSite
@@ -10,8 +9,9 @@
     )
 from waeup.ikoba.app import Company
-from waeup.ikoba.payments.payment import Payer, PaymentItem, Payee, Payment
+from waeup.ikoba.payments.payment import Payer, Payee, Payment
 from waeup.ikoba.payments.demo_provider import (
     DemoCreditcardPaymentService,
     )
+from waeup.ikoba.payments.tests.test_payment import FakePayer, FakePayable
 
 
@@ -35,30 +35,17 @@
         # we can get payments from payment gateways
         service = DemoCreditcardPaymentService()
-        payer, payment_item, payee = Payer(), PaymentItem(), Payee()
+        payer, payable, payee = Payer(), FakePayable(), Payee()
         payer.payer_id = u'SOME_PAYER_ID'
-        result = service.create_payment(payer, [], payee)
+        result = service.create_payment(payer, payable, 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, payable, payee = Payer(), FakePayable(), Payee()
         payer.payer_id = u'SOME_PAYER_ID'
-        result = service.create_payment(payer, [payment_item], payee)
+        result = service.create_payment(payer, payable, payee)
         assert result.payer_id == payer.payer_id
 
@@ -78,5 +65,5 @@
         # we are redirected to 'index' in the beginning
         service = DemoCreditcardPaymentService()
-        p1 = Payment()
+        p1 = Payment(FakePayer(), FakePayable())
         self.app['payments']['1'] = p1
         p_id = p1.payment_id
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_payment.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_payment.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_payment.py	(revision 12734)
@@ -24,10 +24,10 @@
     )
 from zope.component.hooks import setSite
-from zope.interface import implements
+from zope.interface import implements, implementer
 from zope.interface.verify import verifyClass, verifyObject
 from waeup.ikoba.payments.interfaces import (
     IPayment, STATE_UNPAID, STATE_PAID, STATE_FAILED,
     IPaymentGatewayService, IPaymentItem, IPaymentGatewayServicesLister,
-    IPayableFinder,
+    IPayableFinder, IPayable, IPayer,
     )
 from waeup.ikoba.app import Company
@@ -37,4 +37,37 @@
     )
 from waeup.ikoba.testing import (FunctionalLayer, FunctionalTestCase)
+
+
+@implementer(IPayer)
+class FakePayer(object):
+
+    def __init__(
+        self, payer_id=u'PAYER_01', first_name=u'Anna', last_name='Tester'):
+        self.payer_id = payer_id
+        self.first_name = first_name
+        self.last_name = last_name
+
+
+FAKE_PAYMENT_ITEMS = (
+    PaymentItem(u'ITEM1', u'Item title 1', decimal.Decimal("1.00")),
+    PaymentItem(u'ITEM2', u'Item title 2', decimal.Decimal("2.2")),
+    )
+
+
+@implementer(IPayable)
+class FakePayable(object):
+
+    payable_id = u'id1'
+    items = (
+        (u'item 1', decimal.Decimal("1.00")),
+        (u'item 2', decimal.Decimal("2.12")),
+        )
+
+    def __init__(self, payable_id=u'PAYABLE_01', title=u'title',
+                 currency=u'USD', payment_items=FAKE_PAYMENT_ITEMS):
+        self.payable_id = payable_id
+        self.title = title
+        self.currency = currency
+        self.payment_items = payment_items
 
 
@@ -103,12 +136,4 @@
         assert len(util()) > 0
 
-    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_get_payment(self):
         # we can lookup payments.
@@ -116,8 +141,6 @@
         app = self.getRootFolder()['app']
         setSite(app)
-        p1 = Payment()
-        item1 = PaymentItem()
+        p1 = Payment(FakePayer(), FakePayable())
         app['payments']['1'] = p1
-        p1.add_payment_item(item1)
         p_id = p1.payment_id
         result = get_payment(p_id)
@@ -155,15 +178,39 @@
 
 
-class PaymentTests(unittest.TestCase):
+class PaymentTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(PaymentTests, self).setUp()
+        self.payer = FakePayer()
+        self.payable = FakePayable()
 
     def test_iface(self):
         # Payments fullfill any interface contracts
-        obj = Payment()
+        obj = Payment(self.payer, self.payable)
         verifyClass(IPayment, Payment)
         verifyObject(IPayment, obj)
 
+    def test_initial_values(self):
+        # important attributes are set initially
+        payer = self.payer
+        payer.payer_id = u'PAYER_ID'
+        payable = self.payable
+        payable.payable_id = u'PAYABLE_ID'
+        payable.title = u'PAYABLE-TITLE'
+        payable.currency = 'NGN'
+        payment = Payment(payer, payable)
+        assert payment.payer_id == u'PAYER_ID'
+        assert payment.payable_id == u'PAYABLE_ID'
+        assert payment.title == u'PAYABLE-TITLE'
+        assert payment.currency == 'NGN'
+        assert isinstance(payment.creation_date, datetime.datetime)
+        assert payment.payment_date is None
+
     def test_payment_id_unique(self):
         # we get unique payment ids
-        p1, p2 = Payment(), Payment()
+        p1 = Payment(self.payer, self.payable)
+        p2 = Payment(self.payer, self.payable)
         id1, id2 = p1.payment_id, p2.payment_id
         assert id1 != id2
@@ -171,5 +218,5 @@
     def test_payment_id_format(self):
         # payment ids have a special format: "PAY_<32 hex digits>"
-        id1 = Payment().payment_id
+        id1 = Payment(self.payer, self.payable).payment_id
         assert isinstance(id1, basestring)
         assert re.match('PAY_[0-9a-f]{32}', id1)
@@ -177,10 +224,10 @@
     def test_initial_state_is_unpaid(self):
         # the initial state of payments is <unpaid>
-        p1 = Payment()
+        p1 = Payment(self.payer, self.payable)
         assert p1.state == STATE_UNPAID
 
     def test_approve(self):
         # we can approve payments
-        p1 = Payment()
+        p1 = Payment(self.payer, self.payable)
         p1.approve()
         assert p1.state == STATE_PAID
@@ -190,5 +237,5 @@
     def test_approve_datetime_given(self):
         # we can give a datetime
-        p1 = Payment()
+        p1 = Payment(self.payer, self.payable)
         some_datetime = datetime.datetime(2014, 1, 1, 0, 0, 0)
         p1.approve(payment_date=some_datetime)
@@ -198,5 +245,5 @@
         # if we do not give a datetime, current one will be used
         current = datetime.datetime.utcnow()
-        p1 = Payment()
+        p1 = Payment(self.payer, self.payable)
         p1.approve()
         assert p1.payment_date >= current
@@ -204,54 +251,29 @@
     def test_mark_failed(self):
         # we can mark payments as failed
-        p1 = Payment()
+        p1 = Payment(self.payer, self.payable)
         p1.mark_failed()
         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")
+        payable = self.payable
+        payable.payment_items[0].amount = decimal.Decimal("12.25")
+        payable.payment_items[1].amount = decimal.Decimal("0.5")
+        p1 = Payment(self.payer, self.payable)
         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")
+        payable = self.payable
+        payable.payment_items[0].amount = decimal.Decimal("2.21")
+        payable.payment_items[1].amount = decimal.Decimal("-3.23")
+        p1 = Payment(self.payer, payable)
         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)
+        # the amount of zero items is None.
+        payable = FakePayable(payment_items=())
+        p1 = Payment(self.payer, payable)
+        self.assertEqual(p1.amount, decimal.Decimal("0.00"))
 
 
Index: main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_paypal.py
===================================================================
--- main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_paypal.py	(revision 12733)
+++ main/waeup.ikoba/branches/uli-payments/src/waeup/ikoba/payments/tests/test_paypal.py	(revision 12734)
@@ -1174,5 +1174,5 @@
     def test_store_credit_card_invalid(self):
         # an exception is raised with invalid credit cards.
-        site = self.create_site()
+        self.create_site()
         service = PayPalCreditCardService()
         credit_card = CreditCard(
@@ -1203,5 +1203,5 @@
         # we can actually create payments
         service = PayPalCreditCardService()
-        site = self.create_site()
+        self.create_site()
         credit_card = self.get_credit_card()
         result = service.store_credit_card(credit_card)
