source: main/waeup.aaue/trunk/src/waeup/aaue/interswitch/browser.py @ 17488

Last change on this file since 17488 was 17485, checked in by Henrik Bettermann, 17 months ago

Application split payment configuration was very neat and now ...

  • Property svn:executable set to *
File size: 21.5 KB
Line 
1    # -*- coding: utf-8 -*-
2## $Id: browser.py 17429 2023-06-05 04:34:30Z henrik $
3##
4## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2 of the License, or
8## (at your option) any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty ofself.context.amount_auth
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program; if not, write to the Free Software
17## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18##
19import httplib
20import hashlib
21import grok
22import os
23import csv
24from xml.dom import minidom
25from zope.interface import Interface
26from zope.component import queryAdapter
27from waeup.kofa.interfaces import CLEARED
28from kofacustom.nigeria.interswitch.browser import (
29    InterswitchPaymentRequestWebservicePageStudent,
30    InterswitchPaymentRequestWebservicePageApplicant,
31    InterswitchPaymentVerifyWebservicePageApplicant,
32    InterswitchPaymentVerifyWebservicePageStudent,
33    InterswitchPageStudent, InterswitchPageApplicant,
34    module_activated,
35    )
36from waeup.aaue.students.interfaces import ICustomStudentOnlinePayment
37from waeup.aaue.applicants.interfaces import ICustomApplicantOnlinePayment
38from waeup.aaue.students.utils import SFEECHANGES
39from waeup.aaue.interfaces import MessageFactory as _
40
41PRODUCT_ID = '5845'
42SITE_NAME = 'aaue.waeup.org'
43PROVIDER_ACCT = '0200244434'
44PROVIDER_BANK_ID = '11'
45PROVIDER_ITEM_NAME = 'WAeAC Portal Fee'
46INSTITUTION_NAME = 'AAU Ekpoma'
47CURRENCY = '566'
48GATEWAY_AMT = 200.0
49POST_ACTION = 'https://webpay.interswitchng.com/paydirect/pay'
50
51HOST = 'webpay.interswitchng.com'
52URL = '/paydirect/api/v1/gettransaction.json'
53HTTPS = True
54MAC = '9718FA00B0F5070B388A9896ADCED9B2FB02D30F71E12E68BDADC63F6852A3496FF97D8A0F9DA9F753B911A49BB09BB87B55FD02046BD325C74C46C0123CF023'
55
56httplib.HTTPSConnection.debuglevel = 0
57
58BANK_ACCOUNTS = {
59    'edohis':   ('1222577132', '117'),
60    'union':    ('1019763348', '7'),
61    'sport':    ('1021941220', '7'),
62    'access':   ('1012688013', '123'),
63    'notebook': ('4011210501', '51'),
64    'library':  ('2000122995', '8'),
65    'fac1':     ('1022438743', '7'),
66    'fac2':     ('2000249757', '8'),
67    'fac3':     ('1012678566', '123'),
68    'matricgown': ('2000249757', '8'),
69    'lapel':      ('2000249757', '8'),
70
71    'acceptance': ('2000249757', '8'),
72
73    'hostel_maintenance': ('1006406795', '123'),
74    'bed_allocation':     ('1006406795', '123'),
75    'late_registration':  ('5210006575', '51'),
76    'ent_combined':       ('6220029828', '51'),
77    'ent_registration_0': ('6220029828', '51'),
78    'ent_registration_1': ('6220029828', '51'),
79    'ent_registration_2': ('6220029828', '51'),
80    'ent_text_book_0':    ('6220029828', '51'),
81    'ent_text_book_1':    ('6220029828', '51'),
82    'ent_text_book_2':    ('6220029828', '51'),
83    'gst_registration_1': ('1010893123', '117'),
84    'gst_registration_2': ('1010893123', '117'),
85    'gst_text_book_0':    ('1010893123', '117'),
86    'gst_text_book_1':    ('1010893123', '117'),
87    'gst_text_book_2':    ('1010893123', '117'),
88    'gst_text_book_3':    ('1010893123', '117'),
89
90    'postgrad': ('1010827641', '117'),
91    }
92
93FEE_NAMES = {
94    'edohis':     'Edo State Health Insurance Scheme',
95    'union':      'Student Union Dues',
96    'sport':      'Sport Development Fee',
97    'access':     'Access Card Fee',
98    'notebook':   'Branded Notebook',
99    'library':    'Library Development Fee',
100    'tuition':    'Tuition',
101    'acceptance': 'Acceptance Fee',
102    'matricgown': 'Matriculation Gown Fee',
103    'lapel':      'File/Lapel Fee',
104    'lmsplus':    'LMS Plus Fee',
105    'nuga':       'NUGA Fee',
106    }
107
108
109SCHOOLFEES = dict()
110
111for year in SFEECHANGES:
112    schoolfees_path = os.path.join(
113        os.path.dirname(__file__), '../students/schoolfees_%s.csv' %year)
114    reader = csv.DictReader(open(schoolfees_path, 'rb'))
115    SCHOOLFEES[year] = {item['code']:item for item in reader}
116
117acceptancefees_path = os.path.join(
118    os.path.dirname(__file__), '../students/acceptancefees.csv')
119reader = csv.DictReader(open(acceptancefees_path, 'rb'))
120ACCEPTANCEFEES = {item['code']:item for item in reader}
121
122class CustomInterswitchPageApplicant(InterswitchPageApplicant):
123    """ View which sends a POST request to the Interswitch
124    CollegePAY payment gateway.
125
126    So far only PT application has been configured.
127    """
128    grok.context(ICustomApplicantOnlinePayment)
129    action = POST_ACTION
130    site_name = SITE_NAME
131    currency = CURRENCY
132    provider_bank_id = PROVIDER_BANK_ID
133    provider_acct = PROVIDER_ACCT
134    pay_item_id = '101'
135    product_id = PRODUCT_ID
136
137    def update(self):
138        if not module_activated(
139            self.context.__parent__.__parent__.year, self.context):
140            self.flash(_('Forbidden'), type='danger')
141            self.redirect(self.url(self.context, '@@index'))
142            return
143        error = self.init_update()
144        if error:
145            self.flash(error, type='danger')
146            self.redirect(self.url(self.context, '@@index'))
147            return
148        # Already now it becomes an Interswitch payment. We set the net amount
149        # and add the gateway amount.
150        if not self.context.r_company:
151            self.context.net_amt = self.context.amount_auth
152            self.context.amount_auth += GATEWAY_AMT
153            self.context.gateway_amt = GATEWAY_AMT
154            self.context.r_company = u'interswitch'
155        xmldict = {}
156        provider_amt = 2000.0
157        if self.applicant.__parent__.code in ('ver2019', 'send2019'):
158            provider_amt = 0.0
159        elif self.applicant.__parent__.code.startswith('cert'):
160            provider_amt = 3000.0
161        elif self.applicant.__parent__.code.startswith('trans'):
162            provider_amt = 3000.0
163        xmldict['institution_acct'] = '1012332141'
164        xmldict['institution_bank_id'] = '123'
165        if self.applicant.applicant_id.startswith('dsh'):
166            xmldict['institution_acct'] = '1014847058'
167            xmldict['institution_bank_id'] = '7'
168        if self.applicant.applicant_id.startswith('ijmbe'):
169            xmldict['institution_acct'] = '1012278272'
170            xmldict['institution_bank_id'] = '123'
171        xmldict['detail_ref'] = self.context.p_id
172        xmldict['provider_amt'] = 100 * provider_amt
173        xmldict['provider_acct'] = PROVIDER_ACCT
174        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
175        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
176        xmldict['institution_item_name'] = self.context.category
177        xmldict['institution_name'] = INSTITUTION_NAME
178        xmldict['institution_amt'] = 100 * self.context.net_amt
179        if not self.context.provider_amt:
180            self.context.provider_amt = provider_amt
181            self.context.amount_auth += provider_amt
182        if provider_amt:
183            xmltext = """<payment_item_detail>
184<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
185<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
186<item_detail item_id="2" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
187</item_details>
188</payment_item_detail>""" % xmldict
189        else:
190            xmltext = """<payment_item_detail>
191<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
192<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
193</item_details>
194</payment_item_detail>""" % xmldict
195
196        # Overwrite above configuration
197        if self.applicant.__parent__.code in ('cert1', 'cert2', 'cert6', 'cert9'):
198            xmldict['institution_amt'] = 100 * self.context.net_amt - 100000
199            xmltext = """<payment_item_detail>
200<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s">
201<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
202<item_detail item_id="2" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
203<item_detail item_id="3" item_name="Ambrose Alli Foundation" item_amt="100000" bank_id="117" acct_num="1015567975" />
204</item_details>
205</payment_item_detail>""" % xmldict
206
207        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
208        xmlitems = ''
209        xmldoc = minidom.parseString(xmltext)
210        itemlist = xmldoc.getElementsByTagName('item_detail')
211        for s in itemlist:
212            xmlitems += "%s (%s %s, %s (%s)),  " % (
213                s.attributes['item_name'].value,
214                u'\u20a6',
215                int(s.attributes['item_amt'].value)/100,
216                s.attributes['acct_num'].value,
217                s.attributes['bank_id'].value,
218                )
219        self.context.p_split_data = xmlitems
220        self.amount_auth = int(100 * self.context.amount_auth)
221        hashargs = (
222            self.context.p_id +
223            PRODUCT_ID +
224            self.pay_item_id +
225            str(int(self.amount_auth)) +
226            self.site_redirect_url +
227            MAC)
228        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
229        return
230
231class CustomInterswitchPageStudent(InterswitchPageStudent):
232    """ View which sends a POST request to the Interswitch
233    CollegePAY payment gateway.
234    """
235    grok.context(ICustomStudentOnlinePayment)
236    action = POST_ACTION
237    site_name = SITE_NAME
238    currency = CURRENCY
239    pay_item_id = '101'
240    product_id = PRODUCT_ID
241
242    def update(self):
243        if not module_activated(
244            self.context.student.current_session, self.context):
245            self.flash(_('Forbidden'), type='danger')
246            self.redirect(self.url(self.context, '@@index'))
247            return
248        error = self.init_update()
249        if error:
250            self.flash(error, type='danger')
251            self.redirect(self.url(self.context, '@@index'))
252            return
253        student = self.student
254        category = self.context.p_category
255        # To guarantee that cleared students pay both acceptance fee
256        # and school fees, the page can only be accessed
257        # for school fee payments if acceptance/clearance fee has
258        # been successfully queried/paid beforehand. This
259        # requirement applies to students in state 'cleared' and
260        # entry_session greater than 2012 only.
261        if self.context.p_category.startswith('schoolfee') and \
262            student.state == CLEARED and \
263            student.entry_session > 2012:
264            acceptance_fee_paid = False
265            for ticket in student['payments'].values():
266                if ticket.p_state == 'paid' and \
267                    ticket.p_category.startswith('clearance'):
268                    acceptance_fee_paid = True
269                    break
270            if not acceptance_fee_paid:
271                self.flash(
272                    _('Please pay acceptance fee first.'), type="danger")
273                self.redirect(self.url(self.context, '@@index'))
274                return
275        # Already now it becomes an Interswitch payment. We set the net amount
276        # and add the gateway amount.
277        if not self.context.r_company:
278            self.context.net_amt = self.context.amount_auth
279            self.context.amount_auth += GATEWAY_AMT
280            self.context.gateway_amt = GATEWAY_AMT
281            self.context.r_company = u'interswitch'
282        xmldict = self.xmldict
283        # Provider data
284        xmldict['detail_ref'] = self.context.p_id
285        xmldict['provider_acct'] = PROVIDER_ACCT
286        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
287        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
288        # Institution data
289        xmldict['institution_acct'] = '00000000'
290        xmldict['institution_bank_id'] = '00'
291        provider_amt = 0.0
292        if category.startswith('clearance'):
293            provider_amt = 1500.0
294        elif category.startswith('hostel_maintenance'):
295            provider_amt = 1000.0
296        elif category in ('schoolfee', 'schoolfee_1', 'schoolfee_incl'):
297            provider_amt = 2500.0
298        xmldict['provider_amt'] = 100 * provider_amt
299        xmldict['institution_item_name'] = self.context.category
300        xmldict['institution_name'] = INSTITUTION_NAME
301        xmldict['institution_amt'] = 100 * self.context.net_amt
302        if not self.context.provider_amt:
303            self.context.provider_amt = provider_amt
304            self.context.amount_auth += provider_amt
305        xmltext = ''
306
307        # School fee
308        if category.startswith('schoolfee'):
309            # collect additional fees
310            if self.context.p_category in ('schoolfee_1', 'schoolfee_incl'):
311                xmltext = """<payment_item_detail>
312<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">""" % xmldict
313                item_id = 1
314
315                if student.entry_session < 2013:
316                    sorted_items = SCHOOLFEES[12][student.certcode].items()
317                elif student.entry_session < 2014:
318                    sorted_items = SCHOOLFEES[13][student.certcode].items()
319                elif student.entry_session < 2015:
320                    sorted_items = SCHOOLFEES[14][student.certcode].items()
321                elif student.entry_session < 2020:
322                    sorted_items = SCHOOLFEES[15][student.certcode].items()
323                elif student.entry_session < 2021:
324                    sorted_items = SCHOOLFEES[20][student.certcode].items()
325                elif student.entry_session < 2022:
326                    sorted_items = SCHOOLFEES[21][student.certcode].items()
327                else:
328                    sorted_items = SCHOOLFEES[22][student.certcode].items()
329                sorted_items.insert(0, sorted_items.pop(4))
330                for item in sorted_items:
331                    try:
332                        item_amt = 100 * int(item[1])
333                        if self.context.p_category == 'schoolfee_1' and item[0] == 'tuition':
334                            item_amt /= 2
335                        acct_num = ''
336                        bank_id = ''
337                        item_name = ''
338                        # Find appropriate bank
339                        try:
340                            bank = BANK_ACCOUNTS[item[0]]
341                        except: # transfer to faculty account
342                            if student.faccode in ('FAG', 'FAT', 'FBM', 'FMLS', 'fac1'):
343                                bank = BANK_ACCOUNTS['fac1']
344                            elif student.faccode in ('FCS', 'FED', 'FES', 'FET'):
345                                bank = BANK_ACCOUNTS['fac2']
346                            elif student.faccode in ('FLS', 'FLW', 'FMS', 'FPS', 'FSS'):
347                                bank = BANK_ACCOUNTS['fac3']
348                        acct_num = bank[0]
349                        bank_id = bank[1]
350                        item_name = FEE_NAMES[item[0]]
351                        xmltext += """
352<item_detail item_id="%s" item_name="%s" item_amt="%d" bank_id="%s" acct_num="%s" />""" % (item_id, item_name, item_amt, bank_id, acct_num)
353                        item_id += 1
354                    except:
355                        pass
356                xmldict['item_id'] = item_id
357                xmltext += """
358<item_detail item_id="%(item_id)d" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
359</item_details>
360</payment_item_detail>""" % xmldict
361            # no additional charges, determine faculty bank only
362            else:
363                if student.is_postgrad:
364                    bank = BANK_ACCOUNTS['postgrad']
365                elif student.faccode in ('FAG', 'FAT', 'FBM', 'FMLS', 'fac1'):
366                    bank = BANK_ACCOUNTS['fac1']
367                elif student.faccode in ('FCS', 'FED', 'FES', 'FET'):
368                    bank = BANK_ACCOUNTS['fac2']
369                elif student.faccode in ('FLS', 'FLW', 'FMS', 'FPS', 'FSS'):
370                    bank = BANK_ACCOUNTS['fac3']
371                xmldict['institution_acct'] = bank[0]
372                xmldict['institution_bank_id'] = bank[1]
373
374
375        # Clearance (acceptance) fee
376
377        elif category.startswith('clearance'):
378            # collect additional fees
379            if self.context.p_category == 'clearance_incl':
380                xmltext = """<payment_item_detail>
381<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">""" % xmldict
382                item_id = 1
383                for item in ACCEPTANCEFEES[student.certcode].items():
384                    try:
385                        item_amt = 100 * int(item[1])
386                        bank = BANK_ACCOUNTS[item[0]]
387                        acct_num = bank[0]
388                        bank_id = bank[1]
389                        item_name = FEE_NAMES[item[0]]
390                        xmltext += """
391<item_detail item_id="%s" item_name="%s" item_amt="%d" bank_id="%s" acct_num="%s" />""" % (item_id, item_name, item_amt, bank_id, acct_num)
392                        item_id += 1
393                    except:
394                        pass
395                xmldict['item_id'] = item_id
396                xmltext += """
397<item_detail item_id="%(item_id)d" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
398</item_details>
399</payment_item_detail>""" % xmldict
400            # no additional charges, determine faculty bank only
401            else:
402                if student.is_postgrad:
403                    bank = BANK_ACCOUNTS['postgrad']
404                else:
405                    bank = BANK_ACCOUNTS['acceptance']
406                xmldict['institution_acct'] = bank[0]
407                xmldict['institution_bank_id'] = bank[1]
408
409        # Other fees
410        elif category in BANK_ACCOUNTS.keys():
411            bank = BANK_ACCOUNTS[category]
412            xmldict['institution_acct'] = bank[0]
413            xmldict['institution_bank_id'] = bank[1]
414
415        if not xmltext and provider_amt == 0:
416            xmltext = """<payment_item_detail>
417<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
418<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
419</item_details>
420</payment_item_detail>""" % xmldict
421        elif not xmltext:
422            xmltext = """<payment_item_detail>
423<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
424<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
425<item_detail item_id="2" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
426</item_details>
427</payment_item_detail>""" % xmldict
428        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
429        xmlitems = ''
430        xmldoc = minidom.parseString(xmltext)
431        itemlist = xmldoc.getElementsByTagName('item_detail')
432        for s in itemlist:
433            xmlitems += "%s (%s %s, %s (%s)),  " % (
434                s.attributes['item_name'].value,
435                u'\u20a6',
436                int(s.attributes['item_amt'].value)/100,
437                s.attributes['acct_num'].value,
438                s.attributes['bank_id'].value,
439                )
440        self.context.p_split_data = xmlitems
441        self.context.provider_amt = provider_amt
442        self.amount_auth = int(100 * self.context.amount_auth)
443        hashargs = (
444            self.context.p_id +
445            PRODUCT_ID +
446            self.pay_item_id +
447            str(int(self.amount_auth)) +
448            self.site_redirect_url +
449            MAC)
450        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
451        return
452
453
454class CustomInterswitchPaymentRequestWebservicePageApplicant(
455    InterswitchPaymentRequestWebservicePageApplicant):
456    """Request webservice view for the CollegePAY gateway
457    """
458    grok.context(ICustomApplicantOnlinePayment)
459    gateway_host = HOST
460    gateway_url = URL
461    https = HTTPS
462    mac = MAC
463    product_id = PRODUCT_ID
464
465class CustomInterswitchPaymentVerifyWebservicePageApplicant(
466    InterswitchPaymentVerifyWebservicePageApplicant):
467    """Payment verify view for the CollegePAY gateway
468    """
469    grok.context(ICustomApplicantOnlinePayment)
470    gateway_host = HOST
471    gateway_url = URL
472    https = HTTPS
473    mac = MAC
474    product_id = PRODUCT_ID
475
476class CustomInterswitchPaymentRequestWebservicePageStudent(
477    InterswitchPaymentRequestWebservicePageStudent):
478    """Request webservice view for the CollegePAY gateway
479    """
480    grok.context(ICustomStudentOnlinePayment)
481    gateway_host = HOST
482    gateway_url = URL
483    https = HTTPS
484    mac = MAC
485    product_id = PRODUCT_ID
486
487class CustomInterswitchPaymentVerifyWebservicePageStudent(
488    InterswitchPaymentVerifyWebservicePageStudent):
489    """Payment verify view for the CollegePAY gateway
490    """
491    grok.context(ICustomStudentOnlinePayment)
492    gateway_host = HOST
493    gateway_url = URL
494    https = HTTPS
495    mac = MAC
496    product_id = PRODUCT_ID
Note: See TracBrowser for help on using the repository browser.