source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/remita/studentsbrowser.py @ 16484

Last change on this file since 16484 was 16484, checked in by Henrik Bettermann, 3 years ago

Implement PAYDirect Bank Branch payment.

  • Property svn:keywords set to Id
File size: 12.2 KB
Line 
1## $Id: studentsbrowser.py 16484 2021-05-19 07:47:21Z henrik $
2##
3## Copyright (C) 2017 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import grok
19import hashlib
20from datetime import datetime, timedelta
21from zope.component import getUtility
22from zope.security import checkPermission
23from waeup.kofa.interfaces import IKofaUtils
24from waeup.kofa.utils.helpers import to_timezone
25from waeup.kofa.browser.layout import UtilityView, KofaPage
26from waeup.kofa.browser.viewlets import ManageActionButton
27from waeup.kofa.students.interfaces import IStudentsUtils
28from waeup.kofa.students.browser import OnlinePaymentDisplayFormPage as OPDPStudent
29from kofacustom.nigeria.remita.helpers import (
30    get_JSON_POST_response, query_remita, write_payments_log)
31from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
32from kofacustom.nigeria.students.interfaces import INigeriaStudentOnlinePayment
33from kofacustom.nigeria.interfaces import MessageFactory as _
34
35from kofacustom.nigeria.remita.tests import (
36    MERCHANTID, HOST, HTTPS, API_KEY, SERVICETYPEID, GATEWAY_AMT)
37
38grok.templatedir('browser_templates')
39
40def module_activated(session, payment):
41    if payment.r_company and payment.r_company != 'remita':
42        return False
43    try:
44        return getattr(grok.getSite()['configuration'][str(session)],
45            'remita_enabled', False)
46    except KeyError:
47        session = datetime.now().year
48        try:
49            return getattr(grok.getSite()['configuration'][str(session)],
50                'remita_enabled', False)
51        except KeyError:
52            return False
53
54# Buttons
55
56class RemitaActionButtonStudent(ManageActionButton):
57    grok.order(5)
58    grok.context(INigeriaOnlinePayment)
59    grok.view(OPDPStudent)
60    grok.require('waeup.payStudent')
61    icon = 'actionicon_pay.png'
62    text = _('Pay via Remita')
63    target = 'goto_remita'
64
65    @property
66    def target_url(self):
67        if not module_activated(
68            self.context.student.current_session, self.context):
69            return ''
70        if self.context.p_state != 'unpaid':
71            return ''
72        return self.view.url(self.view.context, self.target)
73
74class RemitaRequestPaymentStatusActionButtonStudent(ManageActionButton):
75    grok.order(6)
76    grok.context(INigeriaOnlinePayment)
77    grok.view(OPDPStudent)
78    grok.require('waeup.payStudent')
79    icon = 'actionicon_call.png'
80    text = _('Requery Remita Payment Status')
81    target = 'request_payment_status'
82
83    @property
84    def target_url(self):
85        if not module_activated(
86            self.context.student.current_session, self.context):
87            return ''
88        if self.context.p_state in ('paid', 'waived', 'scholarship'):
89            return ''
90        return self.view.url(self.view.context, self.target)
91
92
93class RemitaVerifyPaymentStatusActionButtonStudent(ManageActionButton):
94    grok.order(3)
95    grok.context(INigeriaOnlinePayment)
96    grok.view(OPDPStudent)
97    grok.require('waeup.manageStudent')
98    icon = 'actionicon_call.png'
99    text = _('Verify Remita Payment Status')
100    target = 'verify_payment_status'
101
102    @property
103    def target_url(self):
104        if not module_activated(
105            self.context.student.current_session, self.context):
106            return ''
107        if self.context.p_state != 'paid' \
108            or self.context.r_company != u'remita':
109            return ''
110        return self.view.url(self.view.context, self.target)
111
112# Webservice request views
113
114class RemitaRequestPaymentStatusPageStudent(UtilityView, grok.View):
115    """ Request webservice view for the Remita gateway.
116    """
117    grok.context(INigeriaStudentOnlinePayment)
118    grok.name('request_payment_status')
119    grok.require('waeup.payStudent')
120
121    merchantId = MERCHANTID
122    host = HOST
123    https = HTTPS
124    api_key = API_KEY
125
126    def update(self):
127        if not module_activated(
128            self.context.student.current_session, self.context):
129            self.flash(_('Forbidden'), type='danger')
130            self.redirect(self.url(self.context, '@@index'))
131            return
132        if self.context.p_state in ('paid', 'waived', 'scholarship'):
133            self.flash(_('This ticket has already been paid.'), type='danger')
134            return
135        student = self.context.student
136        RRR = self.context.r_pay_reference
137        if not RRR:
138            self.flash(_('Remita Retrieval Reference not found.'), type='danger')
139            return
140        # Remita sends a POST request which may contain more information
141        # if a payment was not successful.
142        resp = self.request.form
143        if resp and resp.get('statuscode') not in (None, '025', '00', '01'):
144            self.flash('Transaction status message from Remita: %s'
145                % resp.get('status'), type='warning')
146        success, msg, log = query_remita(
147            self.context,
148            self.merchantId,
149            self.api_key,
150            RRR,
151            self.host,
152            self.https,
153            False)
154        student.writeLogMessage(self, log)
155        if not success:
156            self.flash(msg, type='danger')
157            return
158        write_payments_log(student.student_id, self.context)
159        flashtype, msg, log = self.context.doAfterStudentPayment()
160        if log is not None:
161            student.writeLogMessage(self, log)
162        self.flash(msg, type=flashtype)
163        return
164
165    def render(self):
166        self.redirect(self.url(self.context, '@@index'))
167        return
168
169class RemitaVerifyPaymentStatusPageStudent(UtilityView, grok.View):
170    """ Request webservice view for the Remita gateway.
171    """
172    grok.context(INigeriaStudentOnlinePayment)
173    grok.name('verify_payment_status')
174    grok.require('waeup.manageStudent')
175
176    merchantId = MERCHANTID
177    host = HOST
178    https = HTTPS
179    api_key = API_KEY
180
181    def update(self):
182        if not module_activated(
183            self.context.student.current_session, self.context):
184            self.flash(_('Forbidden'), type='danger')
185            self.redirect(self.url(self.context, '@@index'))
186            return
187        if self.context.p_state  != 'paid' \
188            or self.context.r_company != u'remita':
189            self.flash(_('This ticket has not been paid.'), type='danger')
190            return
191        student = self.context.student
192        RRR = self.context.r_pay_reference
193        if not RRR:
194            self.flash(_('Remita Retrieval Reference not found.'), type='danger')
195            return
196        # Remita sends a POST request which may contain more information
197        # if a payment was not successful.
198        resp = self.request.form
199        if resp and resp.get('statuscode') not in (None, '025', '00', '01'):
200            self.flash('Transaction status message from Remita: %s'
201                % resp.get('status'), type='warning')
202        success, msg, log = query_remita(
203            self.context,
204            self.merchantId,
205            self.api_key,
206            RRR,
207            self.host,
208            self.https,
209            True)
210        student.writeLogMessage(self, log)
211        if not success:
212            self.flash(msg, type='danger')
213            return
214        self.flash(msg)
215        return
216
217    def render(self):
218        self.redirect(self.url(self.context, '@@index'))
219        return
220
221
222# Forwarding pages
223
224class RemitaPageStudent(KofaPage):
225    """ View which sends a POST request to the Remita payment gateway.
226    """
227    grok.context(INigeriaOnlinePayment)
228    grok.name('goto_remita')
229    grok.template('goto_remita')
230    grok.require('waeup.payStudent')
231    label = _('Pay via Remita')
232    submit_button = _('Pay now')
233
234    merchantId = MERCHANTID
235    host = HOST
236    https = HTTPS
237    api_key = API_KEY
238    serviceTypeId = SERVICETYPEID
239    gateway_amt = GATEWAY_AMT
240
241    #orderId = '3456346346'
242    init_url = '/remita/ecomm/split/init.reg'
243    amount='1000'
244    lineitems = (
245                  {"lineItemsId":"itemid1","beneficiaryName":"Klaus Mueller",
246                  "beneficiaryAccount":"6020067886","bankCode":"011",
247                  "beneficiaryAmount":"500","deductFeeFrom":"1"},
248                  {"lineItemsId":"itemid2","beneficiaryName":"Werner Rumm",
249                  "beneficiaryAccount":"0360883515","bankCode":"050",
250                  "beneficiaryAmount":"500","deductFeeFrom":"0"}
251                )
252
253    @property
254    def action(self):
255        if self.https:
256            return 'https://' + self.host + '/remita/ecomm/finalize.reg'
257        return 'http://' + self.host + '/remita/ecomm/finalize.reg'
258
259    def init_update(self):
260        if self.context.r_pay_reference or self.context.p_state == 'paid':
261            return _("Payment ticket can't be re-sent to Remita.")
262        if self.context.r_company and self.context.r_company != 'remita':
263            return _("Payment ticket has been used for another payment gateway.")
264        now = datetime.utcnow()
265        if self.context.creation_date.tzinfo is not None:
266            # That's bad. Please store timezone-naive datetimes only!
267            now = self.context.creation_date.tzinfo.localize(now)
268        time_delta = now - self.context.creation_date
269        if time_delta.days > 7:
270            return _("This payment ticket is too old. Please create a new ticket.")
271        certificate = getattr(self.context.student['studycourse'],'certificate',None)
272        if certificate is None:
273            return _("Study course data are incomplete.")
274        kofa_utils = getUtility(IKofaUtils)
275        student_utils = getUtility(IStudentsUtils)
276        if student_utils.samePaymentMade(self.context.student, self.context.p_category,
277            self.context.p_item, self.context.p_session):
278            return _("This type of payment has already been made.")
279        self.responseurl = self.url(self.context, 'request_payment_status')
280        resp = get_JSON_POST_response(
281            merchantId=self.merchantId,
282            serviceTypeId=self.serviceTypeId,
283            api_key=self.api_key,
284            orderId=self.orderId,
285            amount=self.amount,
286            responseurl=self.responseurl,
287            host=self.host,
288            url=self.init_url,
289            https=self.https,
290            fullname=self.context.student.display_fullname,
291            email=self.context.student.email,
292            lineitems=self.lineitems)
293        if resp.get('error'):
294            return resp.get('error')
295        if resp.get('statuscode') not in ('021', '025', '055'):
296            return 'RRR generation message from Remita: ' + resp.get('status')
297        self.rrr = self.context.r_pay_reference = resp['RRR'].rstrip()
298        hashargs =      self.merchantId + self.rrr + self.api_key
299        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
300        self.customer = self.context.student
301        self.customer.writeLogMessage(self,
302            'RRR retrieved: %s, ServiceTypeId: %s'
303            % (self.rrr, self.serviceTypeId))
304        return
305
306    def update(self):
307        if not module_activated(
308            self.context.student.current_session, self.context):
309            self.flash(_('Forbidden'), type='danger')
310            self.redirect(self.url(self.context, '@@index'))
311            return
312        self.orderId = self.context.p_id
313        error = self.init_update()
314        if error:
315            self.flash(error, type='danger')
316            self.redirect(self.url(self.context, '@@index'))
317            return
318        # Already now it becomes a Remita payment. We set the net amount
319        # and add the gateway amount.
320        if not self.context.r_company:
321            self.context.net_amt = self.context.amount_auth
322            self.context.amount_auth += self.gateway_amt
323            self.context.gateway_amt = self.gateway_amt
324            self.context.r_company = u'remita'
325        self.amount_auth = int(100 * self.context.amount_auth)
326        return
Note: See TracBrowser for help on using the repository browser.