Index: main/waeup.custom/trunk/src/waeup/custom/students/browser.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/students/browser.py	(revision 7888)
+++ main/waeup.custom/trunk/src/waeup/custom/students/browser.py	(revision 7889)
@@ -16,4 +16,6 @@
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##
+from datetime import datetime
+from urllib import urlencode
 import grok
 from zope.formlib.textwidgets import BytesDisplayWidget
@@ -21,11 +23,12 @@
     FriendlyDateWidget, FriendlyDateDisplayWidget
     )
-from waeup.kofa.browser.layout import KofaPage
+from waeup.kofa.browser.layout import KofaPage, UtilityView
+from waeup.kofa.accesscodes import create_accesscode
 from waeup.kofa.students.interfaces import IStudentOnlinePayment
 from waeup.kofa.students.browser import (
     StudentPersonalDisplayFormPage, StudentPersonalManageFormPage,
     StudentClearanceManageFormPage, StudentClearanceEditFormPage,
-    StudentClearanceDisplayFormPage
-    )
+    StudentClearanceDisplayFormPage, write_log_message,
+    OnlinePaymentDisplayFormPage)
 from waeup.kofa.students.viewlets import RequestCallbackActionButton
 from waeup.custom.students.interfaces import (
@@ -69,4 +72,12 @@
     form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
 
+#class OnlinePaymentDisplayFormPage(OnlinePaymentDisplayFormPage):
+#    """ Page to view an online payment ticket.
+
+#    The customized version provides also product data for
+#    payment providers
+#    """
+
+
 class InterswitchActionButton(RequestCallbackActionButton):
     grok.order(2)
@@ -75,9 +86,26 @@
     target = 'goto_interswitch'
 
+    @property
+    def target_url(self):
+        if self.context.p_state != 'unpaid':
+            return ''
+        return self.view.url(self.view.context, self.target)
+
 class RequestCallbackActionButton(RequestCallbackActionButton):
     grok.order(3)
     icon = 'actionicon_call.png'
     text = _('Request CollegePAY callback')
-    target = 'callback'
+    product_id = '57'
+    
+    def target_url(self):
+        if self.context.p_state != 'unpaid':
+            return ''
+        site_redirect_url = self.view.url(self.view.context, 'callback')
+        query_url = "https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx"
+        args = {
+            'transRef':self.context.p_id,
+            'prodID':self.product_id,
+            'redirectURL':site_redirect_url}
+        return query_url + '?%s' % urlencode(args)
 
 class InterswitchPage(KofaPage):
@@ -89,15 +117,7 @@
     grok.template('goto_interswitch')
     grok.require('waeup.payStudent')
-
     label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
     submit_button = _('Submit')
-
-    # Uniben product data (to be removed later)
-    product_id = '57'
-    currency = '566'
-    pay_item_id = '5700'
-
     action = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
-    site_redirect_url = 'http://localhost?echo='
     site_name = 'xyz.waeup.org'
     provider_acct = '2345'
@@ -107,4 +127,7 @@
     institution_bank_id = '9'
     institution_name = 'Sample University'
+    currency = '566'
+    pay_item_id = '5700'
+    product_id = '57'
 
     def update(self):
@@ -119,4 +142,5 @@
         self.amount_100 = 100 * self.amount
         self.local_date_time = str(self.context.creation_date)
+        self.site_redirect_url = self.url(self.context, 'callback')
         certificate = getattr(self.student['studycourse'],'certificate',None)
         if certificate is not None:
@@ -145,2 +169,81 @@
         self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
         return
+
+class OnlinePaymentCallbackPage(UtilityView, grok.View):
+    """ Callback view for the CollegePAY gateway
+    """
+    grok.context(IStudentOnlinePayment)
+    grok.name('callback')
+    grok.require('waeup.payStudent')
+
+    # This view is not yet working for offline querying transactions
+    # since the the query string differs from the query string sent after
+    # posting transactions. This Interswitch bug must be removed first.
+    # Alternatively, we could use the webservice only and replace
+    # the RequestCallbackActionButton by a RequestWebserviceActionButton
+
+    def update(self):
+        if self.context.p_state == 'paid':
+            self.flash(_('This ticket has already been paid.'))
+            return
+        student = self.context.getStudent()
+        query = self.request.form
+        # Should be logged instead of printed
+        print query
+        if query.get('resp', None) != '00':
+            self.flash(_('Unsuccessful callback: ${a}',
+                mapping = {'a': query.get('desc', _('No query string'))}))
+            write_log_message(self,'invalid callback: %s' % self.context.p_id)
+            self.context.r_card_num = query.get('cardNum', None)
+            self.context.r_code = query.get('resp', None)
+            self.context.p_state = 'failed'
+            return
+
+        # Add amount checking
+
+
+        # Add webservice validation
+
+
+        write_log_message(self,'valid callback: %s' % self.context.p_id)
+        self.context.r_amount_approved = self.context.amount_auth
+        self.context.r_card_num = query.get('cardNum', None)
+        self.context.r_code = query.get('resp', None)
+        self.context.r_desc = query.get('desc', None)
+        self.context.r_pay_reference  = query.get('payRef', None)
+        self.context.p_state = 'paid'
+        self.context.payment_date = datetime.now()
+
+        if self.context.p_category == 'clearance':
+            # Create CLR access code
+            pin, error = create_accesscode('CLR',0,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,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,student.student_id)
+            if error:
+                self.flash(_('Valid callback received. ${a}',
+                    mapping = {'a':error}))
+                return
+            self.context.ac = pin
+        self.flash(_('Valid callback received.'))
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
+
+
+
Index: main/waeup.custom/trunk/src/waeup/custom/students/tests.py
===================================================================
--- main/waeup.custom/trunk/src/waeup/custom/students/tests.py	(revision 7888)
+++ main/waeup.custom/trunk/src/waeup/custom/students/tests.py	(revision 7889)
@@ -95,4 +95,15 @@
     layer = FunctionalLayer
 
+    def callback_url(self, payment_url, resp, apprAmt):
+        return payment_url + (
+            '/callback?echo=' +
+            '&resp=%s' +
+            '&desc=Something went wrong' +
+            '&txnRef=p1331792385335' +
+            '&payRef=' + '&retRef=' +
+            '&cardNum=0' +
+            '&apprAmt=%s' +
+            '&url=http://xyz') % (resp, apprAmt)
+
     def test_manage_payments(self):
         # Add missing configuration data
@@ -130,9 +141,31 @@
 
         # Manager receives valid callback
-        self.browser.open(payment_url)
-        self.browser.getLink("Request CollegePAY callback").click()
+        #self.browser.open(payment_url)
+        #self.browser.getLink("Request CollegePAY callback").click()
+
+        self.browser.open(self.callback_url(payment_url, 'XX', '300'))
+        self.assertMatches('...Unsuccessful callback: Something went wrong...',
+                          self.browser.contents)
+        self.assertMatches('...Failed...',
+                           self.browser.contents)
+        self.browser.open(self.payments_student_path + '/addop')
+        self.browser.getControl("Create ticket").click()
+        ctrl = self.browser.getControl(name='val_id')
+
+        #value = ctrl.options[1]
+        #self.browser.getLink(value).click()
+        #self.assertMatches('...Amount Authorized...',
+        #                   self.browser.contents)
+        #payment_url = self.browser.url
+
+        self.browser.open(payment_url + '/callback')
+        self.assertMatches('...Unsuccessful callback: No query string...',
+                          self.browser.contents)
+        self.assertMatches('...Failed...',
+                           self.browser.contents)
+        #print self.browser.contents
+        self.browser.open(self.callback_url(payment_url, '00', '300'))
         self.assertMatches('...Valid callback received...',
                           self.browser.contents)
-
         self.browser.open(self.manage_student_path)
         self.browser.getControl(
@@ -147,5 +180,6 @@
                            self.browser.contents)
         # Remove all payments so that we can add a school fee payment again
-        for payment in self.student['payments'].keys():
+        keys = [i for i in self.student['payments'].keys()]
+        for payment in keys:
             del self.student['payments'][payment]
         self.browser.open(self.payments_student_path + '/addop')
