Index: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/applicantsbrowser.py
===================================================================
--- main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/applicantsbrowser.py	(revision 14766)
+++ main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/applicantsbrowser.py	(revision 14766)
@@ -0,0 +1,273 @@
+## $Id: browser.py 14759 2017-08-03 09:09:54Z henrik $
+##
+## Copyright (C) 2017 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+import hashlib
+from datetime import datetime, timedelta
+from zope.component import getUtility
+from zope.security import checkPermission
+from waeup.kofa.interfaces import IKofaUtils
+from waeup.kofa.utils.helpers import to_timezone
+from waeup.kofa.browser.layout import UtilityView, KofaPage
+from waeup.kofa.browser.viewlets import ManageActionButton
+from waeup.kofa.applicants.browser import OnlinePaymentDisplayFormPage as OPDPApplicant
+from kofacustom.nigeria.remita.helpers import (
+    get_JSON_POST_response, query_remita, write_payments_log)
+from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
+from kofacustom.nigeria.applicants.interfaces import INigeriaApplicantOnlinePayment
+from kofacustom.nigeria.interfaces import MessageFactory as _
+from kofacustom.nigeria.remita.studentsbrowser import module_activated
+
+grok.templatedir('browser_templates')
+
+# Buttons
+
+class RemitaActionButtonApplicant(ManageActionButton):
+    grok.order(1)
+    grok.context(INigeriaOnlinePayment)
+    grok.view(OPDPApplicant)
+    grok.require('waeup.payApplicant')
+    icon = 'actionicon_pay.png'
+    text = _('Pay via Remita')
+    target = 'goto_remita'
+
+    @property
+    def target_url(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return ''
+        if self.context.p_state != 'unpaid':
+            return ''
+        return self.view.url(self.view.context, self.target)
+
+class RemitaRequestPaymentStatusActionButtonApplicant(ManageActionButton):
+    grok.order(2)
+    grok.context(INigeriaOnlinePayment)
+    grok.view(OPDPApplicant)
+    grok.require('waeup.payApplicant')
+    icon = 'actionicon_call.png'
+    text = _('Requery Remita Payment Status')
+    target = 'request_payment_status'
+
+    @property
+    def target_url(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return ''
+        if self.context.p_state in ('paid', 'waived'):
+            return ''
+        return self.view.url(self.view.context, self.target)
+
+class RemitaVerifyPaymentStatusActionButtonApplicant(ManageActionButton):
+    grok.order(3)
+    grok.context(INigeriaOnlinePayment)
+    grok.view(OPDPApplicant)
+    grok.require('waeup.manageApplication')
+    icon = 'actionicon_call.png'
+    text = _('Verify Remita Payment Status')
+    target = 'verify_payment_status'
+
+    @property
+    def target_url(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return ''
+        if self.context.p_state != 'paid' \
+            or self.context.r_company != u'remita':
+            return ''
+        return self.view.url(self.view.context, self.target)
+
+# Webservice request views
+
+class RemitaRequestPaymentStatusPageApplicant(UtilityView, grok.View):
+    """ Request webservice view for the Remita gateway.
+    """
+    grok.context(INigeriaApplicantOnlinePayment)
+    grok.name('request_payment_status')
+    grok.require('waeup.payApplicant')
+
+    # Here we use Remita test portal data
+    merchantId = '2547916'
+    host = 'www.remitademo.net'
+    https = True
+    api_key = '1946'
+
+    def update(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return
+        if self.context.p_state in ('paid', 'waived'):
+            self.flash(_('This ticket has already been paid.'), type='danger')
+            return
+        applicant = self.context.__parent__
+        RRR = self.context.r_pay_reference
+        if not RRR:
+            self.flash(_('Remita Retrieval Reference not found.'), type='danger')
+            return
+        # Remita sends a POST request which may contain more information
+        # if a payment was not successful.
+        resp = self.request.form
+        if resp and resp.get('statuscode') not in (None, '025', '00', '01'):
+            self.flash('Transaction status message from Remita: %s'
+                % resp.get('status'), type='warning')
+        success, msg, log = query_remita(
+            self.context,
+            self.merchantId,
+            self.api_key,
+            RRR,
+            self.host,
+            self.https,
+            False)
+        applicant.writeLogMessage(self, log)
+        if not success:
+            self.flash(msg, type='danger')
+            return
+        write_payments_log(applicant.applicant_id, self.context)
+        flashtype, msg, log = self.context.doAfterApplicantPayment()
+        if log is not None:
+            applicant.writeLogMessage(self, log)
+        self.flash(msg, type=flashtype)
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
+
+class RemitaVerifyPaymentStatusPageApplicant(UtilityView, grok.View):
+    """ Request webservice view for the Remita gateway.
+    """
+    grok.context(INigeriaApplicantOnlinePayment)
+    grok.name('verify_payment_status')
+    grok.require('waeup.manageApplication')
+
+    # Here we use Remita test portal data
+    merchantId = '2547916'
+    host = 'www.remitademo.net'
+    https = True
+    api_key = '1946'
+
+    def update(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return
+        if self.context.p_state  != 'paid' \
+            or self.context.r_company != u'remita':
+            self.flash(_('This ticket has not been paid.'), type='danger')
+            return
+        applicant = self.context.__parent__
+        RRR = self.context.r_pay_reference
+        if not RRR:
+            self.flash(_('Remita Retrieval Reference not found.'), type='danger')
+            return
+        # Remita sends a POST request which may contain more information
+        # if a payment was not successful.
+        resp = self.request.form
+        if resp and resp.get('statuscode') not in (None, '025', '00', '01'):
+            self.flash('Transaction status message from Remita: %s'
+                % resp.get('status'), type='warning')
+        success, msg, log = query_remita(
+            self.context,
+            self.merchantId,
+            self.api_key,
+            RRR,
+            self.host,
+            self.https,
+            True)
+        applicant.writeLogMessage(self, log)
+        if not success:
+            self.flash(msg, type='danger')
+            return
+        self.flash(msg)
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
+
+
+# Forwarding pages
+
+class RemitaPageApplicant(KofaPage):
+    """ View which sends a POST request to the Remita payment gateway.
+    """
+    grok.context(INigeriaApplicantOnlinePayment)
+    grok.name('goto_remita')
+    grok.template('goto_remita')
+    grok.require('waeup.payApplicant')
+    label = _('Pay via Remita')
+    submit_button = _('Pay now')
+    https = True
+
+    # Here we use Remita test portal data
+    merchantId = '2547916'
+    serviceTypeId = '4430731'
+    api_key = '1946'
+    orderId = '3456346346'
+    host = 'www.remitademo.net'
+    init_url = '/remita/ecomm/split/init.reg'
+    amount='1000'
+    lineitems = (
+                  {"lineItemsId":"itemid1","beneficiaryName":"Klaus Mueller",
+                  "beneficiaryAccount":"6020067886","bankCode":"011",
+                  "beneficiaryAmount":"500","deductFeeFrom":"1"},
+                  {"lineItemsId":"itemid2","beneficiaryName":"Werner Rumm",
+                  "beneficiaryAccount":"0360883515","bankCode":"050",
+                  "beneficiaryAmount":"500","deductFeeFrom":"0"}
+                )
+
+    action = 'http://www.remitademo.net/remita/ecomm/finalize.reg'
+
+    def init_update(self):
+        if self.context.p_state == 'paid':
+            return _("Payment ticket can't be re-sent to Remita.")
+        now = datetime.utcnow()
+        if self.context.creation_date.tzinfo is not None:
+            # That's bad. Please store timezone-naive datetimes only!
+            now = self.context.creation_date.tzinfo.localize(now)
+        time_delta = now - self.context.creation_date
+        if time_delta.days > 7:
+            return _("This payment ticket is too old. Please create a new ticket.")
+        self.responseurl = self.url(self.context, 'request_payment_status')
+        resp = get_JSON_POST_response(
+            merchantId=self.merchantId,
+            serviceTypeId=self.serviceTypeId,
+            api_key=self.api_key,
+            orderId=self.orderId,
+            amount=self.amount,
+            responseurl=self.responseurl,
+            host=self.host,
+            url=self.init_url,
+            https=self.https,
+            fullname=self.context.__parent__.display_fullname,
+            email=self.context.__parent__.email,
+            lineitems=self.lineitems)
+        if resp.get('error'):
+            return resp.get('error')
+        if resp.get('statuscode') not in ('021', '025', '055'):
+            return 'RRR generation message from Remita: ' + resp.get('status')
+        # Already now it becomes a Remita payment
+        self.context.r_company = u'remita'
+        self.rrr = self.context.r_pay_reference = resp['RRR'].rstrip()
+        hashargs = 	self.merchantId + self.rrr + self.api_key
+        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
+        self.customer = self.context.__parent__
+        return
+
+    def update(self):
+        if not module_activated(self.context.__parent__.__parent__.year):
+            return
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        return
Index: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/tests.py
===================================================================
--- main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/tests.py	(revision 14765)
+++ main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/tests.py	(revision 14766)
@@ -184,5 +184,5 @@
 
     @external_test
-    def test_remita_form(self):
+    def test_student_remita_form(self):
         # Manager can access Remita form
         self.browser.getLink("Pay via Remita").click()
@@ -218,2 +218,46 @@
         self.assertTrue('Successful callback received' in self.browser.contents)
         return
+
+class RemitaTestsApplicants(ApplicantsFullSetup):
+    """Tests for the Remita payment gateway.
+    """
+
+    layer = FunctionalLayer
+
+    # successful transaction
+    # (hopefully this transaction remains in the Remita database)
+    #orderId = '3456346346'
+    rrr = u'280007640804'
+    #amount = 1000.0
+
+    def setUp(self):
+        super(RemitaTestsApplicants, self).setUp()
+        configuration = SessionConfiguration()
+        configuration.academic_session = datetime.now().year - 2
+        configuration.remita_enabled = True
+        self.app['configuration'].addSessionConfiguration(configuration)
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.manage_path)
+        #IWorkflowState(self.student).setState('started')
+        super(RemitaTestsApplicants, self).fill_correct_values()
+        self.applicantscontainer.application_fee = 1000.0
+        self.browser.getControl(name="form.nationality").value = ['NG']
+        self.browser.getControl(name="transition").value = ['start']
+        self.browser.getControl("Save").click()
+        self.browser.getControl("Add online").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        self.payment = self.applicant.values()[0]
+        self.payment_url = self.browser.url
+
+    @external_test
+    def test_applicant_remita_form(self):
+        # Manager can access Remita form
+        self.browser.getLink("Pay via Remita").click()
+        # The RRR has been retrieved
+        self.assertTrue('<input name="rrr" type="hidden" value="%s">' % self.rrr
+            in self.browser.contents)
+        self.assertEqual(self.payment.r_pay_reference, self.rrr)
+        self.assertEqual(self.payment.r_company, 'remita')
+        # Means of testing end here.
+        return
