[ADD] payment_sips

contains acquirer definition to support Atos Wordline online payments
Courtesy of Eezee-It

Closes #6684
This commit is contained in:
Aristobulo Meneses 2015-05-13 11:49:25 +02:00 committed by Martin Trigaux
parent 799fff6897
commit b2193e6734
12 changed files with 405 additions and 0 deletions

View File

@ -0,0 +1,2 @@
import models
import controllers

View File

@ -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 <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'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,
}

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import main

View File

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

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<record id="payment_acquirer_sips" model="payment.acquirer">
<field name="name">Sips</field>
<field name="provider">sips</field>
<field name="company_id" ref="base.main_company"/>
<field name="view_template_id" ref="payment_sips.sips_acquirer_button"/>
<field name="environment">test</field>
<field name="pre_msg"><![CDATA[
<p>You will be redirected to the Sips website after clicking on payment button.</p>]]></field>
</record>
<record id="sequence_payment_transaction_type_sips" model="ir.sequence.type">
<field name="name">Sips Payment Transaction</field>
<field name="code">sips.payment.transaction</field>
</record>
<record id="sequence_payment_transaction_sips" model="ir.sequence">
<field name="name">Sips Payment Transaction</field>
<field name="code">sips.payment.transaction</field>
<field name="prefix"></field>
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
</record>
</data>
</openerp>

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
import sips

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="acquirer_form_sips" model="ir.ui.view">
<field name="name">acquirer.form.sips</field>
<field name="model">payment.acquirer</field>
<field name="inherit_id" ref="payment.acquirer_form"/>
<field name="arch" type="xml">
<xpath expr='//group[@name="acquirer_display"]' position='after'>
<group attrs="{'invisible': [('provider', '!=', 'sips')]}">
<field name="sips_secret"/>
<field name="sips_merchant_id"/>
</group>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
<template id="sips_acquirer_button">
<form t-att-action="tx_url" method="post" target="_self">
<input type="hidden" name="Data" t-att-value="tx_values['Data']"/>
<input type="hidden" name="InterfaceVersion" t-att-value="tx_values['InterfaceVersion']"/>
<input type="hidden" name="Seal" t-att-value="tx_values['Seal']"/>
<!-- submit -->
<button type="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_sips/static/src/img/sips_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
</button>
</form>
</template>
</data>
</openerp>