Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 8420)
@@ -591,13 +591,11 @@
             'b':self.context.p_id})
 
-class OnlinePaymentCallbackPage(UtilityView, grok.View):
-    """ Callback view
+class OnlinePaymentApprovePage(UtilityView, grok.View):
+    """ Approval view
     """
     grok.context(IApplicantOnlinePayment)
-    grok.name('simulate_callback')
-    grok.require('waeup.payApplicant')
-
-    # This update method simulates a valid callback und must be
-    # neutralized in the customization package.
+    grok.name('approve')
+    grok.require('waeup.managePortal')
+
     def update(self):
         self.wf_info = IWorkflowInfo(self.context.__parent__)
@@ -607,8 +605,5 @@
             self.flash('Error: %s' % sys.exc_info()[1])
             return
-        self.context.r_amount_approved = self.context.amount_auth
-        self.context.r_code = u'00'
-        self.context.p_state = 'paid'
-        self.context.payment_date = datetime.utcnow()
+        self.context.approve()
         ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
         self.context.__parent__.loggerInfo(
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 8420)
@@ -696,13 +696,12 @@
         #self.assertMatches('...Ticket not yet paid...',
         #                   self.browser.contents)
-        # Request callback
-        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 + '/simulate_callback')
-        self.assertMatches(
-            "...Transition 'pay' requires 'started' as source state...",
-            self.browser.contents)
+        # Approve payment
+        # Applicants can't approve payments
+        self.assertRaises(
+            Unauthorized, self.browser.open, payment_url + '/approve')
+        # We approve the payment by bypassing the view
+        payment_id = self.applicant.keys()[0]
+        payment = self.applicant[payment_id]
+        payment.approve()
         # The payment slip can be downloaded now
         self.browser.open(payment_url)
@@ -711,10 +710,28 @@
         self.assertEqual(self.browser.headers['Content-Type'],
                          'application/pdf')
-        # Applicant is is in state 'paid'
+        # Applicant is is not yet in state 'paid' because it was only
+        # the payment which we set to paid
         self.browser.open(self.view_path)
-        self.assertMatches('...paid...',
+        self.assertMatches('...started...',
                            self.browser.contents)
         state = IWorkflowState(self.applicant).getState()
+        self.assertTrue(state == 'started')
+        # Let's logout and approve the payment as manager
+        self.browser.getLink("Logout").click()
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        # First we reset the payment
+        payment.r_amount_approved = 0.0
+        payment.r_code = u''
+        payment.p_state = 'unpaid'
+        payment.r_desc = u''
+        payment.payment_date = None
+        self.browser.open(payment_url)
+        self.browser.getLink("Approve payment").click()
+        self.assertEqual(payment.p_state, 'paid')
+        self.assertEqual(payment.r_amount_approved, 200.0)
+        self.assertEqual(payment.r_code, 'AP')
+        state = IWorkflowState(self.applicant).getState()
         self.assertTrue(state == 'paid')
+        return
 
     def test_final_submit(self):
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py	(revision 8420)
@@ -185,15 +185,13 @@
         return self.view.url(self.view.context, self.target)
 
-class RequestCallbackActionButton(ManageActionButton):
+class ApprovePaymentActionButton(ManageActionButton):
     grok.order(2)
     grok.context(IApplicantOnlinePayment)
     grok.view(OnlinePaymentDisplayFormPage)
-    grok.require('waeup.payApplicant')
+    grok.require('waeup.managePortal')
     icon = 'actionicon_call.png'
-    text = _('Request callback')
-    target = 'simulate_callback'
-
-    # This button must be neutralized
-    # in the customization package.
+    text = _('Approve payment')
+    target = 'approve'
+
     @property
     def target_url(self):
Index: main/waeup.kofa/trunk/src/waeup/kofa/payments/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/payments/interfaces.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/payments/interfaces.py	(revision 8420)
@@ -130,2 +130,5 @@
         readonly = False,
         )
+
+    def approve():
+        "Approve an online payment and set to paid."
Index: main/waeup.kofa/trunk/src/waeup/kofa/payments/payment.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/payments/payment.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/payments/payment.py	(revision 8420)
@@ -24,8 +24,9 @@
 from zope.component import getUtility
 from waeup.kofa.interfaces import IKofaUtils
+from waeup.kofa.interfaces import MessageFactory as _
 from waeup.kofa.payments.interfaces import (
     IPayment, ISCPayment, IOnlinePayment,
     payment_states, payment_categories)
-from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.utils.helpers import attrs_to_fields, get_current_principal
 
 class Payment(grok.Container):
@@ -50,4 +51,5 @@
         return payment_categories.getTermByToken(self.p_category).title
 
+# not used
 class SCPayment(Payment):
     """This is a scratch card payment.
