diff --git a/eopayment/__init__.py b/eopayment/__init__.py index 7a15cbc..fbd0e55 100644 --- a/eopayment/__init__.py +++ b/eopayment/__init__.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- +import importlib import logging -from common import (URL, HTML, FORM, RECEIVED, ACCEPTED, PAID, DENIED, - CANCELED, CANCELLED, ERROR, WAITING, ResponseError, force_text) +from .common import (URL, HTML, FORM, RECEIVED, ACCEPTED, PAID, DENIED, + CANCELED, CANCELLED, ERROR, WAITING, ResponseError, force_text) __all__ = ['Payment', 'URL', 'HTML', 'FORM', 'SIPS', 'SYSTEMPAY', 'SPPLUS', 'TIPI', 'DUMMY', 'get_backend', 'RECEIVED', 'ACCEPTED', @@ -23,7 +24,7 @@ logger = logging.getLogger(__name__) def get_backend(kind): '''Resolve a backend name into a module object''' - module = __import__(kind, globals(), locals(), []) + module = importlib.import_module('.' + kind, package='eopayment') return module.Payment __BACKENDS = [ DUMMY, SIPS, SIPS2, SYSTEMPAY, SPPLUS, OGONE, PAYBOX, PAYZEN, TIPI ] diff --git a/eopayment/common.py b/eopayment/common.py index ac37e77..258f856 100644 --- a/eopayment/common.py +++ b/eopayment/common.py @@ -2,9 +2,15 @@ import os.path import os import random import logging -import cgi from datetime import date +import six + +if six.PY3: + import html +else: + import cgi + __all__ = ['PaymentCommon', 'URL', 'HTML', 'RANDOM', 'RECEIVED', 'ACCEPTED', 'PAID', 'ERROR', 'WAITING'] @@ -29,15 +35,27 @@ ORDERID_TRANSACTION_SEPARATOR = '!' def force_text(s, encoding='utf-8'): - if isinstance(s, unicode): + if issubclass(type(s), six.text_type): return s try: - return unicode(s, encoding) + if not issubclass(type(s), six.string_types): + if six.PY3: + if isinstance(s, bytes): + s = six.text_type(s, encoding) + else: + s = six.text_type(s) + elif hasattr(s, '__unicode__'): + s = six.text_type(s) + else: + s = six.text_type(bytes(s), encoding) + else: + s = s.decode(encoding) except UnicodeDecodeError: - return unicode(s) + return six.text_type(s, encoding, 'ignore') + return s def force_byte(s, encoding='utf-8'): - if isinstance(s, str): + if isinstance(s, bytes): return s try: return s.encode(encoding) @@ -148,7 +166,10 @@ class Form(object): return s def escape(self, s): - return cgi.escape(force_text(s, self.encoding).encode(self.encoding)) + if six.PY3: + return html.escape(force_text(s, self.encoding)) + else: + return cgi.escape(force_text(s, self.encoding)).encode(self.encoding) def __str__(self): s = '
' % (self.method, self.url) diff --git a/eopayment/dummy.py b/eopayment/dummy.py index 2ed8655..ef08517 100644 --- a/eopayment/dummy.py +++ b/eopayment/dummy.py @@ -1,21 +1,18 @@ -import urllib import string import logging import warnings def N_(message): return message -try: - from cgi import parse_qs -except ImportError: - from urlparse import parse_qs +from six.moves.urllib.parse import parse_qs, urlencode -from common import PaymentCommon, URL, PaymentResponse, PAID, ERROR, WAITING, ResponseError +from .common import (PaymentCommon, URL, PaymentResponse, PAID, ERROR, WAITING, + ResponseError, force_text) __all__ = [ 'Payment' ] SERVICE_URL = 'http://dummy-payment.demo.entrouvert.com/' -ALPHANUM = string.letters + string.digits +ALPHANUM = string.ascii_letters + string.digits LOGGER = logging.getLogger(__name__) class Payment(PaymentCommon): @@ -114,14 +111,14 @@ class Payment(PaymentCommon): } query.update(dict(name=name, address=address, email=email, phone=phone, orderid=orderid, info1=info1, info2=info2, info3=info3)) - for key in query.keys(): + for key in list(query.keys()): if query[key] is None: del query[key] - url = '%s?%s' % (SERVICE_URL, urllib.urlencode(query)) + url = '%s?%s' % (SERVICE_URL, urlencode(query)) return transaction_id, URL, url def response(self, query_string, logger=LOGGER, **kwargs): - form = parse_qs(query_string) + form = parse_qs(force_text(query_string)) if not 'transaction_id' in form: raise ResponseError() transaction_id = form.get('transaction_id',[''])[0] diff --git a/eopayment/ogone.py b/eopayment/ogone.py index 900d348..af0600d 100644 --- a/eopayment/ogone.py +++ b/eopayment/ogone.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- import hashlib import string -import urlparse +import six +from six.moves.urllib import parse as urlparse from decimal import Decimal, ROUND_HALF_UP -from common import (PaymentCommon, PaymentResponse, FORM, CANCELLED, PAID, +from .common import (PaymentCommon, PaymentResponse, FORM, CANCELLED, PAID, ERROR, Form, DENIED, ACCEPTED, ORDERID_TRANSACTION_SEPARATOR, WAITING, ResponseError, force_byte, force_text) def N_(message): return message @@ -499,7 +500,7 @@ class Payment(PaymentCommon): # arrondi comptable francais amount = amount.quantize(Decimal('1.'), rounding=ROUND_HALF_UP) params = { - 'AMOUNT': unicode(amount), + 'AMOUNT': force_text(amount), 'ORDERID': reference, 'PSPID': self.pspid, 'LANGUAGE': language, @@ -517,7 +518,7 @@ class Payment(PaymentCommon): params['EMAIL'] = email if description: params['COM'] = description - for key, value in kwargs.iteritems(): + for key, value in kwargs.items(): params[key.upper()] = value params['SHASIGN'] = self.sha_sign_in(params) # uniformize all values to UTF-8 string @@ -533,7 +534,10 @@ class Payment(PaymentCommon): return reference, FORM, form def response(self, query_string, **kwargs): - params = urlparse.parse_qs(query_string, True) + if six.PY3: + params = urlparse.parse_qs(query_string, True, encoding='iso-8859-1') + else: + params = urlparse.parse_qs(query_string, True) params = dict((key.upper(), params[key][0]) for key in params) if not set(params) >= set(['ORDERID', 'PAYID', 'STATUS', 'NCERROR']): raise ResponseError() diff --git a/eopayment/paybox.py b/eopayment/paybox.py index 2ebed40..4333173 100644 --- a/eopayment/paybox.py +++ b/eopayment/paybox.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 +import codecs from collections import OrderedDict import datetime import logging @@ -9,15 +10,18 @@ from decimal import Decimal, ROUND_DOWN from Crypto.Signature import PKCS1_v1_5 from Crypto.PublicKey import RSA from Crypto.Hash import SHA -import urlparse -import urllib + +import six +from six.moves.urllib import parse as urlparse +from six.moves.urllib import parse as urllib + import base64 from gettext import gettext as _ import string import warnings -from common import (PaymentCommon, PaymentResponse, FORM, PAID, ERROR, Form, - ORDERID_TRANSACTION_SEPARATOR, ResponseError) +from .common import (PaymentCommon, PaymentResponse, FORM, PAID, ERROR, Form, + ORDERID_TRANSACTION_SEPARATOR, ResponseError, force_text) __all__ = ['sign', 'Payment'] @@ -109,9 +113,10 @@ def sign(data, key): algo = ALGOS[v] break assert algo, 'Missing or invalid PBX_HASH' - tosign = ['%s=%s' % (k, unicode(v).encode('utf-8')) for k, v in data] + tosign = ['%s=%s' % (k, force_text(v)) for k, v in data] tosign = '&'.join(tosign) logger.debug('signed string %r', tosign) + tosign = tosign.encode('utf-8') signature = hmac.new(key, tosign, algo) return tuple(data) + (('PBX_HMAC', signature.hexdigest().upper()),) @@ -208,22 +213,22 @@ class Payment(PaymentCommon): def request(self, amount, email, name=None, orderid=None, **kwargs): d = OrderedDict() - d['PBX_SITE'] = unicode(self.site) - d['PBX_RANG'] = unicode(self.rang).strip()[-2:] - d['PBX_IDENTIFIANT'] = unicode(self.identifiant) + d['PBX_SITE'] = force_text(self.site) + d['PBX_RANG'] = force_text(self.rang).strip()[-2:] + d['PBX_IDENTIFIANT'] = force_text(self.identifiant) d['PBX_TOTAL'] = (amount * Decimal(100)).to_integral_value(ROUND_DOWN) - d['PBX_DEVISE'] = unicode(self.devise) + d['PBX_DEVISE'] = force_text(self.devise) transaction_id = kwargs.get('transaction_id') or \ self.transaction_id(12, string.digits, 'paybox', self.site, self.rang, self.identifiant) - d['PBX_CMD'] = unicode(transaction_id) + d['PBX_CMD'] = force_text(transaction_id) # prepend order id command reference if orderid: d['PBX_CMD'] = orderid + ORDERID_TRANSACTION_SEPARATOR + d['PBX_CMD'] - d['PBX_PORTEUR'] = unicode(email) + d['PBX_PORTEUR'] = force_text(email) d['PBX_RETOUR'] = 'montant:M;reference:R;code_autorisation:A;erreur:E;signature:K' d['PBX_HASH'] = 'SHA512' - d['PBX_TIME'] = kwargs.get('time') or (unicode(datetime.datetime.utcnow().isoformat('T')).split('.')[0]+'+00:00') + d['PBX_TIME'] = kwargs.get('time') or (force_text(datetime.datetime.utcnow().isoformat('T')).split('.')[0]+'+00:00') d['PBX_ARCHIVAGE'] = transaction_id if self.normal_return_url: d['PBX_EFFECTUE'] = self.normal_return_url @@ -236,16 +241,21 @@ class Payment(PaymentCommon): "use automatic_return_url", DeprecationWarning) automatic_return_url = self.callback if automatic_return_url: - d['PBX_REPONDRE_A'] = unicode(automatic_return_url) + d['PBX_REPONDRE_A'] = force_text(automatic_return_url) d = d.items() - d = sign(d, self.shared_secret.decode('hex')) + + if six.PY3: + shared_secret = codecs.decode(bytes(self.shared_secret, 'ascii'), 'hex') + else: + shared_secret = codecs.decode(bytes(self.shared_secret), 'hex') + d = sign(d, shared_secret) url = URLS[self.platform] fields = [] for k, v in d: fields.append({ 'type': u'hidden', - 'name': unicode(k), - 'value': unicode(v), + 'name': force_text(k), + 'value': force_text(v), }) form = Form(url, 'POST', fields, submit_name=None, submit_value=u'Envoyer', encoding='utf-8') diff --git a/eopayment/sips.py b/eopayment/sips.py index ef5f1c6..ced03a8 100644 --- a/eopayment/sips.py +++ b/eopayment/sips.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import urlparse +from six.moves.urllib import parse as urlparse import string import subprocess from decimal import Decimal @@ -9,8 +9,8 @@ import os.path import uuid import warnings -from common import PaymentCommon, HTML, PaymentResponse, ResponseError -from cb import CB_RESPONSE_CODES +from .common import PaymentCommon, HTML, PaymentResponse, ResponseError +from .cb import CB_RESPONSE_CODES ''' Payment backend module for the ATOS/SIPS system used by many Frenck banks. @@ -112,7 +112,7 @@ class Payment(PaymentCommon): if PATHFILE in self.options: params[PATHFILE] = self.options[PATHFILE] executable = os.path.join(self.binpath, executable) - args = [executable] + ["%s=%s" % p for p in params.iteritems()] + args = [executable] + ["%s=%s" % p for p in params.items()] self.logger.debug('executing %s' % args) result,_ = subprocess.Popen(' '.join(args), stdout=subprocess.PIPE, shell=True).communicate() diff --git a/eopayment/sips2.py b/eopayment/sips2.py index 584ec56..4def5d7 100644 --- a/eopayment/sips2.py +++ b/eopayment/sips2.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import collections import json -import urlparse +from six.moves.urllib import parse as urlparse import string from decimal import Decimal import uuid @@ -11,8 +11,8 @@ from gettext import gettext as _ import requests import warnings -from common import (PaymentCommon, FORM, Form, PaymentResponse, PAID, ERROR, - CANCELED, ResponseError) +from .common import (PaymentCommon, FORM, Form, PaymentResponse, PAID, ERROR, + CANCELED, ResponseError, force_text) __all__ = ['Payment'] @@ -139,12 +139,12 @@ class Payment(PaymentCommon): } def encode_data(self, data): - return u'|'.join(u'%s=%s' % (unicode(key), unicode(value)) - for key, value in data.iteritems()) + return u'|'.join(u'%s=%s' % (force_text(key), force_text(value)) + for key, value in data.items()) def seal_data(self, data): s = self.encode_data(data) - s += unicode(self.secret_key) + s += force_text(self.secret_key) s = s.encode('utf-8') s = hashlib.sha256(s).hexdigest() return s @@ -172,13 +172,13 @@ class Payment(PaymentCommon): info1=None, info2=None, info3=None, next_url=None, **kwargs): data = self.get_data() transaction_id = self.transaction_id(10, string.digits, 'sips2', data['merchantId']) - data['transactionReference'] = unicode(transaction_id) - data['orderId'] = orderid or unicode(uuid.uuid4()).replace('-', '') + data['transactionReference'] = force_text(transaction_id) + data['orderId'] = orderid or force_text(uuid.uuid4()).replace('-', '') if info1: - data['statementReference'] = unicode(info1) + data['statementReference'] = force_text(info1) else: data['statementReference'] = data['transactionReference'] - data['amount'] = unicode(int(Decimal(amount) * 100)) + data['amount'] = force_text(int(Decimal(amount) * 100)) if email: data['billingContact.email'] = email if 'captureDay' in kwargs: @@ -215,7 +215,7 @@ class Payment(PaymentCommon): def decode_data(self, data): data = data.split('|') - data = [map(unicode, p.split('=', 1)) for p in data] + data = [map(force_text, p.split('=', 1)) for p in data] return collections.OrderedDict(data) def check_seal(self, data, seal): @@ -254,9 +254,9 @@ class Payment(PaymentCommon): for key in sorted(data.keys()): if key in ('keyVersion', 'sealAlgorithm', 'seal'): continue - data_to_send.append(unicode(data[key])) + data_to_send.append(force_text(data[key])) data_to_send_str = u''.join(data_to_send).encode('utf-8') - return hmac.new(unicode(self.secret_key).encode('utf-8'), data_to_send_str, hashlib.sha256).hexdigest() + return hmac.new(force_text(self.secret_key).encode('utf-8'), data_to_send_str, hashlib.sha256).hexdigest() def perform_cash_management_operation(self, endpoint, data): data['merchantId'] = self.merchant_id @@ -282,13 +282,13 @@ class Payment(PaymentCommon): def cancel(self, amount, bank_data, **kwargs): data = {} - data['operationAmount'] = unicode(int(Decimal(amount) * 100)) + data['operationAmount'] = force_text(int(Decimal(amount) * 100)) data['transactionReference'] = bank_data.get('transactionReference') return self.perform_cash_management_operation('cancel', data) def validate(self, amount, bank_data, **kwargs): data = {} - data['operationAmount'] = unicode(int(Decimal(amount) * 100)) + data['operationAmount'] = force_text(int(Decimal(amount) * 100)) data['transactionReference'] = bank_data.get('transactionReference') return self.perform_cash_management_operation('validate', data) diff --git a/eopayment/spplus.py b/eopayment/spplus.py index d6e7b48..e8b1ffc 100644 --- a/eopayment/spplus.py +++ b/eopayment/spplus.py @@ -4,8 +4,8 @@ import binascii from gettext import gettext as _ import hmac import hashlib -import urlparse -import urllib +from six.moves.urllib import parse as urlparse +from six.moves.urllib import parse as urllib import string import datetime as dt import logging @@ -13,15 +13,15 @@ import re import warnings import Crypto.Cipher.DES -from common import (PaymentCommon, URL, PaymentResponse, RECEIVED, ACCEPTED, - PAID, ERROR, ResponseError) +from .common import (PaymentCommon, URL, PaymentResponse, RECEIVED, ACCEPTED, + PAID, ERROR, ResponseError, force_byte) def N_(message): return message __all__ = ['Payment'] -KEY_DES_KEY = '\x45\x1f\xba\x4f\x4c\x3f\xd4\x97' -IV = '\x30\x78\x30\x62\x2c\x30\x78\x30' +KEY_DES_KEY = b'\x45\x1f\xba\x4f\x4c\x3f\xd4\x97' +IV = b'\x30\x78\x30\x62\x2c\x30\x78\x30' REFERENCE = 'reference' ETAT = 'etat' SPCHECKOK = 'spcheckok' @@ -56,7 +56,7 @@ TEST_STATE = ('99',) def decrypt_ntkey(ntkey): - key = binascii.unhexlify(ntkey.replace(' ','')) + key = binascii.unhexlify(ntkey.replace(b' ', b'')) return decrypt_key(key) def decrypt_key(key): @@ -70,7 +70,7 @@ def extract_values(query_string): k, v = kv.split('=', 1) if k != 'hmac': result.append(v) - return ''.join(result) + return force_byte(''.join(result)) def sign_ntkey_query(ntkey, query): key = decrypt_ntkey(ntkey) @@ -89,7 +89,7 @@ def sign_url_paiement(ntkey, query): data_to_sign = ''.join(fields) return hmac.new(key[:20], data_to_sign, hashlib.sha1).hexdigest().upper() -ALPHANUM = string.letters + string.digits +ALPHANUM = string.ascii_letters + string.digits SERVICE_URL = "https://www.spplus.net/paiement/init.do" class Payment(PaymentCommon): @@ -178,7 +178,7 @@ class Payment(PaymentCommon): form = urlparse.parse_qs(query_string) if not set(form) >= set([REFERENCE, ETAT, REFSFP]): raise ResponseError() - for key, value in form.iteritems(): + for key, value in form.items(): form[key] = value[0] logger.debug('received query_string %s' % query_string) logger.debug('parsed as %s' % form) @@ -233,8 +233,8 @@ if __name__ == '__main__': ntkey = '58 6d fc 9c 34 91 9b 86 3f fd 64 63 c9 13 4a 26 ba 29 74 1e c7 e9 80 79' if len(sys.argv) == 2: - print sign_url_paiement(ntkey, sys.argv[1]) - print sign_ntkey_query(ntkey, sys.argv[1]) + print(sign_url_paiement(ntkey, sys.argv[1])) + print(sign_ntkey_query(ntkey, sys.argv[1])) elif len(sys.argv) > 2: - print sign_url_paiement(sys.argv[1], sys.argv[2]) - print sign_ntkey_query(sys.argv[1], sys.argv[2]) + print(sign_url_paiement(sys.argv[1], sys.argv[2])) + print(sign_ntkey_query(sys.argv[1], sys.argv[2])) diff --git a/eopayment/systempayv2.py b/eopayment/systempayv2.py index ccf38d0..d2de01c 100644 --- a/eopayment/systempayv2.py +++ b/eopayment/systempayv2.py @@ -4,13 +4,13 @@ import datetime as dt import hashlib import logging import string -import urlparse +from six.moves.urllib import parse as urlparse import warnings from gettext import gettext as _ -from common import (PaymentCommon, PaymentResponse, PAID, ERROR, FORM, Form, - ResponseError, force_text, force_byte) -from cb import CB_RESPONSE_CODES +from .common import (PaymentCommon, PaymentResponse, PAID, ERROR, FORM, Form, + ResponseError, force_text, force_byte) +from .cb import CB_RESPONSE_CODES __all__ = ['Payment'] @@ -117,20 +117,20 @@ PARAMETERS = [ 'ONEY_SANDBOX, PAYPAL, PAYPAL_SB, PAYSAFECARD, ' 'VISA')), # must be SINGLE or MULTI with parameters - Parameter('vads_payment_config', '', 07, default='SINGLE', + Parameter('vads_payment_config', '', 7, default='SINGLE', choices=('SINGLE', 'MULTI'), needed=True), Parameter('vads_return_mode', None, 48, default='GET', choices=('', 'NONE', 'POST', 'GET')), Parameter('signature', 'an', None, length=40), - Parameter('vads_site_id', 'n', 02, length=8, needed=True, + Parameter('vads_site_id', 'n', 2, length=8, needed=True, description=_(u'Identifiant de la boutique')), Parameter('vads_theme_config', 'ans', 32, max_length=255), - Parameter(VADS_TRANS_DATE, 'n', 04, length=14, needed=True, + Parameter(VADS_TRANS_DATE, 'n', 4, length=14, needed=True, default=isonow), - Parameter('vads_trans_id', 'n', 03, length=6, needed=True), + Parameter('vads_trans_id', 'n', 3, length=6, needed=True), Parameter('vads_validation_mode', 'n', 5, max_length=1, choices=('', 0, 1), default=''), - Parameter('vads_version', 'an', 01, default='V2', needed=True, + Parameter('vads_version', 'an', 1, default='V2', needed=True, choices=('V2',)), Parameter('vads_url_success', 'ans', 24, max_length=127), Parameter('vads_url_referral', 'ans', 26, max_length=127), @@ -173,7 +173,7 @@ EXTRA_RESULT_MAP = { def add_vads(kwargs): new_vargs = {} - for k, v in kwargs.iteritems(): + for k, v in kwargs.items(): if k.startswith('vads_'): new_vargs[k] = v else: @@ -276,8 +276,8 @@ class Payment(PaymentCommon): info2, info3, next_url, kwargs) # amount unit is cents amount = '%.0f' % (100 * amount) - kwargs.update(add_vads({'amount': unicode(amount)})) - if amount < 0: + kwargs.update(add_vads({'amount': force_text(amount)})) + if int(amount) < 0: raise ValueError('amount must be an integer >= 0') normal_return_url = self.normal_return_url if next_url: @@ -285,30 +285,30 @@ class Payment(PaymentCommon): "set normal_return_url in options", DeprecationWarning) normal_return_url = next_url if normal_return_url: - kwargs[VADS_URL_RETURN] = unicode(normal_return_url) + kwargs[VADS_URL_RETURN] = force_text(normal_return_url) if name is not None: - kwargs['vads_cust_name'] = unicode(name) + kwargs['vads_cust_name'] = force_text(name) if first_name is not None: - kwargs[VADS_CUST_FIRST_NAME] = unicode(first_name) + kwargs[VADS_CUST_FIRST_NAME] = force_text(first_name) if last_name is not None: - kwargs[VADS_CUST_LAST_NAME] = unicode(last_name) + kwargs[VADS_CUST_LAST_NAME] = force_text(last_name) if address is not None: - kwargs['vads_cust_address'] = unicode(address) + kwargs['vads_cust_address'] = force_text(address) if email is not None: - kwargs['vads_cust_email'] = unicode(email) + kwargs['vads_cust_email'] = force_text(email) if phone is not None: - kwargs['vads_cust_phone'] = unicode(phone) + kwargs['vads_cust_phone'] = force_text(phone) if info1 is not None: - kwargs['vads_order_info'] = unicode(info1) + kwargs['vads_order_info'] = force_text(info1) if info2 is not None: - kwargs['vads_order_info2'] = unicode(info2) + kwargs['vads_order_info2'] = force_text(info2) if info3 is not None: - kwargs['vads_order_info3'] = unicode(info3) + kwargs['vads_order_info3'] = force_text(info3) if orderid is not None: # check orderid format first name = 'vads_order_id' - orderid = unicode(orderid) + orderid = force_text(orderid) ptype = 'an-' p = Parameter(name, ptype, 13, max_length=32) if not p.check_value(orderid): @@ -318,14 +318,14 @@ class Payment(PaymentCommon): transaction_id = self.transaction_id(6, string.digits, 'systempay', self.options[VADS_SITE_ID]) - kwargs[VADS_TRANS_ID] = unicode(transaction_id) + kwargs[VADS_TRANS_ID] = force_text(transaction_id) fields = kwargs for parameter in PARAMETERS: name = parameter.name # import default parameters from configuration if name not in fields \ and name in self.options: - fields[name] = unicode(self.options[name]) + fields[name] = force_text(self.options[name]) # import default parameters from module if name not in fields and parameter.default is not None: if callable(parameter.default): @@ -333,7 +333,7 @@ class Payment(PaymentCommon): else: fields[name] = parameter.default check_vads(fields) - fields[SIGNATURE] = unicode(self.signature(fields)) + fields[SIGNATURE] = force_text(self.signature(fields)) self.logger.debug('%s request contains fields: %s', __name__, fields) transaction_id = '%s_%s' % (fields[VADS_TRANS_DATE], transaction_id) self.logger.debug('%s transaction id: %s', __name__, transaction_id) @@ -346,14 +346,14 @@ class Payment(PaymentCommon): 'name': force_text(field_name), 'value': force_text(field_value), } - for field_name, field_value in fields.iteritems()]) + for field_name, field_value in fields.items()]) return transaction_id, FORM, form def response(self, query_string, **kwargs): fields = urlparse.parse_qs(query_string, True) if not set(fields) >= set([SIGNATURE, VADS_CTX_MODE, VADS_AUTH_RESULT]): raise ResponseError() - for key, value in fields.iteritems(): + for key, value in fields.items(): fields[key] = value[0] copy = fields.copy() bank_status = [] @@ -417,8 +417,8 @@ class Payment(PaymentCommon): self.logger.debug('ordered keys %s' % ordered_keys) ordered_fields = [force_byte(fields[key]) for key in ordered_keys] secret = getattr(self, 'secret_%s' % fields['vads_ctx_mode'].lower()) - signed_data = '+'.join(ordered_fields) - signed_data = '%s+%s' % (signed_data, force_byte(secret)) + signed_data = b'+'.join(ordered_fields) + signed_data = b'%s+%s' % (signed_data, force_byte(secret)) self.logger.debug(u'generating signature on «%s»', signed_data) sign = hashlib.sha1(signed_data).hexdigest() self.logger.debug(u'signature «%s»', sign) diff --git a/eopayment/tipi.py b/eopayment/tipi.py index 3a3b571..1eb73de 100644 --- a/eopayment/tipi.py +++ b/eopayment/tipi.py @@ -1,15 +1,15 @@ # -*- coding: utf-8 -*- from decimal import Decimal, ROUND_DOWN -from common import (PaymentCommon, PaymentResponse, URL, PAID, DENIED, +from .common import (PaymentCommon, PaymentResponse, URL, PAID, DENIED, CANCELLED, ERROR, ResponseError) -from urllib import urlencode -from urlparse import parse_qs +from six.moves.urllib.parse import urlencode, parse_qs + from gettext import gettext as _ import logging import warnings -from systempayv2 import isonow +from .systempayv2 import isonow __all__ = ['Payment'] @@ -95,12 +95,12 @@ class Payment(PaymentCommon): refdet = str(refdet) if 6 > len(refdet) > 30: raise ValueError('len(REFDET) < 6 or > 30') - except Exception, e: + except Exception as e: raise ValueError('REFDET format invalide, %r' % refdet, e) if objet is not None: try: objet = str(objet) - except Exception, e: + except Exception as e: raise ValueError('OBJET must be a string', e) if not objet.replace(' ','').isalnum(): raise ValueError('OBJECT must only contains ' @@ -113,7 +113,7 @@ class Payment(PaymentCommon): raise ValueError('no @ in MEL') if not (6 <= len(mel) <= 80): raise ValueError('len(MEL) is invalid, must be between 6 and 80') - except Exception, e: + except Exception as e: raise ValueError('MEL is not a valid email, %r' % mel, e) saisie = saisie or self.saisie @@ -146,7 +146,7 @@ class Payment(PaymentCommon): fields = parse_qs(query_string, True) if not set(fields) >= set(['refdet', 'resultrans']): raise ResponseError() - for key, value in fields.iteritems(): + for key, value in fields.items(): fields[key] = value[0] refdet = fields.get('refdet') if refdet is None: @@ -183,11 +183,11 @@ class Payment(PaymentCommon): if __name__ == '__main__': p = Payment({'numcli': '12345'}) - print p.request(amount=Decimal('123.12'), + print(p.request(amount=Decimal('123.12'), exer=9999, refdet=999900000000999999, objet='tout a fait', email='info@entrouvert.com', urlcl='http://example.com/tipi/test', - saisie='T') - print p.response('objet=tout+a+fait&montant=12312&saisie=T&mel=info%40entrouvert.com&numcli=12345&exer=9999&refdet=999900000000999999&resultrans=P') + saisie='T')) + print(p.response('objet=tout+a+fait&montant=12312&saisie=T&mel=info%40entrouvert.com&numcli=12345&exer=9999&refdet=999900000000999999&resultrans=P')) diff --git a/setup.py b/setup.py index dd9543c..5bcca4c 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ class TestCommand(distutils.core.Command): class eo_sdist(sdist): def run(self): - print "creating VERSION file" + print("creating VERSION file") if os.path.exists('VERSION'): os.remove('VERSION') version = get_version() @@ -54,7 +54,7 @@ class eo_sdist(sdist): version_file.write(version) version_file.close() sdist.run(self) - print "removing VERSION file" + print("removing VERSION file") if os.path.exists('VERSION'): os.remove('VERSION') @@ -72,7 +72,7 @@ def get_version(): stderr=subprocess.PIPE) result = p.communicate()[0] if p.returncode == 0: - version = result.split()[0][1:] + version = str(result.split()[0][1:]) version = version.replace('-', '.') return version return '0.0.0' @@ -83,7 +83,7 @@ setuptools.setup( license='GPLv3 or later', description='Common API to use all French online payment credit card ' 'processing services', - long_description=file( + long_description=open( os.path.join( os.path.dirname(__file__), 'README.txt')).read(), @@ -96,6 +96,7 @@ setuptools.setup( install_requires=[ 'pycrypto >= 2.5', 'requests', + 'six', ], cmdclass={ 'sdist': eo_sdist, diff --git a/tests/test_ogone.py b/tests/test_ogone.py index 025dcdb..b667445 100644 --- a/tests/test_ogone.py +++ b/tests/test_ogone.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from unittest import TestCase -import urllib +from six.moves.urllib import parse as urllib import eopayment import eopayment.ogone as ogone diff --git a/tests/test_paybox.py b/tests/test_paybox.py index 0a8de60..bedcb69 100644 --- a/tests/test_paybox.py +++ b/tests/test_paybox.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- +import codecs from unittest import TestCase from decimal import Decimal import base64 -import urllib +from six.moves.urllib import parse as urllib import eopayment.paybox as paybox import eopayment @@ -19,7 +20,8 @@ BACKEND_PARAMS = { class PayboxTests(TestCase): def test_sign(self): - key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'.decode('hex') + key = b'0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + key = codecs.decode(key, 'hex') d = dict(paybox.sign([ ['PBX_SITE', u'12345678'], ['PBX_RANG', u'32'], @@ -97,7 +99,7 @@ UX4D2A/QcMvkEcRVXFx5tQqcE9/JnMqE41TF/ebn7jC/MBxxtPFkUN7+EZoeMN7x OWzAMDm/xsCWRvvel4GGixgm3aQRUPyTrlm4Ksy32Ya0rNnEDMAvB3dxOn7cp8GR ZdzrudBlevZXpr6iYwIDAQAB -----END PUBLIC KEY-----''' - data = 'coin\n' + data = b'coin\n' sig64 = '''VCt3sgT0ecacmDEWWNVXJ+jGmIPBMApK42tBJV0FlDjpllOGPy8MsAmLW4/QjTtx z0Dkz0NjxvU+5WzQZh9Uuxr/egRCwV4NMRWqu0zaVVioeBvl4/5CWm4f4/1L9+0m FBFKOZhgBJnkC+l6+XhT4aYWKaQ4ocmOMV92yjeXTE4=''' diff --git a/tests/test_spplus.py b/tests/test_spplus.py index 34f48a1..a1a2058 100644 --- a/tests/test_spplus.py +++ b/tests/test_spplus.py @@ -2,8 +2,8 @@ from unittest import TestCase import eopayment.spplus as spplus class SPPlustTest(TestCase): - ntkey = '58 6d fc 9c 34 91 9b 86 3f ' \ - 'fd 64 63 c9 13 4a 26 ba 29 74 1e c7 e9 80 79' + ntkey = b'58 6d fc 9c 34 91 9b 86 3f ' \ + b'fd 64 63 c9 13 4a 26 ba 29 74 1e c7 e9 80 79' tests = [('x=coin', 'c04f8266d6ae3ce37551cce996c751be4a95d10a'), ('x=coin&y=toto', 'ef008e02f8dbf5e70e83da416b0b3a345db203de'), diff --git a/tests/test_systempayv2.py b/tests/test_systempayv2.py index 25ad104..c558bbf 100644 --- a/tests/test_systempayv2.py +++ b/tests/test_systempayv2.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -import urlparse +from six.moves.urllib import parse as urlparse from eopayment.systempayv2 import Payment, VADS_CUST_FIRST_NAME, \ VADS_CUST_LAST_NAME, PAID diff --git a/tox.ini b/tox.ini index 72cd3f7..61acc97 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,10 @@ # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. +[tox] +envlist = py2,py3 +toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/eopayment/ + [testenv] # django.contrib.auth is not tested it does not work with our templates commands =