Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/sample_payment_data.csv
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/sample_payment_data.csv	(revision 13872)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/sample_payment_data.csv	(revision 13872)
@@ -0,0 +1,2 @@
+applicant_id,p_state,p_category,p_id,p_session,r_amount_approved,amount_auth,creation_date,type
+dp2011_1234,paid,application,p1266236341955,2015,19500.1,19500.1,2010-11-25 20:16:33.757 WAT,online
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_batching.py	(revision 13657)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_batching.py	(revision 13872)
@@ -33,5 +33,6 @@
 from waeup.kofa.app import University
 from waeup.kofa.applicants.batching import (
-    ApplicantsContainerProcessor, ApplicantProcessor)
+    ApplicantsContainerProcessor, ApplicantProcessor,
+    ApplicantOnlinePaymentProcessor)
 from waeup.kofa.applicants.container import ApplicantsContainer
 from waeup.kofa.applicants.applicant import Applicant
@@ -39,5 +40,6 @@
 from waeup.kofa.university.department import Department
 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
-from waeup.kofa.interfaces import IBatchProcessor, IUserAccount
+from waeup.kofa.interfaces import (
+    IBatchProcessor, IUserAccount, DuplicationError)
 from waeup.kofa.applicants.workflow import CREATED
 
@@ -75,4 +77,11 @@
 
 APPLICANT_HEADER_FIELDS_UPDATE2 = APPLICANT_SAMPLE_DATA_UPDATE2.split(
+    '\n')[0].split(',')
+
+PAYMENT_SAMPLE_DATA = open(
+    os.path.join(os.path.dirname(__file__), 'sample_payment_data.csv'),
+    'rb').read()
+
+PAYMENT_HEADER_FIELDS = PAYMENT_SAMPLE_DATA.split(
     '\n')[0].split(',')
 
@@ -225,4 +234,7 @@
             self.application_number]
         self.workdir = tempfile.mkdtemp()
+
+        self.logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'applicants.log')
         return
 
@@ -252,7 +264,4 @@
         open(self.csv_file_update, 'wb').write(APPLICANT_SAMPLE_DATA_UPDATE)
         open(self.csv_file_update2, 'wb').write(APPLICANT_SAMPLE_DATA_UPDATE2)
-
-        self.logfile = os.path.join(
-            self.app['datacenter'].storage, 'logs', 'applicants.log')
 
     def test_interface(self):
@@ -415,2 +424,111 @@
             logcontent)
         shutil.rmtree(os.path.dirname(fin_file))
+
+class PaymentProcessorTest(ApplicantImportExportSetup):
+
+    def setUp(self):
+        super(PaymentProcessorTest, self).setUp()
+
+        applicant = Applicant()
+        applicant.firstname = u'Anna2'
+        applicant.lastname = u'Tester'
+        applicant.applicant_id = u'dp2011_1234'
+        self.app['applicants']['dp2011'].addApplicant(applicant)
+        payment = createObject(u'waeup.ApplicantOnlinePayment')
+        payment.p_id = 'p120'
+        payment.p_session = 2012
+        payment.p_category = 'application'
+        payment.p_state = 'paid'
+        applicant['p120'] = payment
+        self.applicant2 = applicant
+        self.processor = ApplicantOnlinePaymentProcessor()
+        self.csv_file = os.path.join(
+            self.workdir, 'sample_payment_data.csv')
+        open(self.csv_file, 'wb').write(PAYMENT_SAMPLE_DATA)
+
+    def test_interface(self):
+        # Make sure we fulfill the interface contracts.
+        assert verifyObject(IBatchProcessor, self.processor) is True
+        assert verifyClass(
+            IBatchProcessor, ApplicantOnlinePaymentProcessor) is True
+
+    def test_getEntry(self):
+        assert self.processor.getEntry(
+            dict(applicant_id='ID_NONE', p_id='nonsense'), self.app) is None
+        assert self.processor.getEntry(
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
+            self.app) is self.applicant2['p120']
+        assert self.processor.getEntry(
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p120#'),
+            self.app) is self.applicant2['p120']
+
+    def test_delEntry(self):
+        assert self.processor.getEntry(
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
+            self.app) is self.applicant2['p120']
+        self.assertEqual(len(self.applicant2.keys()),1)
+        self.processor.delEntry(
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
+            self.app)
+        assert self.processor.getEntry(
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
+            self.app) is None
+        self.assertEqual(len(self.applicant.keys()),0)
+
+    def test_addEntry(self):
+        self.assertEqual(len(self.applicant2.keys()),1)
+        self.processor.delEntry(
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
+            self.app)
+        payment1 = createObject(u'waeup.ApplicantOnlinePayment')
+        payment1.p_category = 'application'
+        payment1.p_id = 'p234'
+        self.processor.addEntry(
+            payment1, dict(applicant_id=self.applicant2.applicant_id, p_id='p234'),
+            self.app)
+        self.assertEqual(len(self.applicant2.keys()),1)
+        self.assertEqual(self.applicant2['p234'].p_id, 'p234')
+        # Same payment must not exist.
+        payment1.p_state = 'paid'
+        payment2 = createObject(u'waeup.ApplicantOnlinePayment')
+        payment2.p_id = 'p456'
+        payment2.p_category = 'application'
+        self.assertRaises(
+            DuplicationError, self.processor.addEntry, payment2,
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p456'), self.app)
+
+    def test_import(self):
+        num, num_warns, fin_file, fail_file = self.processor.doImport(
+            self.csv_file, PAYMENT_HEADER_FIELDS,'create')
+        self.assertEqual(num_warns,1)
+        fail_file = open(fail_file).read()
+        self.assertTrue('Payment has already been made' in fail_file)
+        self.processor.delEntry(
+            dict(applicant_id=self.applicant2.applicant_id, p_id='p120'),
+            self.app)
+        num, num_warns, fin_file, fail_file = self.processor.doImport(
+            self.csv_file, PAYMENT_HEADER_FIELDS,'create')
+        self.assertEqual(num_warns,0)
+        payment = self.processor.getEntry(dict(applicant_id='dp2011_1234',
+            p_id='p1266236341955'), self.app)
+        self.assertEqual(payment.p_id, 'p1266236341955')
+        cdate = payment.creation_date.strftime("%Y-%m-%d %H:%M:%S")
+        # Ooooh, still the old problem, see
+        # http://mail.dzug.org/mailman/archives/zope/2006-August/001153.html.
+        # WAT is interpreted as GMT-1 and not GMT+1
+        self.assertEqual(cdate, '2010-11-25 21:16:33')
+        self.assertEqual(str(payment.creation_date.tzinfo),'UTC')
+        shutil.rmtree(os.path.dirname(fin_file))
+        logcontent = open(self.logfile).read()
+        # Logging message from updateEntry
+        self.assertTrue(
+            'INFO - system - ApplicantOnlinePayment Processor - dp2011_1234 - '
+            'previous update cancelled'
+            in logcontent)
+        self.assertTrue(
+            'INFO - system - ApplicantOnlinePayment Processor - '
+            'sample_payment_data - dp2011_1234 - updated: p_id=p1266236341955, '
+            'creation_date=2010-11-25 21:16:33.757000+00:00, '
+            'r_amount_approved=19500.1, p_category=application, '
+            'amount_auth=19500.1, p_session=2015, p_state=paid'
+            in logcontent)
