diff --git a/addons/payment_sips/__init__.py b/addons/payment_sips/__init__.py new file mode 100644 index 00000000000..396c76fe87a --- /dev/null +++ b/addons/payment_sips/__init__.py @@ -0,0 +1,2 @@ +import models +import controllers diff --git a/addons/payment_sips/__openerp__.py b/addons/payment_sips/__openerp__.py new file mode 100644 index 00000000000..89c36a757e9 --- /dev/null +++ b/addons/payment_sips/__openerp__.py @@ -0,0 +1,37 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Copyright Eezee-It +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +{ + 'name': 'Worldline SIPS Payment Acquiring for online payments', + 'version': '1.0', + 'author': 'Eezee-It', + 'category': 'Hidden', + 'description': """ +Worldline SIPS Payment Acquirer for online payments + +Works with Worldline keys version 2.0, contains implementation of +payments acquirer using Worldline SIPS.""", + 'depends': ['payment'], + 'data': [ + 'views/sips.xml', + 'views/payment_acquirer.xml', + 'data/sips.xml', + ], + 'installable': True, +} diff --git a/addons/payment_sips/controllers/__init__.py b/addons/payment_sips/controllers/__init__.py new file mode 100644 index 00000000000..bbd183e955b --- /dev/null +++ b/addons/payment_sips/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import main diff --git a/addons/payment_sips/controllers/main.py b/addons/payment_sips/controllers/main.py new file mode 100644 index 00000000000..47c9c8d0f0e --- /dev/null +++ b/addons/payment_sips/controllers/main.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +try: + import simplejson as json +except ImportError: + import json +import logging +import werkzeug + +from openerp import http +from openerp.http import request + +_logger = logging.getLogger(__name__) + + +class SipsController(http.Controller): + _notify_url = '/payment/sips/ipn/' + _return_url = '/payment/sips/dpn/' + + def _get_return_url(self, **post): + """ Extract the return URL from the data coming from sips. """ + return_url = post.pop('return_url', '') + if not return_url: + tx_obj = request.registry['payment.transaction'] + data = tx_obj._sips_data_to_object(post.get('Data')) + custom = json.loads(data.pop('returnContext', False) or '{}') + return_url = custom.get('return_url', '/') + return return_url + + def sips_validate_data(self, **post): + res = False + env = request.env + tx_obj = env['payment.transaction'] + acquirer_obj = env['payment.acquirer'] + + sips = acquirer_obj.search([('provider', '=', 'sips')], limit=1) + + security = sips._sips_generate_shasign(post) + if security == post['Seal']: + _logger.debug('Sips: validated data') + res = tx_obj.sudo().form_feedback(post, 'sips') + else: + _logger.warning('Sips: data are corrupted') + return res + + @http.route([ + '/payment/sips/ipn/'], + type='http', auth='none', methods=['POST']) + def sips_ipn(self, **post): + """ Sips IPN. """ + self.sips_validate_data(**post) + return '' + + @http.route([ + '/payment/sips/dpn'], type='http', auth="none", methods=['POST']) + def sips_dpn(self, **post): + """ Sips DPN """ + return_url = self._get_return_url(**post) + self.sips_validate_data(**post) + return werkzeug.utils.redirect(return_url) diff --git a/addons/payment_sips/data/sips.xml b/addons/payment_sips/data/sips.xml new file mode 100644 index 00000000000..69a5819d41e --- /dev/null +++ b/addons/payment_sips/data/sips.xml @@ -0,0 +1,28 @@ + + + + + + Sips + sips + + + test + You will be redirected to the Sips website after clicking on payment button.

]]>
+
+ + + Sips Payment Transaction + sips.payment.transaction + + + + Sips Payment Transaction + sips.payment.transaction + + + + +
+
diff --git a/addons/payment_sips/models/__init__.py b/addons/payment_sips/models/__init__.py new file mode 100644 index 00000000000..365abd07d65 --- /dev/null +++ b/addons/payment_sips/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import sips diff --git a/addons/payment_sips/models/sips.py b/addons/payment_sips/models/sips.py new file mode 100644 index 00000000000..980873178a9 --- /dev/null +++ b/addons/payment_sips/models/sips.py @@ -0,0 +1,232 @@ +# -*- coding: utf-'8' "-*-" + +try: + import simplejson as json +except ImportError: + import json +import logging +from hashlib import sha256 +import urlparse +import unicodedata + +from openerp import models, fields, api +from openerp.tools.float_utils import float_compare +from openerp.tools.translate import _ +from openerp.addons.payment.models.payment_acquirer import ValidationError +from openerp.addons.payment_sips.controllers.main import SipsController + +_logger = logging.getLogger(__name__) + + +CURRENCY_CODES = { + 'EUR': '978', + 'USD': '840', + 'CHF': '756', + 'GBP': '826', + 'CAD': '124', + 'JPY': '392', + 'MXN': '484', + 'TRY': '949', + 'AUD': '036', + 'NZD': '554', + 'NOK': '578', + 'BRL': '986', + 'ARS': '032', + 'KHR': '116', + 'TWD': '901', +} + + +class AcquirerSips(models.Model): + _inherit = 'payment.acquirer' + # Fields + sips_merchant_id = fields.Char('SIPS API User Password', + required_if_provider='sips') + sips_secret = fields.Char('SIPS Secret', size=64, required_if_provider='sips') + + # Methods + def _get_sips_urls(self, environment): + """ Worldline SIPS URLS """ + url = { + 'prod': 'https://payment-webinit.sips-atos.com/paymentInit', + 'test': 'https://payment-webinit.simu.sips-atos.com/paymentInit', } + + return {'sips_form_url': url.get(environment, url['test']), } + + @api.model + def _get_providers(self): + providers = super(AcquirerSips, self)._get_providers() + providers.append(['sips', 'Sips']) + return providers + + def _sips_generate_shasign(self, values): + """ Generate the shasign for incoming or outgoing communications. + :param dict values: transaction values + :return string: shasign + """ + if self.provider != 'sips': + raise ValidationError(_('Incorrect payment acquirer provider')) + data = values['Data'] + + # Test key provided by Worldine + key = u'002001000000001_KEY1' + + if self.environment == 'prod': + key = getattr(self, 'sips_secret') + + shasign = sha256(data + key) + return shasign.hexdigest() + + @api.multi + def sips_form_generate_values(self, partner_values, tx_values): + self.ensure_one() + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + currency = self.env['res.currency'].sudo().browse(tx_values['currency_id']) + currency_code = CURRENCY_CODES.get(currency.name, False) + if not currency_code: + raise ValidationError(_('Currency not supported by Wordline')) + amount = int(tx_values.get('amount') * 100) + if self.environment == 'prod': + # For production environment, key version 2 is required + merchant_id = getattr(self, 'sips_merchant_id') + key_version = '2' + else: + # Test key provided by Atos Wordline works only with version 1 + merchant_id = '002001000000001' + key_version = '1' + + sips_tx_values = dict(tx_values) + sips_tx_values.update({ + 'Data': u'amount=%s|' % amount + + u'currencyCode=%s|' % currency_code + + u'merchantId=%s|' % merchant_id + + u'normalReturnUrl=%s|' % urlparse.urljoin(base_url, SipsController._return_url) + + u'automaticResponseUrl=%s|' % urlparse.urljoin(base_url, SipsController._return_url) + + u'transactionReference=%s|' % tx_values['reference'] + + u'statementReference=%s|' % tx_values['reference'] + + u'keyVersion=%s' % key_version, + 'InterfaceVersion': 'HP_2.3', + }) + + return_context = {} + if sips_tx_values.get('return_url'): + return_context[u'return_url'] = u'%s' % sips_tx_values.pop('return_url') + return_context[u'reference'] = u'%s' % sips_tx_values['reference'] + sips_tx_values['Data'] += u'|returnContext=%s' % (json.dumps(return_context)) + + shasign = self._sips_generate_shasign(sips_tx_values) + sips_tx_values['Seal'] = shasign + return partner_values, sips_tx_values + + @api.multi + def sips_get_form_action_url(self): + self.ensure_one() + return self._get_sips_urls(self.environment)['sips_form_url'] + + +class TxSips(models.Model): + _inherit = 'payment.transaction' + + # sips status + _sips_valid_tx_status = ['00'] + _sips_wait_tx_status = ['90', '99'] + _sips_refused_tx_status = ['05', '14', '34', '54', '75', '97'] + _sips_error_tx_status = ['03', '12', '24', '25', '30', '40', '51', '63', '94'] + _sips_pending_tx_status = ['60'] + _sips_cancel_tx_status = ['17'] + + # -------------------------------------------------- + # FORM RELATED METHODS + # -------------------------------------------------- + def _sips_data_to_object(self, data): + res = {} + for element in data.split('|'): + element_split = element.split('=') + res[element_split[0]] = element_split[1] + return res + + @api.model + def _sips_form_get_tx_from_data(self, data): + """ Given a data dict coming from sips, verify it and find the related + transaction record. """ + + data = self._sips_data_to_object(data.get('Data')) + reference = data.get('transactionReference') + + if not reference: + custom = json.loads(data.pop('returnContext', False) or '{}') + reference = custom.get('reference') + + payment_tx = self.search([('reference', '=', reference)]) + if not payment_tx or len(payment_tx) > 1: + error_msg = _('Sips: received data for reference %s') % reference + if not payment_tx: + error_msg += _('; no order found') + else: + error_msg += _('; multiple order found') + _logger.error(error_msg) + raise ValidationError(error_msg) + return payment_tx + + @api.model + def _sips_form_get_invalid_parameters(self, tx, data): + invalid_parameters = [] + + data = self._sips_data_to_object(data.get('Data')) + + # TODO: txn_id: should be false at draft, set afterwards, and verified with txn details + if tx.acquirer_reference and data.get('transactionReference') != tx.acquirer_reference: + invalid_parameters.append(('transactionReference', data.get('transactionReference'), tx.acquirer_reference)) + # check what is bought + if float_compare(float(data.get('amount', '0.0')) / 100, tx.amount, 2) != 0: + invalid_parameters.append(('amount', data.get('amount'), '%.2f' % tx.amount)) + if tx.partner_reference and data.get('customerId') != tx.partner_reference: + invalid_parameters.append(('customerId', data.get('customerId'), tx.partner_reference)) + + return invalid_parameters + + @api.model + def _sips_form_validate(self, tx, data): + data = self._sips_data_to_object(data.get('Data')) + status = data.get('responseCode') + data = { + 'acquirer_reference': data.get('transactionReference'), + 'partner_reference': data.get('customerId'), + 'date_validate': data.get('transactionDateTime', + fields.Datetime.now()) + } + res = False + if status in self._sips_valid_tx_status: + msg = 'Payment for tx ref: %s, got response [%s], set as done.' % \ + (tx.reference, status) + _logger.info(msg) + data.update(state='done', state_message=msg) + res = True + elif status in self._sips_error_tx_status: + msg = 'Payment for tx ref: %s, got response [%s], set as ' \ + 'error.' % (tx.reference, status) + data.update(state='error', state_message=msg) + elif status in self._sips_wait_tx_status: + msg = 'Received wait status for payment ref: %s, got response ' \ + '[%s], set as error.' % (tx.reference, status) + data.update(state='error', state_message=msg) + elif status in self._sips_refused_tx_status: + msg = 'Received refused status for payment ref: %s, got response' \ + ' [%s], set as error.' % (tx.reference, status) + data.update(state='error', state_message=msg) + elif status in self._sips_pending_tx_status: + msg = 'Payment ref: %s, got response [%s] set as pending.' \ + % (tx.reference, status) + data.update(state='pending', state_message=msg) + elif status in self._sips_cancel_tx_status: + msg = 'Received notification for payment ref: %s, got response ' \ + '[%s], set as cancel.' % (tx.reference, status) + data.update(state='cancel', state_message=msg) + else: + msg = 'Received unrecognized status for payment ref: %s, got ' \ + 'response [%s], set as error.' % (tx.reference, status) + data.update(state='error', state_message=msg) + + _logger.info(msg) + tx.write(data) + return res diff --git a/addons/payment_sips/static/description/icon.png b/addons/payment_sips/static/description/icon.png new file mode 100644 index 00000000000..f04253c3bec Binary files /dev/null and b/addons/payment_sips/static/description/icon.png differ diff --git a/addons/payment_sips/static/src/img/sips_icon.png b/addons/payment_sips/static/src/img/sips_icon.png new file mode 100644 index 00000000000..f04253c3bec Binary files /dev/null and b/addons/payment_sips/static/src/img/sips_icon.png differ diff --git a/addons/payment_sips/static/src/img/sips_logo.png b/addons/payment_sips/static/src/img/sips_logo.png new file mode 100644 index 00000000000..f04253c3bec Binary files /dev/null and b/addons/payment_sips/static/src/img/sips_logo.png differ diff --git a/addons/payment_sips/views/payment_acquirer.xml b/addons/payment_sips/views/payment_acquirer.xml new file mode 100644 index 00000000000..3e93859027b --- /dev/null +++ b/addons/payment_sips/views/payment_acquirer.xml @@ -0,0 +1,20 @@ + + + + + + acquirer.form.sips + payment.acquirer + + + + + + + + + + + + + diff --git a/addons/payment_sips/views/sips.xml b/addons/payment_sips/views/sips.xml new file mode 100644 index 00000000000..02553d2c754 --- /dev/null +++ b/addons/payment_sips/views/sips.xml @@ -0,0 +1,20 @@ + + + + + + + +