Ignore:
Timestamp:
19 May 2021, 07:47:21 (4 years ago)
Author:
Henrik Bettermann
Message:

Implement PAYDirect Bank Branch payment.

Location:
main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch
Files:
2 added
3 edited

Legend:

Unmodified
Added
Removed
  • main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch/browser.py

    r16247 r16484  
    2222from waeup.kofa.interfaces import IKofaUtils
    2323from waeup.kofa.utils.helpers import to_timezone
    24 from waeup.kofa.browser.layout import UtilityView, KofaPage
     24from waeup.kofa.browser.layout import UtilityView, KofaPage, KofaFormPage, action
    2525from waeup.kofa.browser.viewlets import ManageActionButton
    2626from waeup.kofa.students.interfaces import IStudentsUtils
     
    2828from waeup.kofa.applicants.browser import OnlinePaymentDisplayFormPage as OPDPApplicant
    2929from kofacustom.nigeria.interswitch.helpers import (
    30     query_interswitch, write_payments_log)
     30    query_interswitch, write_payments_log, fetch_booking_details)
    3131from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
    3232from kofacustom.nigeria.students.interfaces import INigeriaStudentOnlinePayment
    3333from kofacustom.nigeria.applicants.interfaces import INigeriaApplicantOnlinePayment
     34from kofacustom.nigeria.interswitch.tests import PAYDIRECT_URL, PAYDIRECT_HOST, MERCHANT_ID
    3435from kofacustom.nigeria.interfaces import MessageFactory as _
    3536
     
    5859    grok.require('waeup.payStudent')
    5960    icon = 'actionicon_pay.png'
    60     text = _('Pay via Interswitch')
     61    text = _('Pay via Interswitch CollegePAY')
    6162    target = 'goto_interswitch'
    6263
     
    8990    grok.require('waeup.payStudent')
    9091    icon = 'actionicon_call.png'
    91     text = _('Requery Interswitch History')
     92    text = _('Requery CollegePAY History')
    9293    target = 'request_webservice'
    9394
     
    315316    grok.template('student_goto_interswitch')
    316317    grok.require('waeup.payStudent')
    317     label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
     318    label = _('Submit data to CollegePAY')
    318319    submit_button = _('Submit')
    319320
     
    392393    grok.template('applicant_goto_interswitch')
    393394    grok.name('goto_interswitch')
    394     label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
     395    label = _('Submit data to CollegePAY')
    395396    submit_button = _('Submit')
    396397
     
    444445        self.amount_auth = int(100 * self.context.amount_auth)
    445446        return
     447
  • main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch/helpers.py

    r16167 r16484  
    3030def SOAP_post(soap_action, xml, host, url, https):
    3131    """Handles making the SOAP request.
    32 
    33     Further reading:
    34     http://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryWs.asmx?op=getTransactionData
    3532    """
    3633    if https:
     
    4845    return response
    4946
     47def write_payments_log(id, payment):
     48    payment.logger.info(
     49        '%s,%s,%s,%s,%s,%s,%s,%s,,,' % (
     50        id, payment.p_id, payment.p_category,
     51        payment.amount_auth, payment.r_code,
     52        payment.provider_amt, payment.gateway_amt,
     53        payment.thirdparty_amt))
     54
     55# CollegePAY helper functions
    5056
    5157def get_SOAP_response(product_id, transref, host, url, https):
     
    228234    return True, msg, log
    229235
    230 def write_payments_log(id, payment):
    231     payment.logger.info(
    232         '%s,%s,%s,%s,%s,%s,%s,%s,,,' % (
    233         id, payment.p_id, payment.p_category,
    234         payment.amount_auth, payment.r_code,
    235         payment.provider_amt, payment.gateway_amt,
    236         payment.thirdparty_amt))
     236# PAYDirect helper functions
     237
     238def get_SOAP_response_paydirect(merchant_id, p_id, host, url, https):
     239    xml="""\
     240<?xml version="1.0" encoding="utf-8"?>
     241<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:book="http://interswitchng.com/bookonhold">
     242   <soap:Header/>
     243   <soap:Body>
     244      <book:FetchBookingDetails>
     245         <book:ReservationDetailsRequest>
     246            <book:MerchantId>%s</book:MerchantId>
     247            <book:Bookings>
     248               <book:Booking>
     249                  <book:ReferenceNumber>%s%s</book:ReferenceNumber>
     250               </book:Booking>
     251            </book:Bookings>
     252         </book:ReservationDetailsRequest>
     253      </book:FetchBookingDetails>
     254   </soap:Body>
     255</soap:Envelope>""" % (merchant_id, merchant_id, p_id[1:])
     256
     257    response=SOAP_post(
     258        "http://interswitchng.com/bookonhold/FetchBookingDetails",
     259        xml, host, url, https)
     260    if response.status!=200:
     261        error = 'Connection error (%s, %s)' % (response.status, response.reason)
     262        return error
     263    result_xml = response.read()
     264    return result_xml
     265
     266def fetch_booking_details(payment, merchant_id, host, url, https):
     267    result_xml = get_SOAP_response_paydirect(
     268        merchant_id, payment.p_id, host, url, https)
     269    if result_xml.startswith('Connection error'):
     270        return False, result_xml, result_xml
     271    doc=parseString(result_xml)
     272    if not doc.getElementsByTagName('PaymentStatus'):
     273        msg = _('Your payment %s was not found.' % payment.p_id)
     274        log = 'payment %s cannot be found' % payment.p_id
     275        return False, msg, log
     276    p_status = doc.getElementsByTagName('PaymentStatus')[0].firstChild.data
     277    payment.r_code = p_status
     278    try:
     279        payment.r_desc = "%s - %s - %s" % (
     280            doc.getElementsByTagName('ChannelName')[0].firstChild.data,
     281            doc.getElementsByTagName('TerminalId')[0].firstChild.data,
     282            doc.getElementsByTagName('Location'))[0].firstChild.data
     283    except AttributeError:
     284        pass
     285    try:
     286        amount = doc.getElementsByTagName('Amount')[0].firstChild.data
     287        payment.r_amount_approved = int(amount)
     288    except AttributeError:
     289        pass
     290    try:
     291        payment.r_pay_reference = doc.getElementsByTagName(
     292            'ReferenceNumber').firstChild.data
     293    except AttributeError:
     294        pass
     295    if p_status not in ('Pending', 'Completed'):
     296        msg = _('Unknown status: %s' % sr[0])
     297        log = 'invalid callback for payment %s: %s' % (payment.p_id, status)
     298        payment.p_state = 'failed'
     299        notify(grok.ObjectModifiedEvent(payment))
     300        return False, msg, log
     301    if p_status == 'Completed' and not payment.r_amount_approved:
     302        msg = _('Amount unconfirmed')
     303        log = 'unsuccessful callback for payment %s: amount unconfirmed' % payment.p_id
     304        payment.p_state = 'failed'
     305        notify(grok.ObjectModifiedEvent(payment))
     306        return False, msg, log
     307    if p_status == 'Pending':
     308        msg = _('Payment pending')
     309        log = 'unsuccessful callback for payment %s: pending' % payment.p_id
     310        payment.p_state = 'failed'
     311        notify(grok.ObjectModifiedEvent(payment))
     312        return False, msg, log
     313    if payment.r_amount_approved != payment.amount_auth:
     314        msg = _('Callback amount does not match.')
     315        log = 'unsuccessful callback for %s payment %s: callback amount %s does not match' % (
     316            payment.p_category, payment.p_id, amount)
     317        payment.p_state = 'failed'
     318        notify(grok.ObjectModifiedEvent(payment))
     319        return False, msg, log
     320    payment.p_state = 'paid'
     321    payment.payment_date = datetime.utcnow()
     322    msg = _('Successful callback received')
     323    log = 'valid callback for %s payment %s: %s' % (
     324        payment.p_category, payment.p_id, p_status)
     325    notify(grok.ObjectModifiedEvent(payment))
     326    return True, msg, log
  • main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch/tests.py

    r16378 r16484  
    2020from zope.component import createObject, getUtility
    2121from zope.catalog.interfaces import ICatalog
     22from xml.dom.minidom import parseString
    2223from hurry.workflow.interfaces import IWorkflowState
    2324from waeup.kofa.students.tests.test_browser import StudentsFullSetup
     
    2526from waeup.kofa.configuration import SessionConfiguration
    2627from waeup.kofa.students.payments import StudentOnlinePayment
    27 from kofacustom.nigeria.interswitch.helpers import query_interswitch
     28from waeup.kofa.browser.tests.test_pdf import samples_dir
     29from kofacustom.nigeria.interswitch.helpers import (
     30    query_interswitch, get_SOAP_response_paydirect, SOAP_post)
    2831from kofacustom.nigeria.testing import FunctionalLayer
    2932
     
    3134#   If you enable this, please make sure the external services
    3235#   do exist really and are not bothered by being spammed by a test programme.
    33 EXTERNAL_TESTS = False
    34 
    35 def external_test(func):
    36     if not EXTERNAL_TESTS:
     36EXTERNAL_TESTS_1 = True
     37EXTERNAL_TESTS_2 = True
     38
     39PAYDIRECT_HOST = 'sandbox.interswitchng.com'
     40PAYDIRECT_URL = '/bookonhold/bookonhold.asmx'
     41MERCHANT_ID = '6033'
     42
     43def external_test_1(func):
     44    if not EXTERNAL_TESTS_1:
     45        myself = __file__
     46        if myself.endswith('.pyc'):
     47            myself = myself[:-1]
     48        print "WARNING: external tests are skipped!"
     49        print "WARNING: edit %s to enable them." % myself
     50        return
     51    return func
     52
     53def external_test_2(func):
     54    if not EXTERNAL_TESTS_2:
    3755        myself = __file__
    3856        if myself.endswith('.pyc'):
     
    7290    def test_interswitch_form(self):
    7391        # Manager can access InterswitchForm
    74         self.browser.getLink("Pay via Interswitch", index=0).click()
    75         # The total amount to be processed by Interswitch
    76         # has been reduced by the Interswitch fee of 150 Nairas
     92        self.browser.getLink("Pay via Interswitch CollegePAY", index=0).click()
    7793        self.assertMatches('...<input type="hidden" name="pay_item_id" />...',
    7894                           self.browser.contents)
     
    101117        self.assertEqual(self.student['payments'][value].gateway_amt, 0.0)
    102118        self.browser.getLink(value).click()
    103         self.browser.getLink("Pay via Interswitch", index=0).click()
     119        self.browser.getLink("Pay via Interswitch CollegePAY", index=0).click()
    104120        # Split amounts have been set.
    105121        self.assertEqual(self.student['payments'][value].provider_amt, 0.0)
     
    113129    def test_interswitch_form_ticket_expired(self):
    114130        # Manager can access InterswitchForm
    115         self.browser.getLink("Pay via Interswitch", index=0).click()
     131        self.browser.getLink("Pay via Interswitch CollegePAY", index=0).click()
    116132        self.assertMatches('...<input type="hidden" name="pay_item_id" />...',
    117133                           self.browser.contents)
     
    125141        self.payment.creation_date -= delta
    126142        self.browser.open(self.payment_url)
    127         self.browser.getLink("Pay via Interswitch", index=0).click()
     143        self.browser.getLink("Pay via Interswitch CollegePAY", index=0).click()
    128144        self.assertMatches(
    129145            '...This payment ticket is too old. Please create a new ticket...',
     
    132148        self.payment.creation_date += delta
    133149        self.browser.open(self.payment_url)
    134         self.browser.getLink("Pay via Interswitch", index=0).click()
     150        self.browser.getLink("Pay via Interswitch CollegePAY").click()
    135151        self.assertMatches('...Total Amount Authorized:...',
    136152                           self.browser.contents)
     
    140156        # We should not have TZ data in timestamps processed, but it looks
    141157        # like we get some with imports :-/
    142         self.browser.getLink("Pay via Interswitch", index=0).click()
     158        self.browser.getLink("Pay via Interswitch CollegePAY", index=0).click()
    143159        delta = timedelta(days=8)
    144160        self.payment.creation_date -= delta
     
    147163            self.payment.creation_date)
    148164        self.browser.open(self.payment_url)
    149         self.browser.getLink("Pay via Interswitch", index=0).click()
     165        self.browser.getLink("Pay via Interswitch CollegePAY", index=0).click()
    150166        self.assertMatches(
    151167            '...This payment ticket is too old. Please create a new ticket...',
     
    154170        self.payment.creation_date += delta
    155171        self.browser.open(self.payment_url)
    156         self.browser.getLink("Pay via Interswitch", index=0).click()
    157         self.assertMatches('...Total Amount Authorized:...',
    158                            self.browser.contents)
    159 
    160     @external_test
     172        self.browser.getLink("Pay via Interswitch CollegePAY").click()
     173        self.assertMatches('...Total Amount Authorized:...',
     174                           self.browser.contents)
     175
     176    @external_test_1
    161177    def test_query_interswitch_SOAP(self):
    162178        host = 'webpay.interswitchng.com'
     
    176192            'ZIB|WEB|ABAL|3-11-2015|021336:000457580882' in log)
    177193
    178     @external_test
     194    @external_test_1
    179195    def test_query_interswitch_JSON(self):
    180196        host = 'webpay.interswitchng.com'
     
    209225            "u'LeadBankName': None}" in log)
    210226
     227# PAYDirect tests
     228
     229    def create_booking(self, p_id):
     230        xml="""\
     231<?xml version="1.0" encoding="utf-8"?>
     232<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
     233  <soap:Body>
     234    <CreateBooking xmlns="http://interswitchng.com/bookonhold">
     235      <ReservationRequest>
     236        <MerchantId>%s</MerchantId>
     237        <Bookings>
     238          <Booking>
     239            <ReferenceNumber>%s%s</ReferenceNumber>
     240            <ResponseCode>01</ResponseCode>
     241            <ResponseDescription>02</ResponseDescription>
     242            <Description>My Description</Description>
     243            <Amount>1000</Amount>
     244            <DateBooked>2021-05-05</DateBooked>
     245            <DateExpired>2025-05-05</DateExpired>
     246            <FirstName>Dummy</FirstName>
     247            <LastName>Student</LastName>
     248            <Status>string</Status>
     249            <PaymentStatus>string</PaymentStatus>
     250            <PaymentReference>string</PaymentReference>
     251            <ChannelName>04</ChannelName>
     252            <TerminalId>05</TerminalId>
     253            <Location>string</Location>
     254            <PaymentDate>2020-05-05</PaymentDate>
     255            <PaymentMethod>string</PaymentMethod>
     256            <Email>aa@aa.aa</Email>
     257            <ItemCode>01</ItemCode>
     258          </Booking>
     259        </Bookings>
     260      </ReservationRequest>
     261    </CreateBooking>
     262  </soap:Body>
     263</soap:Envelope>""" % (MERCHANT_ID, MERCHANT_ID, p_id[1:])
     264
     265        SOAP_post("http://interswitchng.com/bookonhold/CreateBooking",xml,
     266                  PAYDIRECT_HOST, PAYDIRECT_URL, True)
     267
     268    @external_test_2
     269    def test_get_SOAP_response_paydirect(self):
     270        p_id = 'p6021570467807'
     271        self.create_booking(p_id)
     272        result_xml = get_SOAP_response_paydirect(
     273            MERCHANT_ID, p_id, PAYDIRECT_HOST, PAYDIRECT_URL, True)
     274        doc=parseString(result_xml)
     275        status=doc.getElementsByTagName('PaymentStatus')[0].firstChild.data
     276        amount=doc.getElementsByTagName('Amount')[0].firstChild
     277        self.assertEqual(status, 'Pending')
     278        self.assertEqual(amount, None)
     279        p_id = 'p5812734587097'
     280        result_xml = get_SOAP_response_paydirect(MERCHANT_ID, p_id, PAYDIRECT_HOST, PAYDIRECT_URL, True)
     281        doc=parseString(result_xml)
     282        self.assertEqual(doc.getElementsByTagName('PaymentStatus'), [])
     283
     284    @external_test_2
     285    def test_paydirect(self):
     286        # Manager can access InterswitchForm
     287        self.browser.getLink("Pay via Interswitch PAYDirect").click()
     288        self.assertMatches('...Total Amount Authorized:...',
     289                           self.browser.contents)
     290        self.assertEqual(self.student.current_mode, 'ug_ft')
     291        # Create school fee ticket for returning students. Payment is made
     292        # for next session.
     293        current_payment_key = self.student['payments'].keys()[0]
     294        self.certificate.study_mode = u'ug_pt'
     295        IWorkflowState(self.student).setState('returning')
     296        configuration = createObject('waeup.SessionConfiguration')
     297        configuration.academic_session = 2005
     298        self.app['configuration'].addSessionConfiguration(configuration)
     299        self.browser.open(self.payments_path + '/addop')
     300        self.browser.getControl(name="form.p_category").value = ['schoolfee']
     301        self.browser.getControl("Create ticket").click()
     302        self.browser.open(self.payments_path)
     303        ctrl = self.browser.getControl(name='val_id')
     304        value = ctrl.options[1]
     305        self.assertEqual(self.student['payments'][value].provider_amt, 0.0)
     306        self.assertEqual(self.student['payments'][value].gateway_amt, 0.0)
     307        self.browser.getLink(value).click()
     308        self.browser.getLink("Pay via Interswitch PAYDirect").click()
     309        # Split amounts have been set.
     310        self.assertEqual(self.student['payments'][value].provider_amt, 0.0)
     311        self.assertEqual(self.student['payments'][value].gateway_amt, 300.0)
     312        # Reference number has not yet been saved but can be seen in browser
     313        self.assertEqual(self.student['payments'][value].r_pay_reference, None)
     314        ref_number = '%s%s' % (MERCHANT_ID,value[1:])
     315        self.assertTrue(ref_number in self.browser.contents)
     316        self.browser.getControl("Requery").click()
     317        self.assertTrue('Your payment %s was not found.' % value in self.browser.contents)
     318        self.create_booking(value)
     319        self.browser.getLink("Pay via Interswitch PAYDirect").click()
     320        self.browser.getControl("Requery").click()
     321        self.assertTrue('pending' in self.browser.contents)
     322        # Reference number has still not been saved because the element was empty
     323        self.assertEqual(self.student['payments'][value].r_pay_reference, None)
     324        # Here we have to stop testing because we cannot create completed bookings
     325
     326        # Students can download reference number slip
     327        self.browser.getLink("Pay via Interswitch PAYDirect").click()
     328        self.browser.getLink("Download reference number slip").click()
     329        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     330        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
     331        path = os.path.join(samples_dir(), 'refnumberslip.pdf')
     332        open(path, 'wb').write(self.browser.contents)
     333        print "Sample PDF refnumberslip.pdf written to %s" % path
     334
    211335class InterswitchTestsApplicants(ApplicantsFullSetup):
    212336    """Tests for the Interswitch payment gateway.
     
    241365            self.browser.contents)
    242366        # Manager can access InterswitchForm
    243         self.browser.getLink("Pay via Interswitch", index=0).click()
     367        self.browser.getLink("Pay via Interswitch CollegePAY", index=0).click()
    244368        self.assertMatches('...Total Amount Authorized:...',
    245369                           self.browser.contents)
Note: See TracChangeset for help on using the changeset viewer.