Skip to content

Commit

Permalink
Merge pull request #254 from incaser/redsys-sha256
Browse files Browse the repository at this point in the history
[IMP] payment_redsys: Migracion a HMAC SHA-256
  • Loading branch information
pedrobaeza committed Nov 23, 2015
2 parents c3ef408 + 5ad476a commit 1772454
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 104 deletions.
1 change: 1 addition & 0 deletions payment_redsys/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
'views/redsys.xml',
'views/payment_acquirer.xml'
],
'license': 'AGPL-3',
'installable': True,
}
2 changes: 1 addition & 1 deletion payment_redsys/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def redsys_return(self, **post):
return werkzeug.utils.redirect(return_url)


class website_sale(website_sale):
class WebsiteSale(website_sale):
@http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json',
auth="public", website=True)
def payment_transaction(self, acquirer_id):
Expand Down
163 changes: 88 additions & 75 deletions payment_redsys/models/redsys.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
from hashlib import sha1
from Crypto.Cipher import DES3
import hashlib
import hmac
import base64
import logging
import urlparse
import json

from openerp import models, fields, api, _
from openerp.addons.payment.models.payment_acquirer import ValidationError
Expand Down Expand Up @@ -59,58 +62,20 @@ def _get_providers(self):
('D', 'Domiciliacion'),
], 'Payment Method',
default='T')
redsys_signature_version = fields.Selection(
[('HMAC_SHA256_V1', 'HMAC SHA256 V1')], default='HMAC_SHA256_V1')
redsys_url_ok = fields.Char('URL OK')
redsys_url_ko = fields.Char('URL KO')

def _redsys_generate_digital_sign(self, acquirer, inout, values):
""" Generate the shasign for incoming or outgoing communications.
:param browse acquirer: the payment.acquirer browse record. It should
have a shakey in shaky out
:param string inout: 'in' (encoding) or 'out' (decoding).
:param dict values: transaction values
:return string: shasign
"""
assert acquirer.provider == 'redsys'

def get_value(key):
if values.get(key):
return values[key]
return ''

if inout == 'out':
keys = ['Ds_Amount',
'Ds_Order',
'Ds_MerchantCode',
'Ds_Currency',
'Ds_Response']
else:
keys = ['Ds_Merchant_Amount',
'Ds_Merchant_Order',
'Ds_Merchant_MerchantCode',
'Ds_Merchant_Currency',
'Ds_Merchant_TransactionType',
'Ds_Merchant_MerchantURL']
sign = ''.join('%s' % (get_value(k)) for k in keys)
# Add the pre-shared secret key at the end of the signature
sign = sign + acquirer.redsys_secret_key
if isinstance(sign, str):
sign = urlparse.parse_qsl(sign)
shasign = sha1(sign).hexdigest().upper()
return shasign