@@ -74,3 +76,12 @@
         return
 
+    def approve(self):
+        "Approve online payment and set to paid."
+        self.r_amount_approved = self.amount_auth
+        self.r_code = u'AP'
+        self.p_state = 'paid'
+        #user = get_current_principal()
+        self.payment_date = datetime.utcnow()
+        return
+
 OnlinePayment = attrs_to_fields(OnlinePayment)
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 8420)
@@ -32,5 +32,5 @@
 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
 from waeup.kofa.accesscodes import (
-    invalidate_accesscode, get_access_code, create_accesscode)
+    invalidate_accesscode, get_access_code)
 from waeup.kofa.accesscodes.workflow import USED
 from waeup.kofa.browser import (
@@ -1130,51 +1130,16 @@
             'b':self.context.p_id})
 
-class OnlinePaymentCallbackPage(UtilityView, grok.View):
+class OnlinePaymentApprovePage(UtilityView, grok.View):
     """ Callback view
     """
     grok.context(IStudentOnlinePayment)
-    grok.name('simulate_callback')
-    grok.require('waeup.payStudent')
-
-    # This update method simulates a valid callback und must be
-    # neutralized in the customization package.
+    grok.name('approve')
+    grok.require('waeup.managePortal')
+
     def update(self):
-        if self.context.p_state == 'paid':
-            self.flash(_('This ticket has already been paid.'))
-            return
-        student = self.context.getStudent()
-        write_log_message(self,'valid callback: %s' % self.context.p_id)
-        self.context.r_amount_approved = self.context.amount_auth
-        self.context.r_code = u'00'
-        self.context.p_state = 'paid'
-        self.context.payment_date = datetime.utcnow()
-        if self.context.p_category == 'clearance':
-            # Create CLR access code
-            pin, error = create_accesscode(
-                'CLR',0,self.context.amount_auth,student.student_id)
-            if error:
-                self.flash(_('Valid callback received. ${a}',
-                    mapping = {'a':error}))
-                return
-            self.context.ac = pin
-        elif self.context.p_category == 'schoolfee':
-            # Create SFE access code
-            pin, error = create_accesscode(
-                'SFE',0,self.context.amount_auth,student.student_id)
-            if error:
-                self.flash(_('Valid callback received. ${a}',
-                    mapping = {'a':error}))
-                return
-            self.context.ac = pin
-        elif self.context.p_category == 'bed_allocation':
-            # Create HOS access code
-            pin, error = create_accesscode(
-                'HOS',0,self.context.amount_auth,student.student_id)
-            if error:
-                self.flash(_('Valid callback received. ${a}',
-                    mapping = {'a':error}))
-                return
-            self.context.ac = pin
-        self.flash(_('Valid callback received.'))
+        success, msg = self.context.approveStudentPayment()
+        if success:
+            write_log_message(self,'valid callback: %s' % self.context.p_id)
+        self.flash(msg)
         return
 
@@ -1182,4 +1147,14 @@
         self.redirect(self.url(self.context, '@@index'))
         return
+
+class OnlinePaymentFakeApprovePage(OnlinePaymentApprovePage):
+    """ Approval view for students.
+
+    This view is used for browser tests only and
+    must be neutralized in custom pages!
+    """
+
+    grok.name('fake_approve')
+    grok.require('waeup.payStudent')
 
 class ExportPDFPaymentSlipPage(UtilityView, grok.View):
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 8420)
@@ -510,4 +510,9 @@
         )
 
