source: main/kofacustom.nigeria/trunk/src/kofacustom/nigeria/interswitch/helpers.py @ 16827

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

Set constant item_code.

  • Property svn:keywords set to Id
File size: 14.6 KB
RevLine 
[9746]1## $Id: helpers.py 16587 2021-08-31 06:28:35Z henrik $
2##
3## Copyright (C) 2012 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##
18"""General helper functions for the interswitch module in custom packages.
19"""
[9747]20from datetime import datetime
[9746]21import httplib
[13387]22import hashlib
23import json
24from urllib import urlencode
[9746]25import grok
26from xml.dom.minidom import parseString
27from zope.event import notify
[16487]28from waeup.kofa.payments.interfaces import IPayer
[9746]29from kofacustom.nigeria.interfaces import MessageFactory as _
30
[11914]31def SOAP_post(soap_action, xml, host, url, https):
[9746]32    """Handles making the SOAP request.
33    """
[11914]34    if https:
35        h = httplib.HTTPSConnection(host)
36    else:
37        h = httplib.HTTPConnection(host)
[9746]38    headers={
39        'Host':host,
40        'Content-Type':'text/xml; charset=utf-8',
41        'Content-Length':len(xml),
42        'SOAPAction':'"%s"' % soap_action,
43    }
44    h.request('POST', url, body=xml,headers=headers)
45    response = h.getresponse()
46    return response
47
[16484]48def write_payments_log(id, payment):
49    payment.logger.info(
50        '%s,%s,%s,%s,%s,%s,%s,%s,,,' % (
51        id, payment.p_id, payment.p_category,
52        payment.amount_auth, payment.r_code,
53        payment.provider_amt, payment.gateway_amt,
54        payment.thirdparty_amt))
[13387]55
[16484]56# CollegePAY helper functions
57
[11914]58def get_SOAP_response(product_id, transref, host, url, https):
[9746]59    xml="""\
60<?xml version="1.0" encoding="utf-8"?>
61<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/">
62  <soap:Body>
63    <getTransactionData xmlns="http://tempuri.org/">
64      <product_id>%s</product_id>
65      <trans_ref>%s</trans_ref>
66    </getTransactionData>
67  </soap:Body>
68</soap:Envelope>""" % (product_id, transref)
[11914]69    response=SOAP_post("http://tempuri.org/getTransactionData",xml, host, url, https)
[9746]70    if response.status!=200:
71        return 'Connection error (%s, %s)' % (response.status, response.reason)
72    result_xml = response.read()
73    doc=parseString(result_xml)
74    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
75    return response
76
[13387]77
78def get_JSON_response(product_id, transref, host, url, https, mac, amount):
79    hashargs = product_id + transref + mac
80    hashvalue = hashlib.sha512(hashargs).hexdigest()
81    headers={
82        'Content-Type':'text/xml; charset=utf-8',
83        'Hash':hashvalue,
84    }
85    if https:
86        h = httplib.HTTPSConnection(host)
87    else:
88        h = httplib.HTTPConnection(host)
89    amount = int(100 * amount)
90    args = {'productid': product_id,
91            'transactionreference': transref,
92            'amount': amount}
93    url = '%s?' % url + urlencode(args)
94    h.request("GET", url, headers=headers)
95    response = h.getresponse()
96    if response.status!=200:
97        return {'error': 'Connection error (%s, %s)' % (response.status, response.reason)}
98    jsonout = response.read()
99    parsed_json = json.loads(jsonout)
100    return parsed_json
101
[13584]102def query_interswitch_SOAP(payment, product_id, host, url, https, verify):
[11914]103    sr = get_SOAP_response(product_id, payment.p_id, host, url, https)
[9746]104    if sr.startswith('Connection error'):
105        msg = _('Connection error')
106        log = sr
107        return False, msg, log
108    wlist = sr.split(':')
[13709]109    if len(wlist) < 7:
[9746]110        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
111        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
112        return False, msg, log
113    payment.r_code = wlist[0]
114    payment.r_desc = wlist[1]
115    payment.r_amount_approved = float(wlist[2]) / 100
116    payment.r_card_num = wlist[3]
117    payment.r_pay_reference = wlist[5]
118    payment.r_company = u'interswitch'
119    if payment.r_code != '00':
120        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
121        log = 'unsuccessful callback for %s payment %s: %s' % (
122            payment.p_category, payment.p_id, sr)
123        payment.p_state = 'failed'
124        notify(grok.ObjectModifiedEvent(payment))
125        return False, msg, log
[14245]126    if round(payment.r_amount_approved, 0) != round(payment.amount_auth, 0):
[9746]127        msg = _('Callback amount does not match.')
128        log = 'wrong callback for %s payment %s: %s' % (
129            payment.p_category, payment.p_id, sr)
130        payment.p_state = 'failed'
131        notify(grok.ObjectModifiedEvent(payment))
132        return False, msg, log
133    if wlist[4] != payment.p_id:
134        msg = _('Callback transaction id does not match.')
135        log = 'wrong callback for %s payment %s: %s' % (
136            payment.p_category, payment.p_id, sr)
137        payment.p_state = 'failed'
138        notify(grok.ObjectModifiedEvent(payment))
139        return False, msg, log
140    payment.p_state = 'paid'
[13584]141    if not verify:
142        payment.payment_date = datetime.utcnow()
[13581]143    msg = _('Successful callback received.')
[9746]144    log = 'valid callback for %s payment %s: %s' % (
145        payment.p_category, payment.p_id, sr)
146    notify(grok.ObjectModifiedEvent(payment))
[9774]147    return True, msg, log
148
[13584]149def query_interswitch(payment, product_id, host, url, https, mac, verify):
[13387]150    # If no mac mac key is given, fall back to deprecated SOAP method
151    # (Uniben, AAUA, FCEOkene).
152    if mac == None:
[13584]153        return query_interswitch_SOAP(
154            payment, product_id, host, url, https, verify)
[13387]155    jr = get_JSON_response(product_id, payment.p_id, host, url,
156                           https, mac, payment.amount_auth)
157    error = jr.get('error')
158    if error:
159        msg = log = error
160        return False, msg, log
161
162    # A typical JSON response
[16114]163
164    # old:
165
[13387]166    #  {u'SplitAccounts': [],
167    #   u'MerchantReference':u'p4210665523377',
168    #   u'PaymentReference':u'GTB|WEB|KPOLY|12-01-2015|013138',
169    #   u'TransactionDate':u'2015-01-12T13:43:39.27',
170    #   u'RetrievalReferenceNumber':u'000170548791',
171    #   u'ResponseDescription': u'Approved Successful',
172    #   u'Amount': 2940000,
173    #   u'CardNumber': u'2507',
174    #   u'ResponseCode': u'00',
175    #   u'LeadBankCbnCode': None,
176    #   u'LeadBankName': None}
177
[16114]178    # new:
179
180    # 'PaymentReference' is maybe missing
181
182    #  {u'SplitAccounts': [],
183    #  u'MerchantReference':u'p5918633006916',
184    #  u'TransactionDate':u'2020-06-11T09:17:37',
185    #  u'ResponseDescription':u'Customer Cancellation',
186    #  u'Amount': 89525000,
187    #  u'CardNumber': u'',
188    #  u'ResponseCode': u'Z6',
189    #  u'BankCode': u''}
190
[16167]191    if not 'ResponseCode' in jr.keys() \
192        or not 'ResponseDescription' in jr.keys() \
193        or not 'Amount' in jr.keys():
[13387]194        msg = _('Invalid callback: ${a}', mapping = {'a': str(jr)})
195        log = 'invalid callback for payment %s: %s' % (payment.p_id, str(jr))
196        return False, msg, log
[13585]197    if verify and jr['ResponseCode'] == '20050':
198        msg = _('Integration method has changed.')
199        log = 'invalid callback for payment %s: %s' % (payment.p_id, str(jr))
200        return False, msg, log
[13387]201    payment.r_code = jr['ResponseCode']
202    payment.r_desc = jr['ResponseDescription']
203    payment.r_amount_approved = jr['Amount'] / 100.0
[16167]204    payment.r_card_num = jr.get('CardNumber', u'')
[16114]205    payment.r_pay_reference = jr.get('PaymentReference', u'')
[15727]206    #payment.r_company = u'interswitch'
[13387]207    if payment.r_code != '00':
208        msg = _('Unsuccessful callback: ${a}', mapping = {'a': payment.r_desc})
209        log = 'unsuccessful callback for %s payment %s: %s' % (
210            payment.p_category, payment.p_id, payment.r_desc)
211        payment.p_state = 'failed'
212        notify(grok.ObjectModifiedEvent(payment))
213        return False, msg, log
[14245]214    if round(payment.r_amount_approved, 0) != round(payment.amount_auth, 0):
[13387]215        msg = _('Callback amount does not match.')
216        log = 'wrong callback for %s payment %s: %s' % (
217            payment.p_category, payment.p_id, str(jr))
218        payment.p_state = 'failed'
219        notify(grok.ObjectModifiedEvent(payment))
220        return False, msg, log
221    if jr['MerchantReference'] != payment.p_id:
222        msg = _('Callback transaction id does not match.')
223        log = 'wrong callback for %s payment %s: %s' % (
224            payment.p_category, payment.p_id, str(jr))
225        payment.p_state = 'failed'
226        notify(grok.ObjectModifiedEvent(payment))
227        return False, msg, log
228    payment.p_state = 'paid'
[13584]229    if not verify:
230        payment.payment_date = datetime.utcnow()
[13387]231    msg = _('Successful callback received')
232    log = 'valid callback for %s payment %s: %s' % (
233        payment.p_category, payment.p_id, str(jr))
234    notify(grok.ObjectModifiedEvent(payment))
235    return True, msg, log
236
[16484]237# PAYDirect helper functions
238
[16587]239def create_paydirect_booking(merchant_id, payment, item_code, host, url, https):
[16487]240    p_id = payment.p_id
241    description = payment.p_category
[16515]242    amount = int(100*payment.amount_auth)  # Amount in Kobo
[16487]243    date_booked = payment.creation_date.strftime("%Y-%m-%d")
244    date_expired = "2099-12-31"
245    firstname = IPayer(payment).display_fullname.split()[0]
246    lastname = IPayer(payment).display_fullname.split()[-1]
247    id = IPayer(payment).id
248    email = IPayer(payment).email
249
250    xml="""\
251<?xml version="1.0" encoding="utf-8"?>
252<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/">
253  <soap:Body>
254    <CreateBooking xmlns="http://interswitchng.com/bookonhold">
255      <ReservationRequest>
256        <MerchantId>%s</MerchantId>
257        <Bookings>
258          <Booking>
259            <ReferenceNumber>%s%s</ReferenceNumber>
260            <Description>%s</Description>
261            <Amount>%s</Amount>
262            <DateBooked>%s</DateBooked>
263            <DateExpired>%s</DateExpired>
264            <FirstName>%s</FirstName>
265            <LastName>%s</LastName>
266            <Email>%s</Email>
267            <ItemCode>%s</ItemCode>
268          </Booking>
269        </Bookings>
270      </ReservationRequest>
271    </CreateBooking>
272  </soap:Body>
273</soap:Envelope>""" % (
274    merchant_id, merchant_id, p_id[1:],
275    description, amount,
276    date_booked, date_expired,
277    firstname, lastname,
[16587]278    email, item_code)
[16487]279    response=SOAP_post(
280        "http://interswitchng.com/bookonhold/CreateBooking",
281        xml, host, url, https)
282    if response.status!=200:
283        error = 'Connection error (%s, %s)' % (response.status, response.reason)
284        return error
285    result_xml = response.read()
286    return result_xml
287
[16484]288def get_SOAP_response_paydirect(merchant_id, p_id, host, url, https):
289    xml="""\
290<?xml version="1.0" encoding="utf-8"?>
291<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:book="http://interswitchng.com/bookonhold">
292   <soap:Header/>
293   <soap:Body>
294      <book:FetchBookingDetails>
295         <book:ReservationDetailsRequest>
296            <book:MerchantId>%s</book:MerchantId>
297            <book:Bookings>
298               <book:Booking>
299                  <book:ReferenceNumber>%s%s</book:ReferenceNumber>
300               </book:Booking>
301            </book:Bookings>
302         </book:ReservationDetailsRequest>
303      </book:FetchBookingDetails>
304   </soap:Body>
305</soap:Envelope>""" % (merchant_id, merchant_id, p_id[1:])
306
307    response=SOAP_post(
308        "http://interswitchng.com/bookonhold/FetchBookingDetails",
309        xml, host, url, https)
310    if response.status!=200:
311        error = 'Connection error (%s, %s)' % (response.status, response.reason)
312        return error
313    result_xml = response.read()
314    return result_xml
315
316def fetch_booking_details(payment, merchant_id, host, url, https):
317    result_xml = get_SOAP_response_paydirect(
318        merchant_id, payment.p_id, host, url, https)
319    if result_xml.startswith('Connection error'):
320        return False, result_xml, result_xml
321    doc=parseString(result_xml)
322    if not doc.getElementsByTagName('PaymentStatus'):
323        msg = _('Your payment %s was not found.' % payment.p_id)
324        log = 'payment %s cannot be found' % payment.p_id
325        return False, msg, log
326    p_status = doc.getElementsByTagName('PaymentStatus')[0].firstChild.data
327    payment.r_code = p_status
328    try:
[16488]329        payment.r_desc = "Channel Name: %s - Terminal Id: %s - Location: %s" % (
[16484]330            doc.getElementsByTagName('ChannelName')[0].firstChild.data,
331            doc.getElementsByTagName('TerminalId')[0].firstChild.data,
[16488]332            doc.getElementsByTagName('Location')[0].firstChild.data)
[16484]333    except AttributeError:
334        pass
335    try:
336        amount = doc.getElementsByTagName('Amount')[0].firstChild.data
[16515]337        payment.r_amount_approved = float(amount) / 100
[16484]338    except AttributeError:
339        pass
340    try:
341        payment.r_pay_reference = doc.getElementsByTagName(
[16488]342            'ReferenceNumber')[0].firstChild.data
[16484]343    except AttributeError:
344        pass
345    if p_status not in ('Pending', 'Completed'):
[16488]346        msg = _('Unknown status: %s' % p_status)
347        log = 'invalid callback for payment %s: %s' % (payment.p_id, p_status)
[16484]348        payment.p_state = 'failed'
349        notify(grok.ObjectModifiedEvent(payment))
350        return False, msg, log
351    if p_status == 'Completed' and not payment.r_amount_approved:
352        msg = _('Amount unconfirmed')
353        log = 'unsuccessful callback for payment %s: amount unconfirmed' % payment.p_id
354        payment.p_state = 'failed'
355        notify(grok.ObjectModifiedEvent(payment))
356        return False, msg, log
357    if p_status == 'Pending':
358        msg = _('Payment pending')
359        log = 'unsuccessful callback for payment %s: pending' % payment.p_id
360        payment.p_state = 'failed'
361        notify(grok.ObjectModifiedEvent(payment))
362        return False, msg, log
[16556]363    if payment.r_amount_approved != payment.amount_auth:
[16528]364        msg = _('Callback amount does not match net amount.')
[16484]365        log = 'unsuccessful callback for %s payment %s: callback amount %s does not match' % (
366            payment.p_category, payment.p_id, amount)
367        payment.p_state = 'failed'
368        notify(grok.ObjectModifiedEvent(payment))
369        return False, msg, log
370    payment.p_state = 'paid'
371    payment.payment_date = datetime.utcnow()
372    msg = _('Successful callback received')
373    log = 'valid callback for %s payment %s: %s' % (
374        payment.p_category, payment.p_id, p_status)
375    notify(grok.ObjectModifiedEvent(payment))
376    return True, msg, log
Note: See TracBrowser for help on using the repository browser.