Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7250)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7252)
@@ -531,5 +531,4 @@
         payment.surcharge_3 = academic_session.surcharge_3
         self.context[payment.p_id] = payment
-        #import pdb; pdb.set_trace()
         self.flash('Payment ticket created.')
         return
@@ -929,7 +928,11 @@
         return
 
+    # We explicitely want the forms to be validated before payment tickets
+    # can be created. If no validation is requested, use
+    # 'validator=NullValidator' in the grok.action directive
     @grok.action('Add online payment ticket')
     def addPaymentTicket(self, **data):
         self.redirect(self.url(self.context, '@@addafp'))
+        return
 
     @grok.action('Remove selected tickets')
@@ -983,11 +986,18 @@
 
     def dataNotComplete(self):
+        store = getUtility(IExtFileStore)
+        if not store.getFileByContext(self.context, attr=u'passport.jpg'):
+            return 'No passport picture uploaded.'
         if not self.request.form.get('confirm_passport', False):
-            return 'Passport confirmation box not ticked.'
+            return 'Passport picture confirmation box not ticked.'
         return False
 
+    # We explicitely want the forms to be validated before payment tickets
+    # can be created. If no validation is requested, use
+    # 'validator=NullValidator' in the grok.action directive
     @grok.action('Add online payment ticket')
     def addPaymentTicket(self, **data):
         self.redirect(self.url(self.context, '@@addafp'))
+        return
 
     @grok.action('Remove selected tickets')
@@ -999,4 +1009,5 @@
     @grok.action('Save')
     def save(self, **data):
+        #import pdb; pdb.set_trace()
         if self.passport_changed is False:  # False is not None!
             return # error during image upload. Ignore other values
@@ -1009,9 +1020,8 @@
         if self.passport_changed is False:  # False is not None!
             return # error during image upload. Ignore other values
-        self.applyData(self.context, **data)
-        self.context._p_changed = True
         if self.dataNotComplete():
             self.flash(self.dataNotComplete())
             return
+        self.applyData(self.context, **data)
         state = IWorkflowState(self.context).getState()
         # This shouldn't happen, but the application officer
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py	(revision 7250)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py	(revision 7252)
@@ -28,7 +28,8 @@
 from zope.security.interfaces import Unauthorized
 from zope.testbrowser.testing import Browser
-from hurry.workflow.interfaces import IWorkflowInfo
+from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
 from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
 from waeup.sirp.app import University
+from waeup.sirp.configuration import SessionConfiguration
 from waeup.sirp.applicants.container import ApplicantsContainer
 from waeup.sirp.applicants.applicant import Applicant
@@ -556,10 +557,81 @@
         return
 
+    def test_pay_acceptance_fee(self):
+        self.login()
+        self.browser.open(self.edit_path)
+        # Payment tickets can't be created before the form has been validated
+        self.browser.getControl("Add online payment ticket").click()
+        self.assertTrue('Required input is missing' in self.browser.contents)
+        self.fill_correct_values()
+        # We have to savethe form otherwise the filled fields will be cleared
+        # after adding an online payment, because adding an online payment
+        # requires a filled form but does not save it
+        self.browser.getControl("Save").click()
+        self.browser.getControl("Add online payment ticket").click()
+        # Session object missing
+        self.assertTrue(
+            'Session configuration object is not available'
+            in self.browser.contents)
+        configuration = SessionConfiguration()
+        configuration.academic_session = 2009
+        configuration.acceptance_fee = 200
+        self.app['configuration'].addSessionConfiguration(configuration)
+        self.browser.open(self.edit_path)
+        self.fill_correct_values()
+        self.browser.getControl("Add online payment ticket").click()
+        self.assertMatches('...Payment ticket created...', self.browser.contents)
+        # Payment ticket can be removed if they haven't received a valid callback
+        ctrl = self.browser.getControl(name='val_id')
+        value = ctrl.options[0]
+        ctrl.getControl(value=value).selected = True
+        self.browser.getControl("Remove selected", index=0).click()
+        self.assertMatches('...Successfully removed...', self.browser.contents)
+        # We will try the callback request view
+        self.browser.getControl("Add online payment ticket").click()
+        ctrl = self.browser.getControl(name='val_id')
+        value = ctrl.options[0]
+        self.browser.getLink(value).click()
+        self.assertMatches('...Amount Authorized...',
+                           self.browser.contents)
+        payment_url = self.browser.url
+        # The pdf payment receipt can't yet be opened
+        self.browser.open(payment_url + '/payment_receipt.pdf')
+        self.assertMatches('...Ticket not yet paid...',
+                           self.browser.contents)
+        # Request callback
+        self.browser.open(payment_url)
+        self.browser.getLink("Request callback").click()
+        self.assertMatches('...Valid callback received...',
+                          self.browser.contents)
+        # Callback can't be applied twice
+        self.browser.open(payment_url + '/callback')
+        self.assertMatches('...This ticket has already been paid...',
+                          self.browser.contents)
+        # The payment receipt can be downloaded now
+        self.browser.open(payment_url)
+        self.browser.getLink("Download payment receipt").click()
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
+        # Applicant is is in state 'paid'
+        self.browser.open(self.view_path)
+        self.assertMatches('...paid...',
+                           self.browser.contents)
+        state = IWorkflowState(self.applicant).getState()
+        self.assertTrue(state == 'paid')
+
     def test_final_submit(self):
         # Make sure that a correctly filled form with passport picture
-        # can be submitted
+        # can be submitted (only) after payment
         self.login()
         self.browser.getLink("Edit application record").click()
+        self.assertFalse('Final Submit' in self.browser.contents)
+        IWorkflowInfo(self.applicant).fireTransition('pay')
+        self.browser.open(self.edit_path)
+        self.assertTrue('Final Submit' in self.browser.contents)
         self.fill_correct_values() # fill other fields with correct values
+        self.browser.getControl("Final Submit").click()
+        # We forgot to upload a passport picture
+        self.assertTrue(
+            'No passport picture uploaded' in self.browser.contents)
         # Create a pseudo image file and select it to be uploaded in form
         pseudo_image = StringIO('I pretend to be a graphics file')
@@ -568,21 +640,8 @@
         file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
         self.browser.getControl("Final Submit").click() # (finally) submit form
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.assertTrue(
-            'Passport confirmation box not ticked' in self.browser.contents)
-        self.browser.getControl(name="confirm_passport").value = True
-        IWorkflowInfo(self.applicant).fireTransition('submit')
-        self.browser.getControl("Final Submit").click() # submit form again
-        self.assertTrue(
-            'Wrong state' in self.browser.contents)
-        IWorkflowInfo(self.applicant).fireTransition('reset1')
-        # Now do the whole thing again but with correct state
-        self.login()
-        self.browser.open(self.edit_path)
-        self.fill_correct_values()
-        pseudo_image = StringIO('I pretend to be a graphics file')
-        ctrl = self.browser.getControl(name='form.passport')
-        file_ctrl = ctrl.mech_control
-        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
+        # The picture has been uploaded but the form cannot be submitted
+        # since the passport confirmation box was not ticked
+        self.assertTrue(
+            'Passport picture confirmation box not ticked' in self.browser.contents)
         self.browser.getControl(name="confirm_passport").value = True
         self.browser.getControl("Final Submit").click()
