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 @@
+
+
+
+
+
+
+
+
+
+