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

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

Adjust to new Interswitch API.

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