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

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

Implement PAYDirect Bank Branch payment.

  • Property svn:keywords set to Id
File size: 12.7 KB
RevLine 
[9746]1## $Id: helpers.py 16484 2021-05-19 07:47:21Z 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
28from kofacustom.nigeria.interfaces import MessageFactory as _
29
[11914]30def SOAP_post(soap_action, xml, host, url, https):
[9746]31    """Handles making the SOAP request.
32    """
[11914]33    if https:
34        h = httplib.HTTPSConnection(host)
35    else:
36        h = httplib.HTTPConnection(host)
[9746]37    headers={
38        'Host':host,
39        'Content-Type':'text/xml; charset=utf-8',
40        'Content-Length':len(xml),
41        'SOAPAction':'"%s"' % soap_action,
42    }
43    h.request('POST', url, body=xml,headers=headers)
44    response = h.getresponse()
45    return response
46
[16484]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))
[13387]54
[16484]55# CollegePAY helper functions
56
[11914]57def get_SOAP_response(product_id, transref, host, url, https):
[9746]58    xml="""\
59<?xml version="1.0" encoding="utf-8"?>
60<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/">
61  <soap:Body>
62    <getTransactionData xmlns="http://tempuri.org/">
63      <product_id>%s</product_id>
64      <trans_ref>%s</trans_ref>
65    </getTransactionData>
66  </soap:Body>
67</soap:Envelope>""" % (product_id, transref)
[11914]68    response=SOAP_post("http://tempuri.org/getTransactionData",xml, host, url, https)
[9746]69    if response.status!=200:
70        return 'Connection error (%s, %s)' % (response.status, response.reason)
71    result_xml = response.read()
72    doc=parseString(result_xml)
73    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
74    return response
75
[13387]76
77def get_JSON_response(product_id, transref, host, url, https, mac, amount):
78    hashargs = product_id + transref + mac
79    hashvalue = hashlib.sha512(hashargs).hexdigest()
80    headers={
81        'Content-Type':'text/xml; charset=utf-8',
82        'Hash':hashvalue,
83    }
84    if https:
85        h = httplib.HTTPSConnection(host)
86    else:
87        h = httplib.HTTPConnection(host)
88    amount = int(100 * amount)
89    args = {'productid': product_id,
90            'transactionreference': transref,
91            'amount': amount}
92    url = '%s?' % url + urlencode(args)
93    h.request("GET", url, headers=headers)
94    response = h.getresponse()
95    if response.status!=200:
96        return {'error': 'Connection error (%s, %s)' % (response.status, response.reason)}
97    jsonout = response.read()
98    parsed_json = json.loads(jsonout)
99    return parsed_json
100
[13584]101def query_interswitch_SOAP(payment, product_id, host, url, https, verify):
[11914]102    sr = get_SOAP_response(product_id, payment.p_id, host, url, https)
[9746]103    if sr.startswith('Connection error'):
104        msg = _('Connection error')
105        log = sr
106        return False, msg, log
107    wlist = sr.split(':')
[13709]108    if len(wlist) < 7:
[9746]109        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
110        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
111        return False, msg, log
112    payment.r_code = wlist[0]
113    payment.r_desc = wlist[1]
114    payment.r_amount_approved = float(wlist[2]) / 100
115    payment.r_card_num = wlist[3]
116    payment.r_pay_reference = wlist[5]
117    payment.r_company = u'interswitch'
118    if payment.r_code != '00':
119        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
120        log = 'unsuccessful callback for %s payment %s: %s' % (
121            payment.p_category, payment.p_id, sr)
122        payment.p_state = 'failed'
123        notify(grok.ObjectModifiedEvent(payment))
124        return False, msg, log
[14245]125    if round(payment.r_amount_approved, 0) != round(payment.amount_auth, 0):
[9746]126        msg = _('Callback amount does not match.')
127        log = 'wrong callback for %s payment %s: %s' % (
128            payment.p_category, payment.p_id, sr)
129        payment.p_state = 'failed'
130        notify(grok.ObjectModifiedEvent(payment))
131        return False, msg, log
132    if wlist[4] != payment.p_id:
133        msg = _('Callback transaction id does not match.')
134        log = 'wrong callback for %s payment %s: %s' % (
135            payment.p_category, payment.p_id, sr)
136        payment.p_state = 'failed'
137        notify(grok.ObjectModifiedEvent(payment))
138        return False, msg, log
139    payment.p_state = 'paid'
[13584]140    if not verify:
141        payment.payment_date = datetime.utcnow()
[13581]142    msg = _('Successful callback received.')
[9746]143    log = 'valid callback for %s payment %s: %s' % (
144        payment.p_category, payment.p_id, sr)
145    notify(grok.ObjectModifiedEvent(payment))
[9774]146    return True, msg, log
147
[13584]148def query_interswitch(payment, product_id, host, url, https, mac, verify):
[13387]149    # If no mac mac key is given, fall back to deprecated SOAP method
150    # (Uniben, AAUA, FCEOkene).
151    if mac == None:
[13584]152        return query_interswitch_SOAP(
153            payment, product_id, host, url, https, verify)
[13387]154    jr = get_JSON_response(product_id, payment.p_id, host, url,
155                           https, mac, payment.amount_auth)
156    error = jr.get('error')
157    if error:
158        msg = log = error
159        return False, msg, log
160
161    # A typical JSON response
[16114]162
163    # old:
164
[13387]165    #  {u'SplitAccounts': [],
166    #   u'MerchantReference':u'p4210665523377',
167    #   u'PaymentReference':u'GTB|WEB|KPOLY|12-01-2015|013138',
168    #   u'TransactionDate':u'2015-01-12T13:43:39.27',
169    #   u'RetrievalReferenceNumber':u'000170548791',
170    #   u'ResponseDescription': u'Approved Successful',
171    #   u'Amount': 2940000,
172    #   u'CardNumber': u'2507',
173    #   u'ResponseCode': u'00',
174    #   u'LeadBankCbnCode': None,
175    #   u'LeadBankName': None}
176
[16114]177    # new:
178
179    # 'PaymentReference' is maybe missing
180
181    #  {u'SplitAccounts': [],
182    #  u'MerchantReference':u'p5918633006916',
183    #  u'TransactionDate':u'2020-06-11T09:17:37',
184    #  u'ResponseDescription':u'Customer Cancellation',
185    #  u'Amount': 89525000,
186    #  u'CardNumber': u'',
187    #  u'ResponseCode': u'Z6',
188    #  u'BankCode': u''}
189
[16167]190    if not 'ResponseCode' in jr.keys() \
191        or not 'ResponseDescription' in jr.keys() \
192        or not 'Amount' in jr.keys():
[13387]193        msg = _('Invalid callback: ${a}', mapping = {'a': str(jr)})
194        log = 'invalid callback for payment %s: %s' % (payment.p_id, str(jr))
195        return False, msg, log
[13585]196    if verify and jr['ResponseCode'] == '20050':
197        msg = _('Integration method has changed.')
198        log = 'invalid callback for payment %s: %s' % (payment.p_id, str(jr))
199        return False, msg, log
[13387]200    payment.r_code = jr['ResponseCode']
201    payment.r_desc = jr['ResponseDescription']
202    payment.r_amount_approved = jr['Amount'] / 100.0
[16167]203    payment.r_card_num = jr.get('CardNumber', u'')
[16114]204    payment.r_pay_reference = jr.get('PaymentReference', u'')
[15727]205    #payment.r_company = u'interswitch'
[13387]206    if payment.r_code != '00':
207        msg = _('Unsuccessful callback: ${a}', mapping = {'a': payment.r_desc})
208        log = 'unsuccessful callback for %s payment %s: %s' % (
209            payment.p_category, payment.p_id, payment.r_desc)
210        payment.p_state = 'failed'
211        notify(grok.ObjectModifiedEvent(payment))
212        return False, msg, log
[14245]213    if round(payment.r_amount_approved, 0) != round(payment.amount_auth, 0):
[13387]214        msg = _('Callback amount does not match.')
215        log = 'wrong callback for %s payment %s: %s' % (
216            payment.p_category, payment.p_id, str(jr))
217        payment.p_state = 'failed'
218        notify(grok.ObjectModifiedEvent(payment))
219        return False, msg, log
220    if jr['MerchantReference'] != payment.p_id:
221        msg = _('Callback transaction id does not match.')
222        log = 'wrong callback for %s payment %s: %s' % (
223            payment.p_category, payment.p_id, str(jr))
224        payment.p_state = 'failed'
225        notify(grok.ObjectModifiedEvent(payment))
226        return False, msg, log
227    payment.p_state = 'paid'
[13584]228    if not verify:
229        payment.payment_date = datetime.utcnow()
[13387]230    msg = _('Successful callback received')
231    log = 'valid callback for %s payment %s: %s' % (
232        payment.p_category, payment.p_id, str(jr))
233    notify(grok.ObjectModifiedEvent(payment))
234    return True, msg, log
235
[16484]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
Note: See TracBrowser for help on using the repository browser.