d76419c30cb04c3ebd66d59d70a9ebd8cb926545
[odoo/odoo.git] / addons / payment_acquirer_adyen / models / adyen.py
1 # -*- coding: utf-'8' "-*-"
2
3 import base64
4 try:
5     import simplejson as json
6 except ImportError:
7     import json
8 from hashlib import sha1
9 import hmac
10 import logging
11 import urlparse
12
13 from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError
14 from openerp.addons.payment_acquirer_adyen.controllers.main import AdyenController
15 from openerp.osv import osv, fields
16 from openerp.tools import float_round
17
18 _logger = logging.getLogger(__name__)
19
20
21 class AcquirerAdyen(osv.Model):
22     _inherit = 'payment.acquirer'
23
24     def _get_adyen_urls(self, cr, uid, env, context=None):
25         """ Adyen URLs
26
27          - yhpp: hosted payment page: pay.shtml for single, select.shtml for multiple
28         """
29         if env == 'prod':
30             return {
31                 'adyen_form_url': 'https://prod.adyen.com/hpp/pay.shtml',
32             }
33         else:
34             return {
35                 'adyen_form_url': 'https://test.adyen.com/hpp/pay.shtml',
36             }
37
38     _columns = {
39         'adyen_merchant_account': fields.char('Merchant Account', required_if_provider='adyen'),
40         'adyen_skin_code': fields.char('Skin Code', required_if_provider='adyen'),
41         'adyen_skin_hmac_key': fields.char('Skin HMAC Key', required_if_provider='adyen'),
42     }
43
44     def _adyen_generate_merchant_sig(self, acquirer, inout, values):
45         """ Generate the shasign for incoming or outgoing communications.
46
47         :param browse acquirer: the payment.acquirer browse record. It should
48                                 have a shakey in shaky out
49         :param string inout: 'in' (openerp contacting ogone) or 'out' (adyen
50                              contacting openerp). In this last case only some
51                              fields should be contained (see e-Commerce basic)
52         :param dict values: transaction values
53
54         :return string: shasign
55         """
56         assert inout in ('in', 'out')
57         assert acquirer.name == 'adyen'
58
59         if inout == 'in':
60             keys = "paymentAmount currencyCode shipBeforeDate merchantReference skinCode merchantAccount sessionValidity shopperEmail shopperReference recurringContract allowedMethods blockedMethods shopperStatement merchantReturnData billingAddressType deliveryAddressType offset".split()
61         else:
62             keys = "authResult pspReference merchantReference skinCode paymentMethod shopperLocale merchantReturnData".split()
63
64         def get_value(key):
65             if values.get(key):
66                 return values[key]
67             return ''
68
69         sign = ''.join('%s' % get_value(k) for k in keys).encode('ascii')
70         key = acquirer.adyen_skin_hmac_key.encode('ascii')
71         return base64.b64encode(hmac.new(key, sign, sha1).digest())
72
73     def adyen_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None):
74         base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
75         acquirer = self.browse(cr, uid, id, context=context)
76         # tmp
77         import datetime
78         from dateutil import relativedelta
79         tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1)
80
81         adyen_tx_values = dict(tx_values)
82         adyen_tx_values.update({
83             'merchantReference': tx_values['reference'],
84             'paymentAmount': '%d' % int(float_round(tx_values['amount'], 2) * 100),
85             'currencyCode': tx_values['currency'] and tx_values['currency'].name or '',
86             'shipBeforeDate': tmp_date,
87             'skinCode': acquirer.adyen_skin_code,
88             'merchantAccount': acquirer.adyen_merchant_account,
89             'shopperLocale': partner_values['lang'],
90             'sessionValidity': tmp_date,
91             'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url),
92         })
93         if adyen_tx_values.get('return_url'):
94             adyen_tx_values['merchantReturnData'] = json.dumps({'return_url': '%s' % adyen_tx_values.pop('return_url')})
95         adyen_tx_values['merchantSig'] = self._adyen_generate_merchant_sig(acquirer, 'in', adyen_tx_values)
96         return partner_values, adyen_tx_values
97
98     def adyen_get_form_action_url(self, cr, uid, id, context=None):
99         acquirer = self.browse(cr, uid, id, context=context)
100         return self._get_adyen_urls(cr, uid, acquirer.env, context=context)['adyen_form_url']
101
102
103 class TxAdyen(osv.Model):
104     _inherit = 'payment.transaction'
105
106     _columns = {
107         'adyen_psp_reference': fields.char('Adyen PSP Reference'),
108     }
109
110     # --------------------------------------------------
111     # FORM RELATED METHODS
112     # --------------------------------------------------
113
114     def _adyen_form_get_tx_from_data(self, cr, uid, data, context=None):
115         reference, pspReference = data.get('merchantReference'), data.get('pspReference')
116         if not reference or not pspReference:
117             error_msg = 'Adyen: received data with missing reference (%s) or missing pspReference (%s)' % (reference, pspReference)
118             _logger.error(error_msg)
119             raise ValidationError(error_msg)
120
121         # find tx -> @TDENOTE use pspReference ?
122         tx_ids = self.pool['payment.transaction'].search(cr, uid, [('reference', '=', reference)], context=context)
123         if not tx_ids or len(tx_ids) > 1:
124             error_msg = 'Adyen: received data for reference %s' % (reference)
125             if not tx_ids:
126                 error_msg += '; no order found'
127             else:
128                 error_msg += '; multiple order found'
129             _logger.error(error_msg)
130             raise ValidationError(error_msg)
131         tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
132
133         # verify shasign
134         shasign_check = self.pool['payment.acquirer']._adyen_generate_merchant_sig(tx.acquirer_id, 'out', data)
135         if shasign_check != data.get('merchantSig'):
136             error_msg = 'Adyen: invalid merchantSig, received %s, computed %s' % (data.get('merchantSig'), shasign_check)
137             _logger.warning(error_msg)
138             # raise ValidationError(error_msg)
139
140         return tx
141
142     def _adyen_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
143         # TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details
144         invalid_parameters = []
145         if data.get('skinCode') != tx.acquirer_id.adyen_skin_code:
146             invalid_parameters.append(('skinCode', data.get('skinCode'), tx.acquirer_id.adyen_skin_code))
147         if not data.get('authResult'):
148             invalid_parameters.append(('authResult', data.get('authResult'), 'something'))
149         return invalid_parameters
150
151     def _adyen_form_validate(self, cr, uid, tx, data, context=None):
152         status = data.get('authResult', 'PENDING')
153         if status == 'AUTHORISED':
154             tx.write({
155                 'state': 'done',
156                 'adyen_psp_reference': data.get('pspReference'),
157                 # 'date_validate': data.get('payment_date', fields.datetime.now()),
158                 # 'paypal_txn_type': data.get('express_checkout')
159             })
160             return True
161         elif status == 'PENDING':
162             tx.write({
163                 'state': 'pending',
164                 'adyen_psp_reference': data.get('pspReference'),
165             })
166             return True
167         else:
168             error = 'Paypal: feedback error'
169             _logger.info(error)
170             tx.write({
171                 'state': 'error',
172                 'state_message': error
173             })
174             return False