+    def approveStudentPayment():
+        """Approve payment and create respective activation codes.
+
+        """
+
 IStudentOnlinePayment['p_level'].order = IStudentOnlinePayment[
     'p_session'].order
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/payments.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/payments.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/payments.py	(revision 8420)
@@ -22,8 +22,10 @@
 from zope.component.interfaces import IFactory
 from zope.interface import implementedBy
+from waeup.kofa.interfaces import MessageFactory as _
 from waeup.kofa.students.interfaces import (
     IStudentPaymentsContainer, IStudentNavigation, IStudentOnlinePayment)
 from waeup.kofa.payments import PaymentsContainer, OnlinePayment
 from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.accesscodes import create_accesscode
 
 class StudentPaymentsContainer(PaymentsContainer):
@@ -58,4 +60,37 @@
             return None
 
+    def approveStudentPayment(self):
+        """Approve payment and create respective activation code.
+        """
+        if self.p_state == 'paid':
+            return False, _('This ticket has already been paid.')
+        student = self.getStudent()
+        self.approve()
+        if self.p_category == 'clearance':
+            # Create CLR access code
+            pin, error = create_accesscode(
+                'CLR',0,self.amount_auth,student.student_id)
+            if error:
+                return False, _('Valid callback received. ${a}',
+                    mapping = {'a':error})
+            self.ac = pin
+        elif self.p_category == 'schoolfee':
+            # Create SFE access code
+            pin, error = create_accesscode(
+                'SFE',0,self.amount_auth,student.student_id)
+            if error:
+                return False, _('Valid callback received. ${a}',
+                    mapping = {'a':error})
+            self.ac = pin
+        elif self.p_category == 'bed_allocation':
+            # Create HOS access code
+            pin, error = create_accesscode(
+                'HOS',0,self.amount_auth,student.student_id)
+            if error:
+                return False, _('Valid callback received. ${a}',
+                    mapping = {'a':error})
+            self.ac = pin
+        return True, _('Valid callback received.')
+
 StudentOnlinePayment = attrs_to_fields(StudentOnlinePayment)
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 8420)
@@ -176,4 +176,5 @@
         configuration.academic_session = 2004
         configuration.clearance_fee = 3456.0
+        configuration.booking_fee = 123.4
         self.app['configuration'].addSessionConfiguration(configuration)
 
@@ -1288,8 +1289,8 @@
                            self.browser.contents)
 
-        # Managers can open the callback view which simulates a valid callback
+        # Managers can approve the payment
         self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
         self.browser.open(payment_url)
-        self.browser.getLink("Request callback").click()
+        self.browser.getLink("Approve payment").click()
         self.assertMatches('...Valid callback received...',
                           self.browser.contents)
@@ -1299,6 +1300,6 @@
             self.app['accesscodes']['SFE-0'].values()[0].cost,40000.0)
 
-        # Callback can't be applied twice
-        self.browser.open(payment_url + '/simulate_callback')
+        # Payments can't be approved twice
+        self.browser.open(payment_url + '/approve')
         self.assertMatches('...This ticket has already been paid...',
                           self.browser.contents)
@@ -1337,10 +1338,10 @@
                            self.browser.contents)
 
-        # Managers can open the callback view which simulates a valid callback
+        # Managers can approve the payment
         self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
         ctrl = self.browser.getControl(name='val_id')
         value = ctrl.options[1] # The clearance payment is the second in the table
         self.browser.getLink(value).click()
-        self.browser.open(self.browser.url + '/simulate_callback')
+        self.browser.open(self.browser.url + '/approve')
         self.assertMatches('...Valid callback received...',
                           self.browser.contents)
@@ -1372,5 +1373,5 @@
                            self.browser.contents)
 
-        # Students can open the callback view which simulates a valid callback
+        # Students can't approve the payment
         self.assertEqual(len(self.app['accesscodes']['CLR-0']),0)
         ctrl = self.browser.getControl(name='val_id')