@api.model
def redsys_form_generate_values(self, id, partner_values, tx_values):
acquirer = self.browse(id)
redsys_tx_values = dict(tx_values)
redsys_tx_values.update({
def _prepare_merchant_parameters(self, acquirer, tx_values):
values = {
'Ds_Sermepa_Url': (
self._get_redsys_urls(acquirer.environment)[
'redsys_form_url']),
'Ds_Merchant_Amount': int(tx_values['amount'] * 100),
'Ds_Merchant_Amount': str(int(tx_values['amount'] * 100)),
'Ds_Merchant_Currency': acquirer.redsys_currency or '978',
'Ds_Merchant_Order': (
tx_values['reference'] and tx_values['reference'][:12] or
tx_values['reference'] and tx_values['reference'][-12:] or
False),
'Ds_Merchant_MerchantCode': (
acquirer.redsys_merchant_code and
Expand All @@ -124,7 +89,7 @@ def redsys_form_generate_values(self, id, partner_values, tx_values):
'Ds_Merchant_MerchantName': (
acquirer.redsys_merchant_name and
acquirer.redsys_merchant_name[:25]),
'Ds_Merchant_MerchantURL': (
'Ds_Merchant_MerchantUrl': (
acquirer.redsys_merchant_url and
acquirer.redsys_merchant_url[:250] or ''),
'Ds_Merchant_MerchantData': acquirer.redsys_merchant_data or '',
Expand All @@ -134,14 +99,51 @@ def redsys_form_generate_values(self, id, partner_values, tx_values):
acquirer.redsys_merchant_description[:125]),
'Ds_Merchant_ConsumerLanguage': (
acquirer.redsys_merchant_lang or '001'),
'Ds_Merchant_UrlOK': acquirer.redsys_url_ok or '',
'Ds_Merchant_UrlKO': acquirer.redsys_url_ko or '',
'Ds_Merchant_PayMethods': acquirer.redsys_pay_method or 'T',
})
'Ds_Merchant_UrlOk': acquirer.redsys_url_ok or '',
'Ds_Merchant_UrlKo': acquirer.redsys_url_ko or '',
'Ds_Merchant_Paymethods': acquirer.redsys_pay_method or 'T',
}
return self._url_encode64(json.dumps(values))

def _url_encode64(self, data):
data = unicode(base64.encodestring(data), 'utf-8')
return ''.join(data.splitlines())

def _url_decode64(self, data):
return json.loads(base64.b64decode(data))

redsys_tx_values['Ds_Merchant_MerchantSignature'] = (
self._redsys_generate_digital_sign(
acquirer, 'in', redsys_tx_values))
def sign_parameters(self, secret_key, params64):
params_dic = self._url_decode64(params64)
if 'Ds_Merchant_Order' in params_dic:
order = str(params_dic['Ds_Merchant_Order'])
else:
order = str(params_dic.get('Ds_Order', 'Not found'))
cipher = DES3.new(
key=base64.b64decode(secret_key),
mode=DES3.MODE_CBC,
IV=b'\0\0\0\0\0\0\0\0')
diff_block = len(order) % 8
zeros = diff_block and (b'\0' * (8 - diff_block)) or ''
key = cipher.encrypt(order + zeros.encode('UTF-8'))
dig = hmac.new(
key=key,
msg=params64,
digestmod=hashlib.sha256).digest()
return self._url_encode64(dig)

@api.model
def redsys_form_generate_values(self, id, partner_values, tx_values):
acquirer = self.browse(id)
redsys_tx_values = dict(tx_values)

merchant_parameters = self._prepare_merchant_parameters(
acquirer, tx_values)
redsys_tx_values.update({
'Ds_SignatureVersion': str(acquirer.redsys_signature_version),
'Ds_MerchantParameters': merchant_parameters,
'Ds_Signature': self.sign_parameters(
acquirer.redsys_secret_key, merchant_parameters),
})
return partner_values, redsys_tx_values

@api.multi
Expand All @@ -162,6 +164,10 @@ class TxRedsys(models.Model):

redsys_txnid = fields.Char('Transaction ID')

def merchant_params_json2dict(self, data):
parameters = data.get('Ds_MerchantParameters', '').decode('base64')
return json.loads(parameters)

# --------------------------------------------------
# FORM RELATED METHODS
# --------------------------------------------------
Expand All @@ -170,9 +176,13 @@ class TxRedsys(models.Model):
def _redsys_form_get_tx_from_data(self, data):
""" Given a data dict coming from redsys, verify it and
find the related transaction record. """
reference = data.get('Ds_Order', '')
pay_id = data.get('Ds_AuthorisationCode')
shasign = data.get('Ds_Signature')
parameters = data.get('Ds_MerchantParameters', '')
parameters_dic = json.loads(base64.b64decode(parameters))
reference = parameters_dic.get('Ds_Order', '')
pay_id = parameters_dic.get('Ds_AuthorisationCode')
shasign = data.get(
'Ds_Signature', '').replace('_', '/').replace('-', '+')

if not reference or not pay_id or not shasign:
error_msg = 'Redsys: received data with missing reference' \
' (%s) or pay_id (%s) or shashign (%s)' % (reference,
Expand All @@ -191,10 +201,9 @@ def _redsys_form_get_tx_from_data(self, data):
raise ValidationError(error_msg)

# verify shasign
acquirer = self.env['payment.acquirer']
shasign_check = acquirer._redsys_generate_digital_sign(
tx.acquirer_id, 'out', data)
if shasign_check.upper() != shasign.upper():
shasign_check = tx.acquirer_id.sign_parameters(
tx.acquirer_id.redsys_secret_key, parameters)
if shasign_check != shasign:
error_msg = 'Redsys: invalid shasign, received %s, computed %s,' \
' for data %s' % (shasign, shasign_check, data)
_logger.error(error_msg)
Expand All @@ -204,27 +213,30 @@ def _redsys_form_get_tx_from_data(self, data):
@api.model
def _redsys_form_get_invalid_parameters(self, tx, data):
invalid_parameters = []

parameters_dic = self.merchant_params_json2dict(data)
if (tx.acquirer_reference and
data.get('Ds_Order')) != tx.acquirer_reference:
parameters_dic.get('Ds_Order')) != tx.acquirer_reference:
invalid_parameters.append(
('Transaction Id', data.get('Ds_Order'),
('Transaction Id', parameters_dic.get('Ds_Order'),
tx.acquirer_reference))
# check what is buyed
if (float_compare(float(data.get('Ds_Amount', '0.0')) / 100,
if (float_compare(float(parameters_dic.get('Ds_Amount', '0.0')) / 100,
tx.amount, 2) != 0):
invalid_parameters.append(('Amount', data.get('Ds_Amount'),
'%.2f' % tx.amount))
invalid_parameters.append(
('Amount', parameters_dic.get('Ds_Amount'),
'%.2f' % tx.amount))
return invalid_parameters

@api.model
def _redsys_form_validate(self, tx, data):
status_code = int(data.get('Ds_Response', '29999'))
parameters_dic = self.merchant_params_json2dict(data)
status_code = int(parameters_dic.get('Ds_Response', '29999'))
if (status_code >= 0) and (status_code <= 99):
tx.write({
'state': 'done',
'redsys_txnid': data.get('Ds_AuthorisationCode'),
'state_message': _('Ok: %s') % data.get('Ds_Response'),
'redsys_txnid': parameters_dic.get('Ds_AuthorisationCode'),
'state_message': _('Ok: %s') % parameters_dic.get(
'Ds_Response'),
})
email_act = tx.sale_order_id.action_quotation_send()
# send the email
Expand All @@ -235,25 +247,26 @@ def _redsys_form_validate(self, tx, data):
# 'Payment error: code: %s.'
tx.write({
'state': 'pending',
'redsys_txnid': data.get('Ds_AuthorisationCode'),
'state_message': _('Error: %s') % data.get('Ds_Response'),
'redsys_txnid': parameters_dic.get('Ds_AuthorisationCode'),
'state_message': _('Error: %s') % parameters_dic.get(
'Ds_Response'),
})
return True
if (status_code == 912) and (status_code == 9912):
# 'Payment error: bank unavailable.'
tx.write({
'state': 'cancel',
'redsys_txnid': data.get('Ds_AuthorisationCode'),
'redsys_txnid': parameters_dic.get('Ds_AuthorisationCode'),
'state_message': (_('Bank Error: %s')
% data.get('Ds_Response')),
% parameters_dic.get('Ds_Response')),
})
return True
else:
error = 'Redsys: feedback error'
_logger.info(error)
tx.write({
'state': 'error',
'redsys_txnid': data.get('Ds_AuthorisationCode'),
'redsys_txnid': parameters_dic.get('Ds_AuthorisationCode'),
'state_message': error,
})
return False
Expand Down
1 change: 1 addition & 0 deletions payment_redsys/views/payment_acquirer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<field name="redsys_pay_method" attrs="{'required': [('provider', '=', 'redsys')]}"/>
<field name="redsys_merchant_data" attrs="{'required': [('provider', '=', 'redsys')]}"/>
<field name="redsys_merchant_description" attrs="{'required': [('provider', '=', 'redsys')]}"/>
<field name="redsys_signature_version" attrs="{'required': [('provider', '=', 'redsys')]}"/>
<field name="redsys_url_ok" attrs="{'required': [('provider', '=', 'redsys')]}"/>
<field name="redsys_url_ko" attrs="{'required': [('provider', '=', 'redsys')]}"/>
</group>
Expand Down
46 changes: 18 additions & 28 deletions payment_redsys/views/redsys.xml
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<data noupdate="1">

<template id="redsys_acquirer_button">
<form t-if="acquirer" t-att-action="tx_url" method="post" target="_self">
<input type="hidden" name="Ds_Merchant_Amount" t-att-value="tx_values['Ds_Merchant_Amount']"/>
<input type="hidden" name="Ds_Merchant_Currency" t-att-value="tx_values['Ds_Merchant_Currency']"/>
<input type="hidden" name="Ds_Merchant_Order" t-att-value="tx_values['Ds_Merchant_Order']"/>
<input type="hidden" name="Ds_Merchant_MerchantCode" t-att-value="tx_values['Ds_Merchant_MerchantCode']"/>
<input type="hidden" name="Ds_Merchant_Terminal" t-att-value="tx_values['Ds_Merchant_Terminal']"/>
<input type="hidden" name="Ds_Merchant_TransactionType" t-att-value="tx_values['Ds_Merchant_TransactionType']"/>
<input type="hidden" name="Ds_Merchant_Titular" t-att-value="tx_values['Ds_Merchant_Titular']"/>
<input type="hidden" name="Ds_Merchant_MerchantName" t-att-value="tx_values['Ds_Merchant_MerchantName']"/>
<input type="hidden" name="Ds_Merchant_MerchantURL" t-att-value="tx_values['Ds_Merchant_MerchantURL']"/>
<input type="hidden" name="Ds_Merchant_MerchantData" t-att-value="tx_values['Ds_Merchant_MerchantData']"/>
<input type="hidden" name="Ds_Merchant_ProductDescription" t-att-value="tx_values['Ds_Merchant_ProductDescription']"/>
<input type="hidden" name="Ds_Merchant_ConsumerLanguage " t-att-value="tx_values['Ds_Merchant_ConsumerLanguage']"/>
<input type="hidden" name="Ds_Merchant_UrlOK" t-att-value="tx_values['Ds_Merchant_UrlOK']"/>
<input type="hidden" name="Ds_Merchant_UrlKO" t-att-value="tx_values['Ds_Merchant_UrlKO']"/>
<input type="hidden" name="Ds_Merchant_PayMethods" t-att-value="tx_values['Ds_Merchant_PayMethods']"/>
<input type="hidden" name="Ds_Merchant_MerchantSignature" t-att-value="tx_values['Ds_Merchant_MerchantSignature']"/>
<!-- submit -->
<button type="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt" src="/payment_redsys/static/src/img/redsys_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
</button>
</form>
</template>
<template id="redsys_acquirer_button">
<form t-if="acquirer" t-att-action="tx_url" method="post" target="_self">
<input type="hidden" name="Ds_SignatureVersion"
t-att-value="tx_values['Ds_SignatureVersion']"/>
<input type="hidden" name="Ds_MerchantParameters"
t-att-value="tx_values['Ds_MerchantParameters']"/>
<input type="hidden" name="Ds_Signature"
t-att-value="tx_values['Ds_Signature']"/>
<button type="submit" width="100px"
t-att-class="submit_class">
<img t-if="not submit_txt"
src="/payment_redsys/static/src/img/redsys_icon.png"/>
<span t-if="submit_txt"><t t-esc="submit_txt"/> <span class="fa fa-long-arrow-right"/></span>
</button>
</form>
</template>

</data>
</data>
</openerp>

0 comments on commit 1772454

Please sign in to comment.