diff --git a/addons/crm_partner_assign/res_partner_demo.xml b/addons/crm_partner_assign/res_partner_demo.xml index 776d7d6834c..a0711657810 100644 --- a/addons/crm_partner_assign/res_partner_demo.xml +++ b/addons/crm_partner_assign/res_partner_demo.xml @@ -1,21 +1,21 @@ - - Bronze - 1 - - - Silver - 2 + + Platinum + 4 Gold 3 - - Platinum - 4 + + Silver + 2 + + + Bronze + 1 diff --git a/addons/payment_acquirer/models/payment_acquirer.py b/addons/payment_acquirer/models/payment_acquirer.py index 101510501db..b88601e88f5 100644 --- a/addons/payment_acquirer/models/payment_acquirer.py +++ b/addons/payment_acquirer/models/payment_acquirer.py @@ -1,14 +1,15 @@ # -*- coding: utf-'8' "-*-" -from openerp.osv import osv, fields - import logging +from openerp.osv import osv, fields +from openerp.tools import float_round + _logger = logging.getLogger(__name__) def _partner_format_address(address1=False, address2=False): - return ' '.join((address1 or '', address2 or '')) + return ' '.join((address1 or '', address2 or '')).strip() def _partner_split_name(partner_name): @@ -22,8 +23,9 @@ class ValidationError(ValueError): class PaymentAcquirer(osv.Model): """ Acquirer Model. Each specific acquirer can extend the model by adding - its own fields. Using the required_if_provider='' attribute on fields - it is possible to have required fields that depend on a specific acquirer. + its own fields, using the acquirer_name as a prefix for the new fields. + Using the required_if_provider='' attribute on fields it is possible + to have required fields that depend on a specific acquirer. Each acquirer has a link to an ir.ui.view record that is a template of a button used to display the payment form. See examples in ``payment_acquirer_ogone`` @@ -36,7 +38,10 @@ class PaymentAcquirer(osv.Model): method that generates the values used to render the form button template. - ``_get_form_action_url(self, cr, uid, id, context=None):``: method that returns the url of the button form. It is used for example in - ecommerce application, if you want to repost some data to the acquirer. + ecommerce application, if you want to post some data to the acquirer. + - ``_compute_fees(self, cr, uid, id, amount, currency_id, country_id, + context=None)``: computed the fees of the acquirer, using generic fields + defined on the acquirer model (see fields definition). Each acquirer should also define controllers to handle communication between OpenERP and the acquirer. It generally consists in return urls given to the @@ -54,8 +59,9 @@ class PaymentAcquirer(osv.Model): 'env': fields.selection( [('test', 'Test'), ('prod', 'Production')], string='Environment'), - 'portal_published': fields.boolean('Visible in Portal', - help="Make this payment acquirer available (Customer invoices, etc.)"), + 'portal_published': fields.boolean( + 'Visible in Portal', + help="Make this payment acquirer available (Customer invoices, etc.)"), # Fees 'fees_active': fields.boolean('Compute fees'), 'fees_dom_fixed': fields.float('Fixed domestic fees'), @@ -89,27 +95,91 @@ class PaymentAcquirer(osv.Model): return getattr(self, '%s_get_form_action_url' % acquirer.name)(cr, uid, id, context=context) return False - def form_preprocess_values(self, cr, uid, id, reference, amount, currency_id, tx_id, partner_id, partner_values, tx_custom_values, context=None): - acquirer = self.browse(cr, uid, id, context=context) - tx_values = tx_custom_values + def form_preprocess_values(self, cr, uid, id, reference, amount, currency_id, tx_id, partner_id, partner_values, tx_values, context=None): + """ Pre process values before giving them to the acquirer-specific render + methods. Those methods will receive: - # compute country - if partner_id: - partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) - tx_values['country_id'] = partner.country_id.id - elif partner_values and partner_values.get('country_name'): - country_ids = self.pool['res.country'].search(cr, uid, [('name', '=', partner_values.get('country_name'))], context=context) - tx_values['country_id'] = country_ids and country_ids[0] or None + - partner_values: will contain name, lang, email, zip, address, city, + country_id (int or False), country (browse or False), phone, reference + - tx_values: will contain refernece, amount, currency_id (int or False), + currency (browse or False), partner (browse or False) + """ + acquirer = self.browse(cr, uid, id, context=context) + + if tx_id: + tx = self.browse(cr, uid, id, context=context) + tx_data = { + 'reference': tx.reference, + 'amount': tx.amount, + 'currency_id': tx.currency_id.id, + 'currency': tx.currency_id, + 'partner': tx.partner_id, + } + partner_data = { + 'name': tx.partner_name, + 'lang': tx.partner_lang, + 'email': tx.partner_email, + 'zip': tx.partner_zip, + 'address': tx.partner_address, + 'city': tx.partner_city, + 'country_id': tx.partner_country_id.id, + 'country': tx.partner_country_id, + 'phone': tx.partner_phone, + 'reference': tx.partner_reference, + } else: - tx_values['country_id'] = False + if partner_id: + partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) + partner_data = { + 'name': partner.name, + 'lang': partner.lang, + 'email': partner.email, + 'zip': partner.zip, + 'city': partner.city, + 'address': _partner_format_address(partner.street, partner.street2), + 'country_id': partner.country_id.id, + 'country': partner.country_id, + 'phone': partner.phone, + } + else: + partner, partner_data = False, {} + partner_data.update(partner_values) + + if currency_id: + currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context) + else: + currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id + tx_data = { + 'reference': reference, + 'amount': amount, + 'currency_id': currency.id, + 'currency': currency, + 'partner': partner, + } + + # update tx values + tx_data.update(tx_values) + + # update partner values + if not partner_data.get('address'): + partner_data['address'] = _partner_format_address(partner_data.get('street', ''), partner_data.get('street2', '')) + if not partner_data.get('country') and partner_data.get('country_id'): + partner_data['country'] = self.pool['res.country'].browse(cr, uid, partner_data.get('country_id'), context=context) + partner_data.update({ + 'first_name': _partner_split_name(partner_data['name'])[0], + 'last_name': _partner_split_name(partner_data['name'])[1], + }) # compute fees - if hasattr(self, '%s_compute_fees' % acquirer.name): - tx_values['fees'] = getattr(self, '%s_compute_fees' % acquirer.name)(cr, uid, id, amount, currency_id, tx_values.get('country_id'), context=None) + fees_method_name = '%s_compute_fees' % acquirer.name + if hasattr(self, fees_method_name): + fees = getattr(self, fees_method_name)( + cr, uid, id, tx_data['amount'], tx_data['currency_id'], partner_data['country_id'], context=None) + tx_data['fees'] = float_round(fees, 2) - return tx_values + return (partner_data, tx_data) - def render(self, cr, uid, id, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_custom_values=None, context=None): + def render(self, cr, uid, id, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, context=None): """ Renders the form template of the given acquirer as a qWeb template. All templates will receive: @@ -120,9 +190,9 @@ class PaymentAcquirer(osv.Model): - reference: reference of the transaction - partner: the current partner browse record, if any (not necessarily set) - partner_values: a dictionary of partner-related values - - tx_custom_values: a dictionary of transaction related values that depends - on the acquirer. Some specific keys should be managed - in each provider, depending on the features it offers: + - tx_values: a dictionary of transaction related values that depends on + the acquirer. Some specific keys should be managed in each + provider, depending on the features it offers: - 'feedback_url': feedback URL, controler that manage answer of the acquirer (without base url) -> FIXME @@ -147,38 +217,40 @@ class PaymentAcquirer(osv.Model): """ if context is None: context = {} - if tx_custom_values is None: - tx_custom_values = {} - partner = None - if partner_id: - partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) + if tx_values is None: + tx_values = {} + if partner_values is None: + partner_values = {} acquirer = self.browse(cr, uid, id, context=context) - currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context) # pre-process values - tx_values = self.form_preprocess_values(cr, uid, id, reference, amount, currency, tx_id, partner_id, partner_values, tx_custom_values, context=context) + amount = float_round(amount, 2) + partner_values, tx_values = self.form_preprocess_values( + cr, uid, id, reference, amount, currency_id, tx_id, partner_id, + partner_values, tx_values, context=context) # call _form_generate_values to update the tx dict with acqurier specific values cust_method_name = '%s_form_generate_values' % (acquirer.name) - if tx_id and hasattr(self.pool['payment.transaction'], cust_method_name): - method = getattr(self.pool['payment.transaction'], cust_method_name) - tx_values = method(cr, uid, tx_id, tx_values, context=context) - elif hasattr(self, cust_method_name): + if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) - tx_values = method(cr, uid, id, reference, amount, currency, partner_id, partner_values, tx_values, context=context) + partner_values, tx_values = method(cr, uid, id, partner_values, tx_values, context=context) qweb_context = { + 'tx_url': context.get('tx_url', self.get_form_action_url(cr, uid, id, context=context)), + 'submit_class': context.get('submit_class', 'btn btn-link'), + 'submit_txt': context.get('submit_txt'), 'acquirer': acquirer, 'user': self.pool.get("res.users").browse(cr, uid, uid, context=context), - 'reference': reference, - 'amount': amount, - 'currency': currency, - 'partner': partner, + 'reference': tx_values['reference'], + 'amount': tx_values['amount'], + 'currency': tx_values['currency'], + 'partner': tx_values.get('partner'), 'partner_values': partner_values, 'tx_values': tx_values, 'context': context, } - # because render accepts view ids but not qweb -> need to find the xml_id + + # because render accepts view ids but not qweb -> need to use the xml_id return self.pool['ir.ui.view'].render(cr, uid, acquirer.view_template_id.xml_id, qweb_context, engine='ir.qweb', context=context) @@ -186,12 +258,6 @@ class PaymentTransaction(osv.Model): """ Transaction Model. Each specific acquirer can extend the model by adding its own fields. - Methods that should be added in an acquirer-specific implementation: - - - ``_form_generate_values(self, cr, uid, id, tx_custom_values=None, - context=None)``: method that generates the values used to render the - form button template. - Methods that can be added in an acquirer-specific implementation: - ``_create``: method receiving values used when creating a new @@ -230,22 +296,29 @@ class PaymentTransaction(osv.Model): help='Field used to store error and/or validation messages for information'), # payment 'amount': fields.float('Amount', required=True, - help='Amount in cents', - track_visibility='always'), - 'fees': fields.float('Fees', help='Fees amount; set by the system because depends on the acquirer'), + digits=(16, 2), + track_visibility='always', + help='Amount in cents'), + 'fees': fields.float('Fees', + digits=(16, 2), + track_visibility='always', + help='Fees amount; set by the system because depends on the acquirer'), 'currency_id': fields.many2one('res.currency', 'Currency', required=True), 'reference': fields.char('Order Reference', required=True), + 'acquirer_reference': fields.char('Acquirer Order Reference', + help='Reference of the TX as stored in the acquirer database'), # duplicate partner / transaction data to store the values at transaction time - 'partner_id': fields.many2one('res.partner', 'Partner'), + 'partner_id': fields.many2one('res.partner', 'Partner', track_visibility='onchange',), 'partner_name': fields.char('Partner Name'), 'partner_lang': fields.char('Lang'), 'partner_email': fields.char('Email'), 'partner_zip': fields.char('Zip'), 'partner_address': fields.char('Address'), 'partner_city': fields.char('City'), - 'partner_country_id': fields.many2one('res.country', 'Country'), + 'partner_country_id': fields.many2one('res.country', 'Country', required=True), 'partner_phone': fields.char('Phone'), - 'partner_reference': fields.char('Buyer Reference'), + 'partner_reference': fields.char('Partner Reference', + help='Reference of the customer in the acquirer database'), } _sql_constraints = [ @@ -272,8 +345,9 @@ class PaymentTransaction(osv.Model): # compute fees custom_method_name = '%s_compute_fees' % acquirer.name if hasattr(Acquirer, custom_method_name): - values['fees'] = getattr(Acquirer, custom_method_name)( + fees = getattr(Acquirer, custom_method_name)( cr, uid, acquirer.id, values.get('amount', 0.0), values.get('currency_id'), values.get('country_id'), context=None) + values['fees'] = float_round(fees, 2) # custom create custom_method_name = '%s_create' % acquirer.name @@ -283,30 +357,19 @@ class PaymentTransaction(osv.Model): return super(PaymentTransaction, self).create(cr, uid, values, context=context) def on_change_partner_id(self, cr, uid, ids, partner_id, context=None): + partner = None if partner_id: partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) - values = { - 'partner_name': partner.name, - 'partner_lang': partner.lang, - 'partner_email': partner.email, - 'partner_zip': partner.zip, - 'partner_address': _partner_format_address(partner.street, partner.street2), - 'partner_city': partner.city, - 'partner_country_id': partner.country_id.id, - 'partner_phone': partner.phone, - } - else: - values = { - 'partner_name': False, - 'partner_lang': 'en_US', - 'partner_email': False, - 'partner_zip': False, - 'partner_address': False, - 'partner_city': False, - 'partner_country_id': False, - 'partner_phone': False, - } - return {'values': values} + return {'values': { + 'partner_name': partner and partner.name or False, + 'partner_lang': partner and partner.lang or 'en_US', + 'partner_email': partner and partner.email or False, + 'partner_zip': partner and partner.zip or False, + 'partner_address': _partner_format_address(partner and partner.street or '', partner and partner.street2 or ''), + 'partner_city': partner and partner.city or False, + 'partner_country_id': partner and partner.country_id.id or False, + 'partner_phone': partner and partner.phone or False, + }} # -------------------------------------------------- # FORM RELATED METHODS diff --git a/addons/payment_acquirer/tests/common.py b/addons/payment_acquirer/tests/common.py index 5c915f80cbf..93a1820a447 100644 --- a/addons/payment_acquirer/tests/common.py +++ b/addons/payment_acquirer/tests/common.py @@ -14,8 +14,10 @@ class PaymentAcquirerCommon(common.TransactionCase): self.cr, self.uid, [('name', '=', 'EUR')], limit=1)[0] self.currency_euro = self.registry('res.currency').browse( self.cr, self.uid, self.currency_euro_id) - country_belgium_id = self.registry('res.country').search( + self.country_belgium_id = self.registry('res.country').search( self.cr, self.uid, [('code', 'like', 'BE')], limit=1)[0] + self.country_france_id = self.registry('res.country').search( + self.cr, self.uid, [('code', 'like', 'FR')], limit=1)[0] # dict partner values self.buyer_values = { @@ -27,7 +29,7 @@ class PaymentAcquirerCommon(common.TransactionCase): 'phone': '0032 12 34 56 78', 'city': 'Sin City', 'zip': '1000', - 'country_id': country_belgium_id, + 'country_id': self.country_belgium_id, 'country_name': 'Belgium', } @@ -42,6 +44,6 @@ class PaymentAcquirerCommon(common.TransactionCase): 'phone': '0032 12 34 56 78', 'city': 'Sin City', 'zip': '1000', - 'country_id': country_belgium_id, + 'country_id': self.country_belgium_id, } ) diff --git a/addons/payment_acquirer/views/payment_acquirer.xml b/addons/payment_acquirer/views/payment_acquirer.xml index 7377f4aedb3..9f7429d3f22 100644 --- a/addons/payment_acquirer/views/payment_acquirer.xml +++ b/addons/payment_acquirer/views/payment_acquirer.xml @@ -38,10 +38,10 @@
- This is an HTML form template to submit a payment through this acquirer. - The template will be rendered with qWeb, so it may use qWeb expressions. - The qWeb evaluation context provides: + This template renders the acquirer button with all necessary values. + It is be rendered with qWeb with the following evaluation context:
    +
  • tx_url: transaction URL to post the form
  • acquirer: payment.acquirer browse record
  • user: current user browse record
  • reference: the transaction reference number
  • @@ -49,7 +49,7 @@
  • amount: the transaction amount, a float
  • partner: the buyer partner browse record, not necessarily set
  • partner_values: specific values about the buyer, for example coming from a shipping form
  • -
  • tx_values: specific transaction values
  • +
  • tx_values: transaction values
  • context: the current context dictionary