@@ -1378,5 +1379,8 @@
         self.browser.getLink(value).click()
         payment_url = self.browser.url
-        self.browser.open(payment_url + '/simulate_callback')
+        self.assertRaises(
+            Unauthorized, self.browser.open, payment_url + '/approve')
+        # In the base package they can 'use' a fake approval view
+        self.browser.open(payment_url + '/fake_approve')
         self.assertMatches('...Valid callback received...',
                           self.browser.contents)
@@ -1385,5 +1389,14 @@
           <span>Paid</span>
         </td>...'''
+        expected = '''...
+        <td>
+          <span>Paid</span>
+        </td>...'''
         self.assertMatches(expected,self.browser.contents)
+        payment_id = self.student['payments'].keys()[0]
+        payment = self.student['payments'][payment_id]
+        self.assertEqual(payment.p_state, 'paid')
+        self.assertEqual(payment.r_amount_approved, 3456.0)
+        self.assertEqual(payment.r_code, 'AP')
         # The new CLR-0 pin has been created
         self.assertEqual(len(self.app['accesscodes']['CLR-0']),1)
@@ -1441,7 +1454,7 @@
         self.assertEqual(self.student['payments'][value].p_level, 200)
 
-        # Students can open the callback view which simulates a valid callback
+        # We simulate the approval
         self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
-        self.browser.open(self.browser.url + '/simulate_callback')
+        self.browser.open(self.browser.url + '/fake_approve')
         self.assertMatches('...Valid callback received...',
                           self.browser.contents)
@@ -1453,5 +1466,5 @@
             LookupError, self.browser.getControl, name='val_id')
         self.browser.open(self.payments_path + '/addop')
-        self.browser.getControl(name="form.p_category").value = ['gown']
+        self.browser.getControl(name="form.p_category").value = ['bed_allocation']
         self.browser.getControl("Create ticket").click()
         self.browser.open(self.payments_path)
@@ -1495,5 +1508,5 @@
         value = ctrl.options[0]
         self.browser.getLink(value).click()
-        self.browser.open(self.browser.url + '/simulate_callback')
+        self.browser.open(self.browser.url + '/approve')
         # The new HOS-0 pin has been created
         self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
@@ -1608,5 +1621,5 @@
         return
 
-    def test_student_accommodation(self):
+    def xx_test_student_accommodation(self):
         # Login
         self.browser.open(self.login_path)
@@ -1624,5 +1637,5 @@
         value = ctrl.options[0]
         self.browser.getLink(value).click()
-        self.browser.open(self.browser.url + '/simulate_callback')
+        self.browser.open(self.browser.url + '/fake_approve')
         # The new HOS-0 pin has been created
         self.assertEqual(len(self.app['accesscodes']['HOS-0']),1)
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py	(revision 8420)
@@ -266,5 +266,5 @@
             academic_session = grok.getSite()['configuration'][session]
         except KeyError:
-            details['error'] = u'Session configuration object is not available.'
+            details['error'] = _(u'Session configuration object is not available.')
             return details
         if category == 'schoolfee':
@@ -284,4 +284,6 @@
             details['p_item'] = self.getAccommodationDetails(student)['bt']
             details['amount'] = academic_session.booking_fee
+        if details['amount'] in (0.0, None):
+            details['error'] = _(u'Amount could not be determined.')
         return details
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py	(revision 8418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py	(revision 8420)
@@ -328,15 +328,14 @@
         return self.view.url(self.view.context, self.target)
 
-class RequestCallbackActionButton(ManageActionButton):
+
+class ApprovePaymentActionButton(ManageActionButton):
     grok.order(2)
     grok.context(IStudentOnlinePayment)
     grok.view(OnlinePaymentDisplayFormPage)
-    grok.require('waeup.payStudent')
+    grok.require('waeup.managePortal')
     icon = 'actionicon_call.png'
-    text = _('Request callback')
-    target = 'simulate_callback'
-
-    # This button must be neutralized
-    # in the customization package.
+    text = _('Approve payment')
+    target = 'approve'
+
     @property
     def target_url(self):
