## $Id: studentsbrowser.py 16206 2020-08-18 09:17:00Z 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.students.interfaces import IStudentsUtils from waeup.kofa.students.browser import OnlinePaymentDisplayFormPage as OPDPStudent 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.students.interfaces import INigeriaStudentOnlinePayment from kofacustom.nigeria.interfaces import MessageFactory as _ from kofacustom.nigeria.remita.tests import ( MERCHANTID, HOST, HTTPS, API_KEY, SERVICETYPEID, GATEWAY_AMT) grok.templatedir('browser_templates') def module_activated(session, payment): if payment.r_company and payment.r_company != 'remita': return False try: return getattr(grok.getSite()['configuration'][str(session)], 'remita_enabled', False) except KeyError: return False # Buttons class RemitaActionButtonStudent(ManageActionButton): grok.order(1) grok.context(INigeriaOnlinePayment) grok.view(OPDPStudent) grok.require('waeup.payStudent') icon = 'actionicon_pay.png' text = _('Pay via Remita') target = 'goto_remita' @property def target_url(self): if not module_activated( self.context.student.current_session, self.context): return '' if self.context.p_state != 'unpaid': return '' return self.view.url(self.view.context, self.target) class RemitaRequestPaymentStatusActionButtonStudent(ManageActionButton): grok.order(2) grok.context(INigeriaOnlinePayment) grok.view(OPDPStudent) grok.require('waeup.payStudent') icon = 'actionicon_call.png' text = _('Requery Remita Payment Status') target = 'request_payment_status' @property def target_url(self): if not module_activated( self.context.student.current_session, self.context): return '' if self.context.p_state in ('paid', 'waived', 'scholarship'): return '' return self.view.url(self.view.context, self.target) class RemitaVerifyPaymentStatusActionButtonStudent(ManageActionButton): grok.order(3) grok.context(INigeriaOnlinePayment) grok.view(OPDPStudent) grok.require('waeup.manageStudent') icon = 'actionicon_call.png' text = _('Verify Remita Payment Status') target = 'verify_payment_status' @property def target_url(self): if not module_activated( self.context.student.current_session, self.context): 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 RemitaRequestPaymentStatusPageStudent(UtilityView, grok.View): """ Request webservice view for the Remita gateway. """ grok.context(INigeriaStudentOnlinePayment) grok.name('request_payment_status') grok.require('waeup.payStudent') merchantId = MERCHANTID host = HOST https = HTTPS api_key = API_KEY def update(self): if not module_activated( self.context.student.current_session, self.context): self.flash(_('Forbidden'), type='danger') self.redirect(self.url(self.context, '@@index')) return if self.context.p_state in ('paid', 'waived', 'scholarship'): self.flash(_('This ticket has already been paid.'), type='danger') return student = self.context.student 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) student.writeLogMessage(self, log) if not success: self.flash(msg, type='danger') return write_payments_log(student.student_id, self.context) flashtype, msg, log = self.context.doAfterStudentPayment() if log is not None: student.writeLogMessage(self, log) self.flash(msg, type=flashtype) return def render(self): self.redirect(self.url(self.context, '@@index')) return class RemitaVerifyPaymentStatusPageStudent(UtilityView, grok.View): """ Request webservice view for the Remita gateway. """ grok.context(INigeriaStudentOnlinePayment) grok.name('verify_payment_status') grok.require('waeup.manageStudent') merchantId = MERCHANTID host = HOST https = HTTPS api_key = API_KEY def update(self): if not module_activated( self.context.student.current_session, self.context): self.flash(_('Forbidden'), type='danger') self.redirect(self.url(self.context, '@@index')) 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 student = self.context.student 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) student.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 RemitaPageStudent(KofaPage): """ View which sends a POST request to the Remita payment gateway. """ grok.context(INigeriaOnlinePayment) grok.name('goto_remita') grok.template('goto_remita') grok.require('waeup.payStudent') label = _('Pay via Remita') submit_button = _('Pay now') merchantId = MERCHANTID host = HOST https = HTTPS api_key = API_KEY serviceTypeId = SERVICETYPEID gateway_amt = GATEWAY_AMT #orderId = '3456346346' 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"} ) @property def action(self): if self.https: return 'https://' + self.host + '/remita/ecomm/finalize.reg' return 'http://' + self.host + '/remita/ecomm/finalize.reg' def init_update(self): if self.context.r_pay_reference or self.context.p_state == 'paid': return _("Payment ticket can't be re-sent to Remita.") if self.context.r_company and self.context.r_company != 'remita': return _("Payment ticket has been used for another payment gateway.") 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.") certificate = getattr(self.context.student['studycourse'],'certificate',None) if certificate is None: return _("Study course data are incomplete.") kofa_utils = getUtility(IKofaUtils) student_utils = getUtility(IStudentsUtils) if student_utils.samePaymentMade(self.context.student, self.context.p_category, self.context.p_item, self.context.p_session): return _("This type of payment has already been made.") 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.student.display_fullname, email=self.context.student.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') 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.student self.customer.writeLogMessage(self, 'RRR retrieved: %s, ServiceTypeId: %s' % (self.rrr, self.serviceTypeId)) return def update(self): if not module_activated( self.context.student.current_session, self.context): self.flash(_('Forbidden'), type='danger') self.redirect(self.url(self.context, '@@index')) return self.orderId = self.context.p_id error = self.init_update() if error: self.flash(error, type='danger') self.redirect(self.url(self.context, '@@index')) return # Already now it becomes a Remita payment. We set the net amount # and add the gateway amount. if not self.context.r_company: self.context.net_amt = self.context.amount_auth self.context.amount_auth += self.gateway_amt self.context.gateway_amt = self.gateway_amt self.context.r_company = u'remita' self.amount_auth = int(100 * self.context.amount_auth) return