@@ -98,23 +98,32 @@
- + + - - + - - + + + + + + + + + + + diff --git a/addons/payment_acquirer_adyen/controllers/main.py b/addons/payment_acquirer_adyen/controllers/main.py index 706b26e163d..2ea9fb00c51 100644 --- a/addons/payment_acquirer_adyen/controllers/main.py +++ b/addons/payment_acquirer_adyen/controllers/main.py @@ -19,7 +19,7 @@ class AdyenController(http.Controller): @website.route([ '/payment/adyen/return/', - ], type='http', auth='public', methods=['POST']) + ], type='http', auth='public') def adyen_return(self, **post): """ Paypal IPN. """ _logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug diff --git a/addons/payment_acquirer_adyen/data/adyen.xml b/addons/payment_acquirer_adyen/data/adyen.xml index ec5cb3c7373..92b0cf178b5 100644 --- a/addons/payment_acquirer_adyen/data/adyen.xml +++ b/addons/payment_acquirer_adyen/data/adyen.xml @@ -1,14 +1,14 @@ - + adyen test - OpenERPCOM - cbqYWvVL - cbqYWvVL + dummy + dummy + dummy diff --git a/addons/payment_acquirer_adyen/models/adyen.py b/addons/payment_acquirer_adyen/models/adyen.py index 54c5cdfeb9f..761381d415c 100644 --- a/addons/payment_acquirer_adyen/models/adyen.py +++ b/addons/payment_acquirer_adyen/models/adyen.py @@ -21,26 +21,24 @@ _logger = logging.getLogger(__name__) class AcquirerAdyen(osv.Model): _inherit = 'payment.acquirer' - def _get_adyen_urls(self, cr, uid, ids, name, args, context=None): + def _get_adyen_urls(self, cr, uid, env, context=None): """ Adyen URLs - yhpp: hosted payment page: pay.shtml for single, select.shtml for multiple """ - res = {} - for acquirer in self.browse(cr, uid, ids, context=context): - qualif = acquirer.env - res[acquirer.id] = { - 'adyen_form_url': 'https://%s.adyen.com/hpp/pay.shtml' % qualif, + if env == 'prod': + return { + 'adyen_form_url': 'https://prod.adyen.com/hpp/pay.shtml', + } + else: + return { + 'adyen_form_url': 'https://test.adyen.com/hpp/pay.shtml', } - return res _columns = { 'adyen_merchant_account': fields.char('Merchant Account', required_if_provider='adyen'), 'adyen_skin_code': fields.char('Skin Code', required_if_provider='adyen'), 'adyen_skin_hmac_key': fields.char('Skin HMAC Key', required_if_provider='adyen'), - 'adyen_form_url': fields.function( - _get_adyen_urls, multi='_get_adyen_urls', - type='char', string='Transaction URL', required_if_provider='adyen'), } def _adyen_generate_merchant_sig(self, acquirer, inout, values): @@ -72,9 +70,7 @@ class AcquirerAdyen(osv.Model): key = acquirer.adyen_skin_hmac_key.encode('ascii') return base64.b64encode(hmac.new(key, sign, sha1).digest()) - def adyen_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None): - if partner_values is None: - partner_values = {} + def adyen_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') acquirer = self.browse(cr, uid, id, context=context) # tmp @@ -82,31 +78,26 @@ class AcquirerAdyen(osv.Model): from dateutil import relativedelta tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1) - partner = None - if partner_id: - partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) - tx_values = { - 'merchantReference': reference, - 'paymentAmount': '%d' % int(float_round(amount, 2) * 100), - 'currencyCode': currency and currency.name or 'EUR', + adyen_tx_values = dict(tx_values) + adyen_tx_values.update({ + 'merchantReference': tx_values['reference'], + 'paymentAmount': '%d' % int(float_round(tx_values['amount'], 2) * 100), + 'currencyCode': tx_values['currency'] and tx_values['currency'].name or '', 'shipBeforeDate': tmp_date, 'skinCode': acquirer.adyen_skin_code, 'merchantAccount': acquirer.adyen_merchant_account, - 'shopperLocale': partner and partner.lang or partner_values.get('lang', 'en_US'), + 'shopperLocale': partner_values['lang'], 'sessionValidity': tmp_date, - 'merchantSig': 'oij', 'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url), - } - if tx_custom_values and tx_custom_values.get('return_url'): - tx_values['merchantReturnData'] = json.dumps({'return_url': '%s' % tx_custom_values.pop('return_url')}) - if tx_custom_values: - tx_values.update(tx_custom_values) - tx_values['merchantSig'] = self._adyen_generate_merchant_sig(acquirer, 'in', tx_values) - return tx_values + }) + if adyen_tx_values.get('return_url'): + adyen_tx_values['merchantReturnData'] = json.dumps({'return_url': '%s' % adyen_tx_values.pop('return_url')}) + adyen_tx_values['merchantSig'] = self._adyen_generate_merchant_sig(acquirer, 'in', adyen_tx_values) + return partner_values, adyen_tx_values def adyen_get_form_action_url(self, cr, uid, id, context=None): acquirer = self.browse(cr, uid, id, context=context) - return acquirer.adyen_form_url + return self._get_adyen_urls(cr, uid, acquirer.env, context=context)['adyen_form_url'] class TxAdyen(osv.Model): @@ -120,21 +111,6 @@ class TxAdyen(osv.Model): # FORM RELATED METHODS # -------------------------------------------------- - def adyen_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None): - tx = self.browse(cr, uid, id, context=context) - - tx_data = { - 'shopperLocale': tx.partner_lang, - } - if tx_custom_values: - tx_data.update(tx_custom_values) - return self.pool['payment.acquirer'].paypal_form_generate_values( - cr, uid, tx.acquirer_id.id, - tx.reference, tx.amount, tx.currency_id, - tx_custom_values=tx_data, - context=context - ) - def _adyen_form_get_tx_from_data(self, cr, uid, data, context=None): reference, pspReference = data.get('merchantReference'), data.get('pspReference') if not reference or not pspReference: @@ -164,12 +140,18 @@ class TxAdyen(osv.Model): return tx def _adyen_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): - # TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details invalid_parameters = [] + + # reference at acquirer: pspReference + if tx.acquirer_reference and data.get('pspReference') != tx.acquirer_reference: + invalid_parameters.append(('pspReference', data.get('pspReference'), tx.acquirer_reference)) + # seller if data.get('skinCode') != tx.acquirer_id.adyen_skin_code: invalid_parameters.append(('skinCode', data.get('skinCode'), tx.acquirer_id.adyen_skin_code)) + # result if not data.get('authResult'): invalid_parameters.append(('authResult', data.get('authResult'), 'something')) + return invalid_parameters def _adyen_form_validate(self, cr, uid, tx, data, context=None): diff --git a/addons/payment_acquirer_adyen/static/description/icon.png b/addons/payment_acquirer_adyen/static/description/icon.png index ccbc55b8c5d..e1889808855 100644 Binary files a/addons/payment_acquirer_adyen/static/description/icon.png and b/addons/payment_acquirer_adyen/static/description/icon.png differ diff --git a/addons/payment_acquirer_adyen/static/src/img/adyen_icon.png b/addons/payment_acquirer_adyen/static/src/img/adyen_icon.png new file mode 100644 index 00000000000..90d492c3fe9 Binary files /dev/null and b/addons/payment_acquirer_adyen/static/src/img/adyen_icon.png differ diff --git a/addons/payment_acquirer_adyen/static/src/img/logo.jpg b/addons/payment_acquirer_adyen/static/src/img/logo.jpg deleted file mode 100644 index 63a58006324..00000000000 Binary files a/addons/payment_acquirer_adyen/static/src/img/logo.jpg and /dev/null differ diff --git a/addons/payment_acquirer_adyen/static/src/img/logo.png b/addons/payment_acquirer_adyen/static/src/img/logo.png new file mode 100644 index 00000000000..e1889808855 Binary files /dev/null and b/addons/payment_acquirer_adyen/static/src/img/logo.png differ diff --git a/addons/payment_acquirer_adyen/tests/__init__.py b/addons/payment_acquirer_adyen/tests/__init__.py index b201c351ca7..8c8e8249184 100644 --- a/addons/payment_acquirer_adyen/tests/__init__.py +++ b/addons/payment_acquirer_adyen/tests/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from . import test_adyen +# from . import test_adyen checks = [ - test_adyen, + # test_adyen, ] diff --git a/addons/payment_acquirer_adyen/tests/test_adyen.py b/addons/payment_acquirer_adyen/tests/test_adyen.py index 4fe53b6f2ca..14b5f8fd5e5 100644 --- a/addons/payment_acquirer_adyen/tests/test_adyen.py +++ b/addons/payment_acquirer_adyen/tests/test_adyen.py @@ -1,34 +1,24 @@ # -*- coding: utf-8 -*- +from lxml import objectify +import urlparse + from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError from openerp.addons.payment_acquirer.tests.common import PaymentAcquirerCommon from openerp.addons.payment_acquirer_adyen.controllers.main import AdyenController from openerp.osv.orm import except_orm from openerp.tools import mute_logger -from lxml import objectify -import urlparse - class AdyenCommon(PaymentAcquirerCommon): def setUp(self): super(AdyenCommon, self).setUp() cr, uid = self.cr, self.uid - self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url') - model, self.paypal_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_adyen', 'adyen_acquirer_button') - # create a new ogone account - self.adyen_id = self.payment_acquirer.create( - cr, uid, { - 'name': 'adyen', - 'env': 'test', - 'view_template_id': self.paypal_view_id, - 'adyen_merchant_account': 'OpenERPCOM', - 'adyen_skin_code': 'cbqYWvVL', - 'adyen_skin_hmac_key': 'cbqYWvVL', - }) + # get the adyen account + model, self.adyen_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_adyen', 'payment_acquirer_adyen') # some CC (always use expiration date 06 / 2016, cvc 737, cid 7373 (amex)) self.amex = (('370000000000002', '7373')) @@ -57,6 +47,9 @@ class AdyenForm(AdyenCommon): def test_10_adyen_form_render(self): cr, uid, context = self.cr, self.uid, {} + # be sure not to do stupid things + adyen = self.payment_acquirer.browse(self.cr, self.uid, self.adyen_id, None) + self.assertEqual(adyen.env, 'test', 'test without test env') # ---------------------------------------- # Test: button direct rendering @@ -74,7 +67,7 @@ class AdyenForm(AdyenCommon): # render the button res = self.payment_acquirer.render( cr, uid, self.adyen_id, - 'test_ref0', 0.01, self.currency_euro, + 'test_ref0', 0.01, self.currency_euro_id, partner_id=None, partner_values=self.buyer_values, context=context) @@ -88,12 +81,15 @@ class AdyenForm(AdyenCommon): self.assertEqual( form_input.get('value'), form_values[form_input.get('name')], - 'adyen: wrong value for form: received %s instead of %s' % (form_input.get('value'), form_values[form_input.get('name')]) + 'adyen: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')]) ) @mute_logger('openerp.addons.payment_acquirer_paypal.models.paypal', 'ValidationError') def test_20_paypal_form_management(self): cr, uid, context = self.cr, self.uid, {} + # be sure not to do stupid things + adyen = self.payment_acquirer.browse(self.cr, self.uid, self.adyen_id, None) + self.assertEqual(adyen.env, 'test', 'test without test env') # {'authResult': u'AUTHORISED', # 'merchantReference': u'SO014', diff --git a/addons/payment_acquirer_adyen/views/adyen.xml b/addons/payment_acquirer_adyen/views/adyen.xml index aeaa5260b11..55a5fd4b5b0 100644 --- a/addons/payment_acquirer_adyen/views/adyen.xml +++ b/addons/payment_acquirer_adyen/views/adyen.xml @@ -3,7 +3,7 @@ diff --git a/addons/payment_acquirer_adyen/views/payment_acquirer.xml b/addons/payment_acquirer_adyen/views/payment_acquirer.xml index 9b75828f315..663cf1efe74 100644 --- a/addons/payment_acquirer_adyen/views/payment_acquirer.xml +++ b/addons/payment_acquirer_adyen/views/payment_acquirer.xml @@ -7,10 +7,11 @@ payment.acquirer - - + + + + diff --git a/addons/payment_acquirer_ogone/controllers/main.py b/addons/payment_acquirer_ogone/controllers/main.py index 69307fcc181..0e302d90019 100644 --- a/addons/payment_acquirer_ogone/controllers/main.py +++ b/addons/payment_acquirer_ogone/controllers/main.py @@ -22,7 +22,8 @@ class OgoneController(http.Controller): '/payment/ogone/cancel', '/payment/ogone/test/cancel', ], type='http', auth='admin') def ogone_form_feedback(self, **post): + """ Ogone contacts using GET, at least for accept """ _logger.info('Ogone: entering form_feedback with post data %s', pprint.pformat(post)) # debug cr, uid, context = request.cr, request.uid, request.context - request.registry['payment.transaction'].ogone_form_feedback(cr, uid, post, context) + request.registry['payment.transaction'].form_feedback(cr, uid, post, 'ogone', context=context) return request.redirect(post.pop('return_url', '/')) diff --git a/addons/payment_acquirer_ogone/data/ogone.xml b/addons/payment_acquirer_ogone/data/ogone.xml index 51f67114b1f..2c57a361ef6 100644 --- a/addons/payment_acquirer_ogone/data/ogone.xml +++ b/addons/payment_acquirer_ogone/data/ogone.xml @@ -1,6 +1,6 @@ - + ogone @@ -9,8 +9,8 @@ dummy dummy dummy - tINY4Yv14789gUix1130 - tINYj885Tfvd4P471464 + dummy + dummy diff --git a/addons/payment_acquirer_ogone/models/ogone.py b/addons/payment_acquirer_ogone/models/ogone.py index b06505c1cdd..17726beefd6 100644 --- a/addons/payment_acquirer_ogone/models/ogone.py +++ b/addons/payment_acquirer_ogone/models/ogone.py @@ -14,6 +14,7 @@ from openerp.addons.payment_acquirer_ogone.controllers.main import OgoneControll from openerp.addons.payment_acquirer_ogone.data import ogone from openerp.osv import osv, fields from openerp.tools import float_round +from openerp.tools.float_utils import float_compare _logger = logging.getLogger(__name__) @@ -21,48 +22,26 @@ _logger = logging.getLogger(__name__) class PaymentAcquirerOgone(osv.Model): _inherit = 'payment.acquirer' - def _get_ogone_urls(self, cr, uid, ids, name, args, context=None): + def _get_ogone_urls(self, cr, uid, env, context=None): """ Ogone URLS: - standard order: POST address for form-based @TDETODO: complete me """ - res = {} - for acquirer in self.browse(cr, uid, ids, context=context): - qualif = acquirer.env - res[acquirer.id] = { - 'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.asp' % qualif, - 'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % qualif, - 'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect.asp' % qualif, - 'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % qualif, - } - return res + return { + 'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.asp' % env, + 'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % env, + 'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect.asp' % env, + 'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % env, + } _columns = { - 'ogone_pspid': fields.char( - 'PSPID', required_if_provider='ogone'), - 'ogone_userid': fields.char( - 'API User id', required_if_provider='ogone'), - 'ogone_password': fields.char( - 'Password', required_if_provider='ogone'), - 'ogone_shakey_in': fields.char( - 'SHA Key IN', size=32, required_if_provider='ogone'), - 'ogone_shakey_out': fields.char( - 'SHA Key OUT', size=32, required_if_provider='ogone'), - # store ogone contact URLs -> not necessary IMHO - 'ogone_standard_order_url': fields.function( - _get_ogone_urls, type='char', multi='_get_ogone_urls', - string='Stanrd Order URL (form)'), - 'ogone_direct_order_url': fields.function( - _get_ogone_urls, type='char', multi='_get_ogone_urls', - string='Direct Order URL (2)'), - 'ogone_direct_query_url': fields.function( - _get_ogone_urls, type='char', multi='_get_ogone_urls', - string='Direct Query URL'), - 'ogone_afu_agree_url': fields.function( - _get_ogone_urls, type='char', multi='_get_ogone_urls', - string='AFU Agree URL'), + 'ogone_pspid': fields.char('PSPID', required_if_provider='ogone'), + 'ogone_userid': fields.char('API User ID', required_if_provider='ogone'), + 'ogone_password': fields.char('API User Password', required_if_provider='ogone'), + 'ogone_shakey_in': fields.char('SHA Key IN', size=32, required_if_provider='ogone'), + 'ogone_shakey_out': fields.char('SHA Key OUT', size=32, required_if_provider='ogone'), } def _ogone_generate_shasign(self, acquirer, inout, values): @@ -93,43 +72,39 @@ class PaymentAcquirerOgone(osv.Model): shasign = sha1(sign).hexdigest() return shasign - def ogone_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None): - if partner_values is None: - partner_values = {} + def ogone_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') acquirer = self.browse(cr, uid, id, context=context) - partner = None - if partner_id: - partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) - tx_values = { + + ogone_tx_values = dict(tx_values) + temp_ogone_tx_values = { 'PSPID': acquirer.ogone_pspid, - 'ORDERID': reference, - 'AMOUNT': '%d' % int(float_round(amount, 2) * 100), - 'CURRENCY': currency and currency.name or 'EUR', - 'LANGUAGE': partner and partner.lang or partner_values.get('lang', ''), - 'CN': partner and partner.name or partner_values.get('name', ''), - 'EMAIL': partner and partner.email or partner_values.get('email', ''), - 'OWNERZIP': partner and partner.zip or partner_values.get('zip', ''), - 'OWNERADDRESS': partner and ' '.join((partner.street or '', partner.street2 or '')).strip() or ' '.join((partner_values.get('street', ''), partner_values.get('street2', ''))).strip(), - 'OWNERTOWN': partner and partner.city or partner_values.get('city', ''), - 'OWNERCTY': partner and partner.country_id and partner.country_id.name or partner_values.get('country_name', ''), - 'OWNERTELNO': partner and partner.phone or partner_values.get('phone', ''), + 'ORDERID': tx_values['reference'], + 'AMOUNT': '%d' % int(float_round(tx_values['amount'], 2) * 100), + 'CURRENCY': tx_values['currency'] and tx_values['currency'].name or '', + 'LANGUAGE': partner_values['lang'], + 'CN': partner_values['name'], + 'EMAIL': partner_values['email'], + 'OWNERZIP': partner_values['zip'], + 'OWNERADDRESS': partner_values['address'], + 'OWNERTOWN': partner_values['city'], + 'OWNERCTY': partner_values['country'] and partner_values['country'].name or '', + 'OWNERTELNO': partner_values['phone'], 'ACCEPTURL': '%s' % urlparse.urljoin(base_url, OgoneController._accept_url), 'DECLINEURL': '%s' % urlparse.urljoin(base_url, OgoneController._decline_url), 'EXCEPTIONURL': '%s' % urlparse.urljoin(base_url, OgoneController._exception_url), 'CANCELURL': '%s' % urlparse.urljoin(base_url, OgoneController._cancel_url), } - if tx_custom_values and tx_custom_values.get('return_url'): - tx_values['PARAMPLUS'] = 'return_url=%s' % tx_custom_values.pop('return_url') - if tx_custom_values: - tx_values.update(tx_custom_values) - shasign = self._ogone_generate_shasign(acquirer, 'in', tx_values) - tx_values['SHASIGN'] = shasign - return tx_values + if ogone_tx_values.get('return_url'): + temp_ogone_tx_values['PARAMPLUS'] = 'return_url=%s' % ogone_tx_values.pop('return_url') + shasign = self._ogone_generate_shasign(acquirer, 'in', temp_ogone_tx_values) + temp_ogone_tx_values['SHASIGN'] = shasign + ogone_tx_values.update(temp_ogone_tx_values) + return partner_values, ogone_tx_values def ogone_get_form_action_url(self, cr, uid, id, context=None): acquirer = self.browse(cr, uid, id, context=context) - return acquirer.ogone_standard_order_url + return self._get_ogone_urls(cr, uid, acquirer.env, context=context)['ogone_standard_order_url'] class PaymentTxOgone(osv.Model): @@ -141,10 +116,8 @@ class PaymentTxOgone(osv.Model): _ogone_cancel_tx_status = [1] _columns = { - 'ogone_3ds': fields.dummy('3ds Activated'), + 'ogone_3ds': fields.boolean('3DS Activated'), 'ogone_3ds_html': fields.html('3DS HTML'), - 'ogone_feedback_model': fields.char(), - 'ogone_feedback_eval': fields.char(), 'ogone_complus': fields.char('Complus'), 'ogone_payid': fields.char('PayID', help='Payment ID, generated by Ogone') } @@ -153,30 +126,6 @@ class PaymentTxOgone(osv.Model): # FORM RELATED METHODS # -------------------------------------------------- - def ogone_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None): - """ Ogone-specific value generation for rendering a transaction-based - form button. """ - tx = self.browse(cr, uid, id, context=context) - - tx_data = { - 'LANGUAGE': tx.partner_lang, - 'CN': tx.partner_name, - 'EMAIL': tx.partner_email, - 'OWNERZIP': tx.partner_zip, - 'OWNERADDRESS': tx.partner_address, - 'OWNERTOWN': tx.partner_city, - 'OWNERCTY': tx.partner_country_id and tx.partner_country_id.name or '', - 'OWNERTELNO': tx.partner_phone, - } - if tx_custom_values: - tx_data.update(tx_custom_values) - return self.pool['payment.acquirer'].ogone_form_generate_values( - cr, uid, tx.acquirer_id.id, - tx.reference, tx.amount, tx.currency_id, - tx_custom_values=tx_data, - context=context - ) - def _ogone_form_get_tx_from_data(self, cr, uid, data, context=None): """ Given a data dict coming from ogone, verify it and find the related transaction record. """ @@ -207,29 +156,42 @@ class PaymentTxOgone(osv.Model): return tx - def ogone_form_feedback(self, cr, uid, data, context=None): - tx = self._ogone_form_get_tx_from_data(cr, uid, data, context) - if not tx: - raise ValidationError('Ogone: feedback: tx not found') + def _ogone_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): + invalid_parameters = [] + + # TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details + if tx.acquirer_reference and data.get('PAYID') != tx.acquirer_reference: + invalid_parameters.append(('PAYID', data.get('PAYID'), tx.acquirer_reference)) + # check what is buyed + if float_compare(float(data.get('amount', '0.0')), tx.amount, 2) != 0: + invalid_parameters.append(('amount', data.get('amount'), '%.2f' % tx.amount)) + if data.get('currency') != tx.currency_id.name: + invalid_parameters.append(('currency', data.get('currency'), tx.currency_id.name)) + + return invalid_parameters + + def _ogone_form_validate(self, cr, uid, tx, data, context=None): if tx.state == 'done': - _logger.warning('Ogone: trying to validate an already validated tx (ref %s' % tx.reference) - return False + _logger.warning('Ogone: trying to validate an already validated tx (ref %s)' % tx.reference) + return True status = int(data.get('STATUS', '0')) if status in self._ogone_valid_tx_status: tx.write({ 'state': 'done', 'date_validate': data['TRXDATE'], - 'ogone_payid': data['PAYID'], + 'acquirer_reference': data['PAYID'], }) return True elif status in self._ogone_cancel_tx_status: tx.write({ 'state': 'cancel', + 'acquirer_reference': data.get('PAYID'), }) elif status in self._ogone_pending_tx_status: tx.write({ 'state': 'pending', + 'acquirer_reference': data.get('PAYID'), }) else: error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % { @@ -238,7 +200,11 @@ class PaymentTxOgone(osv.Model): 'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERRORPLUS')), } _logger.info(error) - tx.write({'state': 'error', 'state_message': error}) + tx.write({ + 'state': 'error', + 'state_message': error, + 'acquirer_reference': data.get('PAYID'), + }) return False # -------------------------------------------------- @@ -268,6 +234,7 @@ class PaymentTxOgone(osv.Model): 'PROCESS_MODE': 'CHECKANDPROCESS', } + # TODO: fix URL computation request = urllib2.Request(tx.acquirer_id.ogone_afu_agree_url, urlencode(tx_data)) result = urllib2.urlopen(request).read() diff --git a/addons/payment_acquirer_ogone/static/src/img/ogone_icon.png b/addons/payment_acquirer_ogone/static/src/img/ogone_icon.png new file mode 100644 index 00000000000..f8274724529 Binary files /dev/null and b/addons/payment_acquirer_ogone/static/src/img/ogone_icon.png differ diff --git a/addons/payment_acquirer_ogone/static/src/img/ogone_logo.png b/addons/payment_acquirer_ogone/static/src/img/ogone_logo.png deleted file mode 100644 index 89e0ef6816f..00000000000 Binary files a/addons/payment_acquirer_ogone/static/src/img/ogone_logo.png and /dev/null differ diff --git a/addons/payment_acquirer_ogone/static/src/img/ogone_logo_plain.gif b/addons/payment_acquirer_ogone/static/src/img/ogone_logo_plain.gif deleted file mode 100644 index e7833ec3b6d..00000000000 Binary files a/addons/payment_acquirer_ogone/static/src/img/ogone_logo_plain.gif and /dev/null differ diff --git a/addons/payment_acquirer_ogone/tests/test_ogone.py b/addons/payment_acquirer_ogone/tests/test_ogone.py index 245cef73007..be1f534ea74 100644 --- a/addons/payment_acquirer_ogone/tests/test_ogone.py +++ b/addons/payment_acquirer_ogone/tests/test_ogone.py @@ -1,45 +1,30 @@ # -*- coding: utf-8 -*- +from lxml import objectify +import time +import urlparse + from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError from openerp.addons.payment_acquirer.tests.common import PaymentAcquirerCommon from openerp.addons.payment_acquirer_ogone.controllers.main import OgoneController from openerp.tools import mute_logger -from lxml import objectify -# import requests -import time -import urlparse - class OgonePayment(PaymentAcquirerCommon): def setUp(self): super(OgonePayment, self).setUp() cr, uid = self.cr, self.uid - self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url') - model, self.ogone_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_ogone', 'ogone_acquirer_button') - # create a new ogone account - self.ogone_id = self.payment_acquirer.create( - cr, uid, { - 'name': 'ogone', - 'env': 'test', - 'view_template_id': self.ogone_view_id, - 'ogone_pspid': 'dummy', - 'ogone_userid': 'dummy', - 'ogone_password': 'dummy', - 'ogone_shakey_in': 'tINY4Yv14789gUix1130', - 'ogone_shakey_out': 'tINYj885Tfvd4P471464', - }) - self.ogone_url = self.payment_acquirer._get_ogone_urls(cr, uid, [self.ogone_id], None, None)[self.ogone_id]['ogone_standard_order_url'] - - def test_00_ogone_acquirer(self): - ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None) - self.assertEqual(ogone.env, 'test', 'test without test env') + # get the adyen account + model, self.ogone_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_ogone', 'payment_acquirer_ogone') def test_10_ogone_form_render(self): cr, uid, context = self.cr, self.uid, {} + # be sure not to do stupid thing + ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None) + self.assertEqual(ogone.env, 'test', 'test without test env') # ---------------------------------------- # Test: button direct rendering + shasign @@ -68,7 +53,7 @@ class OgonePayment(PaymentAcquirerCommon): # render the button res = self.payment_acquirer.render( cr, uid, self.ogone_id, - 'test_ref0', 0.01, self.currency_euro, + 'test_ref0', 0.01, self.currency_euro_id, partner_id=None, partner_values=self.buyer_values, context=context) @@ -82,7 +67,7 @@ class OgonePayment(PaymentAcquirerCommon): self.assertEqual( form_input.get('value'), form_values[form_input.get('name')], - 'ogone: wrong value for form: received %s instead of %s' % (form_input.get('value'), form_values[form_input.get('name')]) + 'ogone: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')]) ) # ---------------------------------------- @@ -123,6 +108,9 @@ class OgonePayment(PaymentAcquirerCommon): @mute_logger('openerp.addons.payment_acquirer_ogone.models.ogone', 'ValidationError') def test_20_ogone_form_management(self): cr, uid, context = self.cr, self.uid, {} + # be sure not to do stupid thing + ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None) + self.assertEqual(ogone.env, 'test', 'test without test env') # typical data posted by ogone after client has successfully paid ogone_post_data = { @@ -155,6 +143,7 @@ class OgonePayment(PaymentAcquirerCommon): 'currency_id': self.currency_euro_id, 'reference': 'test_ref_2', 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france_id, }, context=context ) # validate it @@ -183,6 +172,9 @@ class OgonePayment(PaymentAcquirerCommon): def test_30_ogone_s2s(self): test_ref = 'test_ref_%.15f' % time.time() cr, uid, context = self.cr, self.uid, {} + # be sure not to do stupid thing + ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None) + self.assertEqual(ogone.env, 'test', 'test without test env') # create a new draft tx tx_id = self.payment_transaction.create( diff --git a/addons/payment_acquirer_ogone/views/ogone.xml b/addons/payment_acquirer_ogone/views/ogone.xml index f71f92197f0..6cb06507ff1 100644 --- a/addons/payment_acquirer_ogone/views/ogone.xml +++ b/addons/payment_acquirer_ogone/views/ogone.xml @@ -3,7 +3,7 @@ diff --git a/addons/payment_acquirer_ogone/views/payment_acquirer.xml b/addons/payment_acquirer_ogone/views/payment_acquirer.xml index 8ec958a5351..8a64f801dce 100644 --- a/addons/payment_acquirer_ogone/views/payment_acquirer.xml +++ b/addons/payment_acquirer_ogone/views/payment_acquirer.xml @@ -7,9 +7,8 @@ payment.acquirer - - + + @@ -29,8 +28,10 @@ - + + + + diff --git a/addons/payment_acquirer_paypal/controllers/main.py b/addons/payment_acquirer_paypal/controllers/main.py index 9e3c5868307..d5ec93909af 100644 --- a/addons/payment_acquirer_paypal/controllers/main.py +++ b/addons/payment_acquirer_paypal/controllers/main.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- -from openerp.addons.web import http -from openerp.addons.web.http import request -from openerp.addons.website.models import website - try: import simplejson as json except ImportError: import json import logging import pprint -import requests -from urllib import urlencode +import urllib +import urllib2 + +from openerp.addons.web import http +from openerp.addons.web.http import request +from openerp.addons.website.models import website _logger = logging.getLogger(__name__) @@ -21,6 +21,14 @@ class PaypalController(http.Controller): _return_url = '/payment/paypal/dpn/' _cancel_url = '/payment/paypal/cancel/' + def _get_return_url(self, **post): + """ Extract the return URL from the data coming from paypal. """ + return_url = post.pop('return_url', '') + if not return_url: + custom = json.loads(post.pop('custom', '{}')) + return_url = custom.get('return_url', '/') + return return_url + def paypal_validate_data(self, **post): """ Paypal IPN: three steps validation to ensure data correctness @@ -32,14 +40,15 @@ class PaypalController(http.Controller): Once data is validated, process it. """ res = False - paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr" - post_url = '%s?cmd=_notify-validate&%s' % (paypal_url, urlencode(post)) - resp = requests.post(post_url) - if resp.text == 'VERIFIED': + new_post = dict(post, cmd='_notify-validate') + urequest = urllib2.Request("https://www.sandbox.paypal.com/cgi-bin/webscr", urllib.urlencode(new_post)) + uopen = urllib2.urlopen(urequest) + resp = uopen.read() + if resp == 'VERIFIED': _logger.info('Paypal: validated data') cr, uid, context = request.cr, request.uid, request.context res = request.registry['payment.transaction'].form_feedback(cr, uid, post, 'paypal', context=context) - elif resp.text == 'INVALID': + elif resp == 'INVALID': _logger.warning('Paypal: answered INVALID on data verification') else: _logger.warning('Paypal: unrecognized paypal answer, received %s instead of VERIFIED or INVALID' % resp.text) @@ -47,7 +56,7 @@ class PaypalController(http.Controller): @website.route([ '/payment/paypal/ipn/', - ], type='http', auth='public') + ], type='http', auth='public', methods=['POST']) def paypal_ipn(self, **post): """ Paypal IPN. """ _logger.info('Beginning Paypal IPN form_feedback with post data %s', pprint.pformat(post)) # debug @@ -56,14 +65,11 @@ class PaypalController(http.Controller): @website.route([ '/payment/paypal/dpn', - ], type='http', auth="public") + ], type='http', auth="public", methods=['POST']) def paypal_dpn(self, **post): """ Paypal DPN """ _logger.info('Beginning Paypal DPN form_feedback with post data %s', pprint.pformat(post)) # debug - return_url = post.pop('return_url', '') - if not return_url: - custom = json.loads(post.pop('custom', '{}')) - return_url = custom.pop('return_url', '/') + return_url = self._get_return_url(**post) self.paypal_validate_data(**post) return request.redirect(return_url) @@ -71,11 +77,8 @@ class PaypalController(http.Controller): '/payment/paypal/cancel', ], type='http', auth="public") def paypal_cancel(self, **post): - """ TODO - """ + """ When the user cancels its Paypal payment: GET on this route """ cr, uid, context = request.cr, request.uid, request.context - print 'Entering paypal_cancel with post', post - - return_url = post.pop('return_url', '/') - print 'return_url', return_url + _logger.info('Beginning Paypal cancel with post data %s', pprint.pformat(post)) # debug + return_url = self._get_return_url(**post) return request.redirect(return_url) diff --git a/addons/payment_acquirer_paypal/data/paypal.xml b/addons/payment_acquirer_paypal/data/paypal.xml index 117a32dd39f..55245804975 100644 --- a/addons/payment_acquirer_paypal/data/paypal.xml +++ b/addons/payment_acquirer_paypal/data/paypal.xml @@ -6,9 +6,11 @@ paypal test + You will be redirected to the Paypal website after cliking on the payment button.

]]>
https://www.sandbox.paypal.com/cgi-bin/webscr dummy - dummy + dummy dummy dummy diff --git a/addons/payment_acquirer_paypal/models/paypal.py b/addons/payment_acquirer_paypal/models/paypal.py index 8bbd540372b..8a9fac7cc46 100644 --- a/addons/payment_acquirer_paypal/models/paypal.py +++ b/addons/payment_acquirer_paypal/models/paypal.py @@ -1,11 +1,5 @@ # -*- coding: utf-'8' "-*-" -from openerp.addons.payment_acquirer.models import payment_acquirer -from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError -from openerp.addons.payment_acquirer_paypal.controllers.main import PaypalController -from openerp.osv import osv, fields -from openerp.tools.float_utils import float_compare - import base64 try: import simplejson as json @@ -16,16 +10,33 @@ import urlparse import urllib import urllib2 +from openerp.addons.payment_acquirer.models.payment_acquirer import ValidationError +from openerp.addons.payment_acquirer_paypal.controllers.main import PaypalController +from openerp.osv import osv, fields +from openerp.tools.float_utils import float_compare + _logger = logging.getLogger(__name__) class AcquirerPaypal(osv.Model): _inherit = 'payment.acquirer' + def _get_paypal_urls(self, cr, uid, env, context=None): + """ Paypal URLS """ + if env == 'prod': + return { + 'paypal_form_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr', + 'paypal_rest_url': 'https://api.sandbox.paypal.com/v1/oauth2/token', + } + else: + return { + 'paypal_form_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr', + 'paypal_rest_url': 'https://api.sandbox.paypal.com/v1/oauth2/token', + } + _columns = { 'paypal_email_id': fields.char('Email ID', required_if_provider='paypal'), - 'paypal_username': fields.char('Username', required_if_provider='paypal'), - 'paypal_tx_url': fields.char('Transaction URL', required_if_provider='paypal'), + 'paypal_seller_id': fields.char('Seller ID', required_if_provider='paypal'), 'paypal_use_ipn': fields.boolean('Use IPN'), # Server 2 server 'paypal_api_enabled': fields.boolean('Use Rest API'), @@ -36,7 +47,6 @@ class AcquirerPaypal(osv.Model): } _defaults = { - 'paypal_tx_url': 'https://www.sandbox.paypal.com/cgi-bin/webscr', 'paypal_use_ipn': True, 'fees_active': False, 'fees_dom_fixed': 0.35, @@ -65,44 +75,38 @@ class AcquirerPaypal(osv.Model): fees = amount * (1 + acquirer.fees_int_var / 100.0) + acquirer.fees_int_fixed - amount return fees - def paypal_form_generate_values(self, cr, uid, id, reference, amount, currency, partner_id=False, partner_values=None, tx_custom_values=None, context=None): - if partner_values is None: - partner_values = {} + def paypal_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') acquirer = self.browse(cr, uid, id, context=context) - partner = None - if partner_id: - partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) - tx_values = { + paypal_tx_values = dict(tx_values) + paypal_tx_values.update({ 'cmd': '_xclick', 'business': acquirer.paypal_email_id, - 'item_name': reference, - 'item_number': reference, - 'amount': amount, - 'currency_code': currency and currency.name or 'EUR', - 'address1': payment_acquirer._partner_format_address(partner and partner.street or partner_values.get('street', ''), partner and partner.street2 or partner_values.get('street2', '')), - 'city': partner and partner.city or partner_values.get('city', ''), - 'country': partner and partner.country_id and partner.country_id.name or partner_values.get('country_name', ''), - 'email': partner and partner.email or partner_values.get('email', ''), - 'zip': partner and partner.zip or partner_values.get('zip', ''), - 'first_name': payment_acquirer._partner_split_name(partner and partner.name or partner_values.get('name', ''))[0], - 'last_name': payment_acquirer._partner_split_name(partner and partner.name or partner_values.get('name', ''))[1], + 'item_name': tx_values['reference'], + 'item_number': tx_values['reference'], + 'amount': tx_values['amount'], + 'currency_code': tx_values['currency'] and tx_values['currency'].name or '', + 'address1': partner_values['address'], + 'city': partner_values['city'], + 'country': partner_values['country'] and partner_values['country'].name or '', + 'email': partner_values['email'], + 'zip': partner_values['zip'], + 'first_name': partner_values['first_name'], + 'last_name': partner_values['last_name'], 'return': '%s' % urlparse.urljoin(base_url, PaypalController._return_url), 'notify_url': '%s' % urlparse.urljoin(base_url, PaypalController._notify_url), 'cancel_return': '%s' % urlparse.urljoin(base_url, PaypalController._cancel_url), - } + }) if acquirer.fees_active: - tx_values['handling'] = '%.2f' % tx_custom_values.pop('fees', 0.0) - if tx_custom_values and tx_custom_values.get('return_url'): - tx_values['custom'] = json.dumps({'return_url': '%s' % tx_custom_values.pop('return_url')}) - if tx_custom_values: - tx_values.update(tx_custom_values) - return tx_values + paypal_tx_values['handling'] = '%.2f' % paypal_tx_values.pop('fees', 0.0) + if paypal_tx_values.get('return_url'): + paypal_tx_values['custom'] = json.dumps({'return_url': '%s' % paypal_tx_values.pop('return_url')}) + return partner_values, paypal_tx_values def paypal_get_form_action_url(self, cr, uid, id, context=None): acquirer = self.browse(cr, uid, id, context=context) - return acquirer.paypal_tx_url + return self._get_paypal_urls(cr, uid, acquirer.env, context=context)['paypal_form_url'] def _paypal_s2s_get_access_token(self, cr, uid, ids, context=None): """ @@ -144,28 +148,6 @@ class TxPaypal(osv.Model): # FORM RELATED METHODS # -------------------------------------------------- - def paypal_form_generate_values(self, cr, uid, id, tx_custom_values=None, context=None): - tx = self.browse(cr, uid, id, context=context) - - tx_data = { - 'item_name': tx.name, - 'first_name': payment_acquirer._partner_split_name(tx.partner_name)[0], - 'last_name': payment_acquirer._partner_split_name(tx.partner_name)[0], - 'email': tx.partner_email, - 'zip': tx.partner_zip, - 'address1': tx.partner_address, - 'city': tx.partner_city, - 'country': tx.partner_country_id and tx.partner_country_id.name or '', - } - if tx_custom_values: - tx_data.update(tx_custom_values) - return self.pool['payment.acquirer'].paypal_form_generate_values( - cr, uid, tx.acquirer_id.id, - tx.reference, tx.amount, tx.currency_id, - tx_custom_values=tx_data, - context=context - ) - def _paypal_form_get_tx_from_data(self, cr, uid, data, context=None): reference, txn_id = data.get('item_number'), data.get('txn_id') if not reference or not txn_id: @@ -186,9 +168,8 @@ class TxPaypal(osv.Model): return self.browse(cr, uid, tx_ids[0], context=context) def _paypal_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): - # TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details invalid_parameters = [] - if data.get('notify_version')[0] != '2.6': + if data.get('notify_version')[0] != '3.4': _logger.warning( 'Received a notification from Paypal with version %s instead of 2.6. This could lead to issues when managing it.' % data.get('notify_version') @@ -197,60 +178,48 @@ class TxPaypal(osv.Model): _logger.warning( 'Received a notification from Paypal using sandbox' ), + + # TODO: txn_id: shoudl be false at draft, set afterwards, and verified with txn details + if tx.acquirer_reference and data.get('txn_id') != tx.acquirer_reference: + invalid_parameters.append(('txn_id', data.get('txn_id'), tx.acquirer_reference)) # check what is buyed - if float_compare(float(data.get('mc_gross', '0.0')), tx.amount, 2) != 0: - invalid_parameters.append(('mc_gross', data.get('mc_gross'), '%.2f' % tx.amount)) + if float_compare(float(data.get('mc_gross', '0.0')), (tx.amount + tx.fees), 2) != 0: + invalid_parameters.append(('mc_gross', data.get('mc_gross'), '%.2f' % tx.amount)) # mc_gross is amount + fees if data.get('mc_currency') != tx.currency_id.name: - invalid_parameters.append(('mc_currency', data.get('mc_currency'), tx.currency_id.name)) - # if parameters.get('payment_fee') != tx.payment_fee: - # invalid_parameters.append(('payment_fee', tx.payment_fee)) - # if parameters.get('quantity') != tx.quantity: - # invalid_parameters.append(('mc_currency', tx.quantity)) - # if parameters.get('shipping') != tx.shipping: - # invalid_parameters.append(('shipping', tx.shipping)) + invalid_parameters.append(('mc_currency', data.get('mc_currency'), tx.currency_id.name)) + if 'handling_amount' in data and float_compare(float(data.get('handling_amount')), tx.fees, 2) != 0: + invalid_parameters.append(('handling_amount', data.get('handling_amount'), tx.fees)) # check buyer - # if parameters.get('payer_id') != tx.payer_id: - # invalid_parameters.append(('mc_gross', tx.payer_id)) - # if parameters.get('payer_email') != tx.payer_email: - # invalid_parameters.append(('payer_email', tx.payer_email)) + if tx.partner_reference and data.get('payer_id') != tx.partner_reference: + invalid_parameters.append(('payer_id', data.get('payer_id'), tx.partner_reference)) # check seller - # if parameters.get('receiver_email') != tx.receiver_email: - # invalid_parameters.append(('receiver_email', tx.receiver_email)) - # if parameters.get('receiver_id') != tx.receiver_id: - # invalid_parameters.append(('receiver_id', tx.receiver_id)) + if data.get('receiver_email') != tx.acquirer_id.paypal_email_id: + invalid_parameters.append(('receiver_email', data.get('receiver_email'), tx.acquirer_id.paypal_email_id)) + if data.get('receiver_id') != tx.acquirer_id.paypal_seller_id: + invalid_parameters.append(('receiver_id', data.get('receiver_id'), tx.acquirer_id.paypal_seller_id)) return invalid_parameters def _paypal_form_validate(self, cr, uid, tx, data, context=None): status = data.get('payment_status') + data = { + 'acquirer_reference': data.get('txn_id'), + 'paypal_txn_type': data.get('payment_type'), + 'partner_reference': data.get('payer_id') + } if status in ['Completed', 'Processed']: _logger.info('Validated Paypal payment for tx %s: set as done' % (tx.reference)) - tx.write({ - 'state': 'done', - 'date_validate': data.get('payment_date', fields.datetime.now()), - 'paypal_txn_id': data['txn_id'], - 'paypal_txn_type': data.get('express_checkout'), - }) - return True + data.update(state='done', date_validate=data.get('payment_date', fields.datetime.now())) + return tx.write(data) elif status in ['Pending', 'Expired']: _logger.info('Received notification for Paypal payment %s: set as pending' % (tx.reference)) - tx.write({ - 'state': 'pending', - 'state_message': data.get('pending_reason', ''), - 'paypal_txn_id': data['txn_id'], - 'paypal_txn_type': data.get('express_checkout'), - }) - return True + data.udpate(state='pending', state_message=data.get('pending_reason', '')) + return tx.write(data) else: error = 'Received unrecognized status for Paypal payment %s: %s, set as error' % (tx.reference, status) _logger.info(error) - tx.write({ - 'state': 'error', - 'state_message': error, - 'paypal_txn_id': data['txn_id'], - 'paypal_txn_type': data.get('express_checkout'), - }) - return False + data.update(state='error', state_message=error) + return tx.write(data) # -------------------------------------------------- # SERVER2SERVER RELATED METHODS diff --git a/addons/payment_acquirer_paypal/static/src/img/paypal_icon.png b/addons/payment_acquirer_paypal/static/src/img/paypal_icon.png new file mode 100644 index 00000000000..9d0534b3fa8 Binary files /dev/null and b/addons/payment_acquirer_paypal/static/src/img/paypal_icon.png differ diff --git a/addons/payment_acquirer_paypal/tests/test_paypal.py b/addons/payment_acquirer_paypal/tests/test_paypal.py index c29e605e1c8..00dbe869e14 100644 --- a/addons/payment_acquirer_paypal/tests/test_paypal.py +++ b/addons/payment_acquirer_paypal/tests/test_paypal.py @@ -15,22 +15,10 @@ class PaypalCommon(PaymentAcquirerCommon): def setUp(self): super(PaypalCommon, self).setUp() cr, uid = self.cr, self.uid - self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url') - model, self.paypal_view_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_paypal', 'paypal_acquirer_button') - # create a new ogone account - self.paypal_id = self.payment_acquirer.create( - cr, uid, { - 'name': 'paypal', - 'env': 'test', - 'view_template_id': self.paypal_view_id, - 'paypal_email_id': 'dummy', - 'paypal_username': 'dummy', - 'paypal_api_enabled': True, - 'paypal_api_username': 'dummy', - 'paypal_api_password': 'dummy', - }) + # get the paypal account + model, self.paypal_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_acquirer_paypal', 'payment_acquirer_paypal') # tde+seller@openerp.com - tde+buyer@openerp.com - tde+buyer-it@openerp.com # some CC @@ -50,6 +38,9 @@ class PaypalServer2Server(PaypalCommon): def test_00_tx_management(self): cr, uid, context = self.cr, self.uid, {} + # be sure not to do stupid things + paypal = self.payment_acquirer.browse(self.cr, self.uid, self.paypal_id, None) + self.assertEqual(paypal.env, 'test', 'test without test env') res = self.payment_acquirer._paypal_s2s_get_access_token(cr, uid, [self.paypal_id], context=context) self.assertTrue(res[self.paypal_id] is not False, 'paypal: did not generate access token') @@ -78,24 +69,10 @@ class PaypalServer2Server(PaypalCommon): class PaypalForm(PaypalCommon): - def test_00_paypal_acquirer(self): - cr, uid, context = self.cr, self.uid, {} - # forgot some mandatory fields: should crash - with self.assertRaises(except_orm): - self.payment_acquirer.create( - cr, uid, { - 'name': 'paypal', - 'env': 'test', - 'view_template_id': self.paypal_view_id, - 'paypal_email_id': 'tde+paypal-facilitator@openerp.com', - }, context=context) - - paypal = self.payment_acquirer.browse(self.cr, self.uid, self.paypal_id, None) - self.assertEqual(paypal.env, 'test', 'test without test env') - def test_10_paypal_form_render(self): cr, uid, context = self.cr, self.uid, {} # be sure not to do stupid things + self.payment_acquirer.write(cr, uid, self.paypal_id, {'fees_active': False}, context) paypal = self.payment_acquirer.browse(cr, uid, self.paypal_id, context) self.assertEqual(paypal.env, 'test', 'test without test env') @@ -103,6 +80,14 @@ class PaypalForm(PaypalCommon): # Test: button direct rendering # ---------------------------------------- + # render the button + res = self.payment_acquirer.render( + cr, uid, self.paypal_id, + 'test_ref0', 0.01, self.currency_euro_id, + partner_id=None, + partner_values=self.buyer_values, + context=context) + form_values = { 'cmd': '_xclick', 'business': 'tde+paypal-facilitator@openerp.com', @@ -122,14 +107,6 @@ class PaypalForm(PaypalCommon): 'cancel_return': '%s' % urlparse.urljoin(self.base_url, PaypalController._cancel_url), } - # render the button - res = self.payment_acquirer.render( - cr, uid, self.paypal_id, - 'test_ref0', 0.01, self.currency_euro, - partner_id=None, - partner_values=self.buyer_values, - context=context) - # check form result tree = objectify.fromstring(res) self.assertEqual(tree.get('action'), 'https://www.sandbox.paypal.com/cgi-bin/webscr', 'paypal: wrong form POST url') @@ -139,19 +116,24 @@ class PaypalForm(PaypalCommon): self.assertEqual( form_input.get('value'), form_values[form_input.get('name')], - 'paypal: wrong value for form: received %s instead of %s' % (form_input.get('value'), form_values[form_input.get('name')]) + 'paypal: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')]) ) def test_11_paypal_form_with_fees(self): cr, uid, context = self.cr, self.uid, {} - self.payment_acquirer.write(cr, uid, self.paypal_id, { - 'fees_active': True, - }, context) - # be sure not to do stupid things paypal = self.payment_acquirer.browse(self.cr, self.uid, self.paypal_id, None) self.assertEqual(paypal.env, 'test', 'test without test env') + # update acquirer: compute fees + self.payment_acquirer.write(cr, uid, self.paypal_id, { + 'fees_active': True, + 'fees_dom_fixed': 1.0, + 'fees_dom_var': 0.35, + 'fees_int_fixed': 1.5, + 'fees_int_var': 0.50, + }, context) + # render the button res = self.payment_acquirer.render( cr, uid, self.paypal_id, @@ -167,7 +149,7 @@ class PaypalForm(PaypalCommon): for form_input in tree.input: if form_input.get('name') in ['handling']: handling_found = True - self.assertEqual(form_input.get('value'), '0.84', 'paypal: wrong computed fees') + self.assertEqual(form_input.get('value'), '1.56', 'paypal: wrong computed fees') self.assertTrue(handling_found, 'paypal: fees_active did not add handling input in rendered form') @mute_logger('openerp.addons.payment_acquirer_paypal.models.paypal', 'ValidationError') @@ -233,6 +215,7 @@ class PaypalForm(PaypalCommon): 'currency_id': self.currency_euro_id, 'reference': 'test_ref_2', 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france_id, }, context=context ) # validate it diff --git a/addons/payment_acquirer_paypal/views/payment_acquirer.xml b/addons/payment_acquirer_paypal/views/payment_acquirer.xml index 6734b2fdcc5..0c00ee37261 100644 --- a/addons/payment_acquirer_paypal/views/payment_acquirer.xml +++ b/addons/payment_acquirer_paypal/views/payment_acquirer.xml @@ -10,14 +10,16 @@ - - - - - - + + + + + + + + diff --git a/addons/payment_acquirer_paypal/views/paypal.xml b/addons/payment_acquirer_paypal/views/paypal.xml index a6f401f6c3c..186d45fc452 100644 --- a/addons/payment_acquirer_paypal/views/paypal.xml +++ b/addons/payment_acquirer_paypal/views/paypal.xml @@ -3,16 +3,14 @@ diff --git a/addons/payment_acquirer_transfer/data/transfer.xml b/addons/payment_acquirer_transfer/data/transfer.xml index ce6e56be57d..ec81cd82719 100644 --- a/addons/payment_acquirer_transfer/data/transfer.xml +++ b/addons/payment_acquirer_transfer/data/transfer.xml @@ -1,6 +1,6 @@ - + transfer diff --git a/addons/payment_acquirer_transfer/static/src/img/transfer_icon.png b/addons/payment_acquirer_transfer/static/src/img/transfer_icon.png new file mode 100644 index 00000000000..63b10e383ec Binary files /dev/null and b/addons/payment_acquirer_transfer/static/src/img/transfer_icon.png differ diff --git a/addons/payment_acquirer_transfer/views/transfer.xml b/addons/payment_acquirer_transfer/views/transfer.xml index af5c6fcef0f..75912a30f42 100644 --- a/addons/payment_acquirer_transfer/views/transfer.xml +++ b/addons/payment_acquirer_transfer/views/transfer.xml @@ -3,16 +3,19 @@ diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 6be7464e325..b6d8ac62c91 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -96,7 +96,7 @@ class Website(openerp.addons.web.controllers.main.Home): if request.context['editable']: page = 'website.page_404' else: - return request.registry['ir.http']._handle_404(e) + return request.registry['ir.http']._handle_exception(e, 404) return request.website.render(page, values) @website.route('/website/customize_template_toggle', type='json', auth='user') diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 7d14c4deacf..765b7e1cc33 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -61,7 +61,7 @@ class ir_http(orm.AbstractModel): request.lang = request.context['lang'] = path.pop(1) path = '/'.join(path) or '/' return self.reroute(path) - return self._handle_404() + return self._handle_exception(code=404) return super(ir_http, self)._dispatch() def reroute(self, path): @@ -79,37 +79,44 @@ class ir_http(orm.AbstractModel): return self._dispatch() - def _handle_403(self, exception): + def _handle_exception(self, exception=None, code=500): + if isinstance(exception, werkzeug.exceptions.HTTPException) and exception.response: + return exception.response if getattr(request, 'cms', False) and request.website: - logger.warn("403 Forbidden:\n\n%s", traceback.format_exc(exception)) - self._auth_method_public() - return self._render_error(403, { - 'error': exception.message - }) - raise exception + values = dict( + exception=exception, + traceback=traceback.format_exc(exception), + ) + if exception: + if isinstance(exception, openerp.exceptions.AccessError): + code = 403 + else: + code = getattr(exception, 'code', code) + values.update( + qweb_template=getattr(exception, 'qweb_template', None), + qweb_node=getattr(exception, 'qweb_node', None), + qweb_eval=getattr(exception, 'qweb_eval', None), + ) + if code == 500: + logger.error("500 Internal Server Error:\n\n%s", values['traceback']) + elif code == 403: + logger.warn("403 Forbidden:\n\n%s", values['traceback']) - def _handle_404(self, exception=None): - if getattr(request, 'cms', False) and request.website: - return self._render_error(404) - raise request.not_found() + values.update( + status_message=werkzeug.http.HTTP_STATUS_CODES[code], + status_code=code, + ) - def _handle_500(self, exception): - if getattr(request, 'cms', False) and request.website: - logger.error("500 Internal Server Error:\n\n%s", traceback.format_exc(exception)) - return self._render_error(500, { - 'exception': exception, - 'traceback': traceback.format_exc(), - 'qweb_template': getattr(exception, 'qweb_template', None), - 'qweb_node': getattr(exception, 'qweb_node', None), - 'qweb_eval': getattr(exception, 'qweb_eval', None), - }) - raise exception + if not request.uid: + self._auth_method_public() - def _render_error(self, code, values=None): - return werkzeug.wrappers.Response( - request.website._render('website.%s' % code, values), - status=code, - content_type='text/html;charset=utf-8') + try: + html = request.website._render('website.%s' % code, values) + except: + html = request.website._render('website.http_error', values) + return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8') + + return super(ir_http, self)._handle_exception(exception) class ModelConverter(ir.ir_http.ModelConverter): def __init__(self, url_map, model=False): diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index 5876fa3873d..bb2062bad92 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -121,33 +121,49 @@ class Date(orm.AbstractModel): _name = 'website.qweb.field.date' _inherit = ['website.qweb.field', 'ir.qweb.field.date'] + def attributes(self, cr, uid, field_name, record, options, + source_element, g_att, t_att, qweb_context, + context=None): + attrs = super(Date, self).attributes( + cr, uid, field_name, record, options, source_element, g_att, t_att, + qweb_context, context=None) + return itertools.chain(attrs, [('data-oe-original', record[field_name])]) + def from_html(self, cr, uid, model, column, element, context=None): - lang = self.user_lang(cr, uid, context=context) - in_format = lang.date_format.encode('utf-8') - value = element.text_content().strip() - try: - dt = datetime.datetime.strptime(in_format, value) - except ValueError: - dt = parse_fuzzy(in_format, value) + if not value: return False - return dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + datetime.datetime.strptime(value, DEFAULT_SERVER_DATE_FORMAT) + return value class DateTime(orm.AbstractModel): _name = 'website.qweb.field.datetime' _inherit = ['website.qweb.field', 'ir.qweb.field.datetime'] + def attributes(self, cr, uid, field_name, record, options, + source_element, g_att, t_att, qweb_context, + context=None): + column = record._model._all_columns[field_name].column + value = record[field_name] + if isinstance(value, basestring): + value = datetime.datetime.strptime( + value, DEFAULT_SERVER_DATETIME_FORMAT) + value = column.context_timestamp( + cr, uid, timestamp=value, context=context) + + attrs = super(DateTime, self).attributes( + cr, uid, field_name, record, options, source_element, g_att, t_att, + qweb_context, context=None) + return itertools.chain(attrs, [ + ('data-oe-original', value.strftime(openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT)) + ]) + def from_html(self, cr, uid, model, column, element, context=None): - lang = self.user_lang(cr, uid, context=context) - in_format = (u"%s %s" % (lang.date_format, lang.time_format)).encode('utf-8') - value = element.text_content().strip() - try: - dt = datetime.datetime.strptime(in_format, value) - except ValueError: - dt = parse_fuzzy(in_format, value) + if not value: return False - return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + datetime.datetime.strptime(value, DEFAULT_SERVER_DATETIME_FORMAT) + return value class Text(orm.AbstractModel): _name = 'website.qweb.field.text' @@ -302,3 +318,34 @@ class Monetary(orm.AbstractModel): return float(value.replace(lang.thousands_sep, '') .replace(lang.decimal_point, '.')) + +class Duration(orm.AbstractModel): + _name = 'website.qweb.field.duration' + _inherit = [ + 'ir.qweb.field.duration', + 'website.qweb.field.float', + ] + + def attributes(self, cr, uid, field_name, record, options, + source_element, g_att, t_att, qweb_context, + context=None): + attrs = super(Duration, self).attributes( + cr, uid, field_name, record, options, source_element, g_att, t_att, + qweb_context, context=None) + return itertools.chain(attrs, [('data-oe-original', record[field_name])]) + + def from_html(self, cr, uid, model, column, element, context=None): + value = element.text_content().strip() + + # non-localized value + return float(value) + + +class RelativeDatetime(orm.AbstractModel): + _name = 'website.qweb.field.relative' + _inherit = [ + 'ir.qweb.field.relative', + 'website.qweb.field.datetime', + ] + + # get formatting from ir.qweb.field.relative but edition/save from datetime diff --git a/addons/website/models/website.py b/addons/website/models/website.py index df04d56e999..b1df178e8ed 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -80,6 +80,23 @@ def urlplus(url, params): def quote_plus(value): return urllib.quote_plus(value.encode('utf-8') if isinstance(value, unicode) else str(value)) +def preload_records(*args, **kwargs): + """ This helper allows to check the existence and prefetch one or many browse_records at once. + If the browse record(s) does not exists in the db it will raise a LazyResponse + """ + field = kwargs.pop('field', 'name') + on_error = kwargs.pop('on_error', 'website.404') + error_code = kwargs.pop('error_code', 404) + try: + for arg in args: + if isinstance(arg, orm.browse_record): + arg[field] + elif isinstance(arg, orm.browse_record_list): + [record[field] for record in arg] + except: + lazy_error = request.website.render(on_error, status_code=error_code) + raise werkzeug.exceptions.HTTPException(response=lazy_error) + class website(osv.osv): def _get_menu_website(self, cr, uid, ids, context=None): # IF a menu is changed, update all websites diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css index 487306be5dc..eaf1fdbe96b 100644 --- a/addons/website/static/src/css/editor.css +++ b/addons/website/static/src/css/editor.css @@ -206,10 +206,6 @@ ul.oe_menu_editor .disclose { position: static !important; } -.cke_widget_drag_handler_container { - display: none !important; -} - .cke_widget_editable:empty:after { content: " "; white-space: pre-wrap; diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass index 7de48fdc611..885b1c9372c 100644 --- a/addons/website/static/src/css/editor.sass +++ b/addons/website/static/src/css/editor.sass @@ -168,9 +168,6 @@ ul.oe_menu_editor // Breaks completely horribly crazy products listing page, so take it out. .cke_widget_wrapper position: static !important -// "remove" drag & drop of CKE widgets -.cke_widget_drag_handler_container - display: none !important // prevent inline widgets from entirely disappearing when their (textual) // content is removed .cke_widget_editable:empty:after diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index aa3d7adac6c..f88a876d82f 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -323,14 +323,26 @@ init: function (editor) { editor.widgets.add('oeref', { editables: { text: '*' }, + draggable: false, upcast: function (el) { - return el.attributes['data-oe-type'] - && el.attributes['data-oe-type'] !== 'monetary'; + var matches = el.attributes['data-oe-type'] && el.attributes['data-oe-type'] !== 'monetary'; + if (!matches) { return false; } + + if (el.attributes['data-oe-original']) { + while (el.children.length) { + el.children[0].remove(); + } + el.add(new CKEDITOR.htmlParser.text( + el.attributes['data-oe-original'] + )); + } + return true; }, }); editor.widgets.add('monetary', { editables: { text: 'span.oe_currency_value' }, + draggable: false, upcast: function (el) { return el.attributes['data-oe-type'] === 'monetary'; diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 490304cca1c..ac9a10b9019 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -404,6 +404,74 @@ + + + + diff --git a/addons/website_blog/controllers/main.py b/addons/website_blog/controllers/main.py index 29f680f8183..0e53809ae77 100644 --- a/addons/website_blog/controllers/main.py +++ b/addons/website_blog/controllers/main.py @@ -97,6 +97,7 @@ class WebsiteBlog(http.Controller): """ BYPAGE = 10 + website.preload_records(category, tag) cr, uid, context = request.cr, request.uid, request.context blog_post_obj = request.registry['blog.post'] @@ -178,6 +179,8 @@ class WebsiteBlog(http.Controller): - 'nav_list': a dict [year][month] for archives navigation """ + website.preload_records(blog_post) + pager_url = "/blogpost/%s" % blog_post.id pager = request.website.pager( diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml index a8f0d2570cb..662ed4fd52f 100644 --- a/addons/website_blog/views/website_blog_templates.xml +++ b/addons/website_blog/views/website_blog_templates.xml @@ -1,7 +1,7 @@ -