diff --git a/addons/payment/__openerp__.py b/addons/payment/__openerp__.py index d5409ca301f..a9b40aa8534 100644 --- a/addons/payment/__openerp__.py +++ b/addons/payment/__openerp__.py @@ -8,13 +8,13 @@ 'description': """Payment Acquirer Base Module""", 'depends': ['account'], 'data': [ - 'data/payment_acquirer.xml', - 'data/account.xml', - 'views/payment_acquirer.xml', - 'views/assets.xml', - 'views/res_config_view.xml', - 'views/res_partner_view.xml', - 'views/account_payment.xml', + 'data/account_data.xml', + 'data/payment_acquirer_data.xml', + 'views/payment_views.xml', + 'views/account_config_settings_views.xml', + 'views/account_payment_views.xml', + 'views/payment_templates.xml', + 'views/res_partner_views.xml', 'security/ir.model.access.csv', 'security/payment_security.xml', ], diff --git a/addons/payment/data/account.xml b/addons/payment/data/account_data.xml similarity index 100% rename from addons/payment/data/account.xml rename to addons/payment/data/account_data.xml diff --git a/addons/payment/data/payment_acquirer.xml b/addons/payment/data/payment_acquirer_data.xml similarity index 100% rename from addons/payment/data/payment_acquirer.xml rename to addons/payment/data/payment_acquirer_data.xml diff --git a/addons/payment/models/account_payment.py b/addons/payment/models/account_payment.py index 2b825b922e4..c2449129215 100644 --- a/addons/payment/models/account_payment.py +++ b/addons/payment/models/account_payment.py @@ -1,14 +1,21 @@ # coding: utf-8 import datetime + from odoo import _, api, fields, models from odoo.exceptions import ValidationError + class AccountPayment(models.Model): _inherit = 'account.payment' + payment_transaction_id = fields.Many2one('payment.transaction', string="Payment Transaction") + payment_token_id = fields.Many2one('payment.token', string="Saved payment token") + payment_type = fields.Selection(selection_add=[('electronic', 'Electronically receive money')]) + payment_method_id_code = fields.Char(related='payment_method_id.code') + @api.onchange('partner_id') - def onchange_partner_id(self): + def _onchange_partner_id(self): res = {} if self.partner_id: res['domain'] = {'payment_token_id': [('partner_id', '=', self.partner_id.id)]} @@ -16,22 +23,23 @@ class AccountPayment(models.Model): return res @api.onchange('payment_method_id', 'journal_id') - def _clear_electronic_payment_token(self): + def _onchange_payment_method(self): if self.payment_method_id.code == 'electronic': self.payment_token_id = self.env['payment.token'].search([('partner_id', '=', self.partner_id.id)], limit=1) else: self.payment_token_id = False - payment_transaction_id = fields.Many2one('payment.transaction', string="Payment Transaction") - payment_token_id = fields.Many2one('payment.token', string="Saved payment token") - payment_type = fields.Selection(selection_add=[('electronic', 'Electronically receive money')]) - payment_method_id_code = fields.Char(related='payment_method_id.code') + @api.model + def create(self, vals): + account_payment = super(AccountPayment, self).create(vals) + + if account_payment.payment_token_id: + account_payment._do_payment() + return account_payment def _do_payment(self): - tx_obj = self.env['payment.transaction'] reference = "PAYMENT-%s-%s" % (self.id, datetime.datetime.now().strftime('%y%m%d_%H%M%S')) - - tx_values = { + tx = self.env['payment.transaction'].create({ 'amount': self.amount, 'acquirer_id': self.payment_token_id.acquirer_id.id, 'type': 'server2server', @@ -40,9 +48,7 @@ class AccountPayment(models.Model): 'payment_token_id': self.payment_token_id.id, 'partner_id': self.partner_id.id, 'partner_country_id': self.partner_id.country_id.id, - } - - tx = tx_obj.create(tx_values) + }) s2s_result = tx.s2s_do_transaction() @@ -50,12 +56,3 @@ class AccountPayment(models.Model): raise ValidationError(_("Payment transaction failed (%s)") % tx.state_message) self.payment_transaction_id = tx - - @api.model - def create(self, vals): - res = super(AccountPayment, self).create(vals) - - if res.payment_token_id: - res._do_payment() - - return res diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index a2ea8f69f65..f614bf3306f 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -1,10 +1,9 @@ # coding: utf-8 import logging -import openerp -from openerp.osv import osv, fields -from openerp.tools import float_round, float_repr, image_resize_images -from openerp.tools.translate import _ +from odoo import api, exceptions, fields, models, _ +from odoo.tools import float_round, image_resize_images +from odoo.addons.base.module import module _logger = logging.getLogger(__name__) @@ -22,7 +21,7 @@ class ValidationError(ValueError): pass -class PaymentAcquirer(osv.Model): +class PaymentAcquirer(models.Model): """ Acquirer Model. Each specific acquirer can extend the model by adding 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 @@ -34,15 +33,15 @@ class PaymentAcquirer(osv.Model): Methods that should be added in an acquirer-specific implementation: - - ``_form_generate_values(self, cr, uid, id, reference, amount, currency, - partner_id=False, partner_values=None, tx_custom_values=None, context=None)``: + - ``_form_generate_values(self, reference, amount, currency, + partner_id=False, partner_values=None, tx_custom_values=None)``: 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 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). + - ``_get_form_action_url(self):``: method that returns the url of + the button form. It is used for example in ecommerce application if you + want to post some data to the acquirer. + - ``_compute_fees(self, amount, currency_id, country_id)``: computes + 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 @@ -53,89 +52,93 @@ class PaymentAcquirer(osv.Model): _description = 'Payment Acquirer' _order = 'sequence' - def _get_providers(self, cr, uid, context=None): - return [('manual', 'Manual Configuration')] + name = fields.Char('Name', required=True, translate=True) + description = fields.Html('Description') + sequence = fields.Integer('Sequence', help="Determine the display order") + provider = fields.Selection( + selection=[('manual', 'Manual Configuration')], string='Provider', + default='manual', required=True) + company_id = fields.Many2one( + 'res.company', 'Company', + default=lambda self: self.env.user.company_id.id, required=True) + view_template_id = fields.Many2one( + 'ir.ui.view', 'Form Button Template', required=True) + registration_view_template_id = fields.Many2one( + 'ir.ui.view', 'S2S Form Template', domain=[('type', '=', 'qweb')], + help="Template for method registration") + environment = fields.Selection([ + ('test', 'Test'), + ('prod', 'Production')], string='Environment', + default='test', oldname='env', required=True) + website_published = fields.Boolean( + 'Visible in Portal / Website', copy=False, + help="Make this payment acquirer available (Customer invoices, etc.)") + auto_confirm = fields.Selection([ + ('none', 'No automatic confirmation'), + ('confirm_so', 'Confirm the SO on acquirer confirmation'), + ('generate_and_pay_invoice', 'On acquirer confirmation, confirm the SO, generate the invoice and pay it')], + string='Order Confirmation', default='confirm_so', required=True) + journal_id = fields.Many2one( + 'account.journal', 'Accounting Journal', + help="Account journal used for automatic payment reconciliation.") - def _compute_fees_implemented(self, cr, uid, ids, field_names, arg, context=None): - acquirer_model = self.pool['payment.acquirer'] - res = {} + pre_msg = fields.Html( + 'Help Message', translate=True, + help='Message displayed to explain and help the payment process.') + post_msg = fields.Html( + 'Thanks Message', translate=True, + help='Message displayed after having done the payment process.') + pending_msg = fields.Html( + 'Pending Message', translate=True, + default='Pending, Your online payment has been successfully processed. But your order is not validated yet.', + help='Message displayed, if order is in pending state after having done the payment process.') + done_msg = fields.Html( + 'Done Message', translate=True, + default='Done, Your online payment has been successfully processed. Thank you for your order.', + help='Message displayed, if order is done successfully after having done the payment process.') + cancel_msg = fields.Html( + 'Cancel Message', translate=True, + default='Cancel, Your payment has been cancelled.', + help='Message displayed, if order is cancel during the payment process.') + error_msg = fields.Html( + 'Error Message', translate=True, + default='Error, Please be aware that an error occurred during the transaction. The order has been confirmed but will not be paid. Do not hesitate to contact us if you have any questions on the status of your order.', + help='Message displayed, if error is occur during the payment process.') - for acquirer in acquirer_model.browse(cr, uid, ids, context=context): - custom_method_name = '%s_compute_fees' % acquirer.provider - res[acquirer.id] = hasattr(acquirer_model, custom_method_name) + fees_implemented = fields.Boolean('Fees Computation Supported', compute='_compute_fees_implemented') + fees_active = fields.Boolean('Add Extra Fees') + fees_dom_fixed = fields.Float('Fixed domestic fees') + fees_dom_var = fields.Float('Variable domestic fees (in percents)') + fees_int_fixed = fields.Float('Fixed international fees') + fees_int_var = fields.Float('Variable international fees (in percents)') - return res + # TDE FIXME: remove that brol + module_id = fields.Many2one('ir.module.module', string='Corresponding Module') + module_state = fields.Selection(selection=module.STATES, string='Installation State', related='module_id.state') - # indirection to ease inheritance - _provider_selection = lambda self, *args, **kwargs: self._get_providers(*args, **kwargs) - - _columns = { - 'name': fields.char('Name', required=True, translate=True), - 'provider': fields.selection(_provider_selection, string='Provider', required=True, default='manual'), - 'company_id': fields.many2one('res.company', 'Company', required=True), - 'pre_msg': fields.html('Help Message', translate=True, - help='Message displayed to explain and help the payment process.'), - 'post_msg': fields.html('Thanks Message', translate=True, - help='Message displayed after having done the payment process.'), - 'view_template_id': fields.many2one('ir.ui.view', 'Form Button Template', required=True), - 'registration_view_template_id': fields.many2one('ir.ui.view', 'S2S Form Template', - domain=[('type', '=', 'qweb')], - help="Template for method registration"), - 'environment': fields.selection( - [('test', 'Test'), ('prod', 'Production')], - string='Environment', required=True, oldname='env'), - 'website_published': fields.boolean( - 'Visible in Portal / Website', copy=False, - help="Make this payment acquirer available (Customer invoices, etc.)"), - 'auto_confirm': fields.selection( - [('none', 'No automatic confirmation'), - ('confirm_so', 'Confirm the SO on acquirer confirmation'), - ('generate_and_pay_invoice', 'On acquirer confirmation, confirm the SO, generate the invoice and pay it')], - string='Order Confirmation', required=True), - 'pending_msg': fields.html('Pending Message', translate=True, help='Message displayed, if order is in pending state after having done the payment process.'), - 'done_msg': fields.html('Done Message', translate=True, help='Message displayed, if order is done successfully after having done the payment process.'), - 'cancel_msg': fields.html('Cancel Message', translate=True, help='Message displayed, if order is cancel during the payment process.'), - 'error_msg': fields.html('Error Message', translate=True, help='Message displayed, if error is occur during the payment process.'), - # Fees - 'fees_implemented': fields.function(_compute_fees_implemented, type="boolean"), - 'fees_active': fields.boolean('Add Extra Fees'), - 'fees_dom_fixed': fields.float('Fixed domestic fees'), - 'fees_dom_var': fields.float('Variable domestic fees (in percents)'), - 'fees_int_fixed': fields.float('Fixed international fees'), - 'fees_int_var': fields.float('Variable international fees (in percents)'), - 'sequence': fields.integer('Sequence', help="Determine the display order"), - 'module_id': fields.many2one('ir.module.module', string='Corresponding Module'), - 'module_state': fields.related('module_id', 'state', type='char', string='Installation State'), - 'description': fields.html('Description'), - 'journal_id': fields.many2one('account.journal', 'Accounting Journal', help="Account journal used for automatic payment reconciliation."), - } - - image = openerp.fields.Binary("Image", attachment=True, + image = fields.Binary( + "Image", attachment=True, help="This field holds the image used for this provider, limited to 1024x1024px") - image_medium = openerp.fields.Binary("Medium-sized image", attachment=True, - help="Medium-sized image of this provider. It is automatically "\ - "resized as a 128x128px image, with aspect ratio preserved. "\ + image_medium = fields.Binary( + "Medium-sized image", attachment=True, + help="Medium-sized image of this provider. It is automatically " + "resized as a 128x128px image, with aspect ratio preserved. " "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized image", attachment=True, - help="Small-sized image of this provider. It is automatically "\ - "resized as a 64x64px image, with aspect ratio preserved. "\ + image_small = fields.Binary( + "Small-sized image", attachment=True, + help="Small-sized image of this provider. It is automatically " + "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") - _defaults = { - 'company_id': lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(cr, uid, uid).company_id.id, - 'environment': 'test', - 'website_published': False, - 'auto_confirm': 'confirm_so', - 'pending_msg': 'Pending, Your online payment has been successfully processed. But your order is not validated yet.', - 'done_msg': 'Done, Your online payment has been successfully processed. Thank you for your order.', - 'cancel_msg': 'Cancel, Your payment has been cancelled.', - 'error_msg': "Error, Please be aware that an error occurred during the transaction. The order has been confirmed but won't be paid. Don't hesitate to contact us if you have any questions on the status of your order." - } + @api.one + def _compute_fees_implemented(self): + self.fees_implemented = hasattr(self, '%s_compute_fees' % self.provider) - def _check_required_if_provider(self, cr, uid, ids, context=None): + @api.multi + def _check_required_if_provider(self): """ If the field has 'required_if_provider=""' attribute, then it required if record.provider is . """ - for acquirer in self.browse(cr, uid, ids, context=context): + for acquirer in self: if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()): return False return True @@ -144,24 +147,25 @@ class PaymentAcquirer(osv.Model): (_check_required_if_provider, 'Required fields not filled', []), ] - @openerp.api.model + @api.model def create(self, vals): image_resize_images(vals) return super(PaymentAcquirer, self).create(vals) - @openerp.api.multi + @api.multi def write(self, vals): image_resize_images(vals) return super(PaymentAcquirer, self).write(vals) - def get_form_action_url(self, cr, uid, id, context=None): + @api.multi + def get_form_action_url(self): """ Returns the form action URL, for form-based acquirer implementations. """ - acquirer = self.browse(cr, uid, id, context=context) - if hasattr(self, '%s_get_form_action_url' % acquirer.provider): - return getattr(self, '%s_get_form_action_url' % acquirer.provider)(cr, uid, id, context=context) + if hasattr(self, '%s_get_form_action_url' % self.provider): + return getattr(self, '%s_get_form_action_url' % self.provider)() return False - def render(self, cr, uid, id, reference, amount, currency_id, partner_id=False, values=None, context=None): + @api.multi + def render(self, reference, amount, currency_id, partner_id=False, values=None): """ Renders the form template of the given acquirer as a qWeb template. :param string reference: the transaction reference :param float amount: the amount the buyer has to pay @@ -169,7 +173,6 @@ class PaymentAcquirer(osv.Model): :param dict partner_id: optional partner_id to fill values :param dict values: a dictionary of values for the transction that is given to the acquirer-specific method generating the form values - :param dict context: OpenERP context All templates will receive: @@ -184,14 +187,11 @@ class PaymentAcquirer(osv.Model): - 'return_url': URL for coming back after payment validation (wihout base url) -> FIXME - 'cancel_url': URL if the client cancels the payment -> FIXME - 'error_url': URL if there is an issue with the payment -> FIXME - - context: OpenERP context dictionary + - context: Odoo context """ - if context is None: - context = {} if values is None: values = {} - acquirer = self.browse(cr, uid, id, context=context) # reference and amount values.setdefault('reference', reference) @@ -201,18 +201,18 @@ class PaymentAcquirer(osv.Model): # currency id currency_id = values.setdefault('currency_id', currency_id) if currency_id: - currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context) + currency = self.env['res.currency'].browse(currency_id) else: - currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id + currency = self.env.user.company_id.currency_id values['currency'] = currency - # Fill partner_* using values['partner_id'] or partner_id arguement + # Fill partner_* using values['partner_id'] or partner_id argument partner_id = values.get('partner_id', partner_id) billing_partner_id = values.get('billing_partner_id', partner_id) if partner_id: - partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) + partner = self.env['res.partner'].browse(partner_id) if partner_id != billing_partner_id: - billing_partner = self.pool['res.partner'].browse(cr, uid, billing_partner_id, context=context) + billing_partner = self.env['res.partner'].browse(billing_partner_id) else: billing_partner = partner values.update({ @@ -256,90 +256,89 @@ class PaymentAcquirer(osv.Model): if not values.get('partner_address'): values['address'] = _partner_format_address(values.get('partner_street', ''), values.get('partner_street2', '')) if not values.get('partner_country') and values.get('partner_country_id'): - values['country'] = self.pool['res.country'].browse(cr, uid, values.get('partner_country_id'), context=context) + values['country'] = self.env['res.country'].browse(values.get('partner_country_id')) if not values.get('billing_partner_address'): values['billing_address'] = _partner_format_address(values.get('billing_partner_street', ''), values.get('billing_partner_street2', '')) if not values.get('billing_partner_country') and values.get('billing_partner_country_id'): - values['billing_country'] = self.pool['res.country'].browse(cr, uid, values.get('billing_partner_country_id'), context=context) - + values['billing_country'] = self.env['res.country'].browse(values.get('billing_partner_country_id')) # compute fees - fees_method_name = '%s_compute_fees' % acquirer.provider + fees_method_name = '%s_compute_fees' % self.provider if hasattr(self, fees_method_name): - fees = getattr(self, fees_method_name)(cr, uid, id, values['amount'], values['currency_id'], values.get('partner_country_id'), context=None) + fees = getattr(self, fees_method_name)(values['amount'], values['currency_id'], values.get('partner_country_id')) values['fees'] = float_round(fees, 2) # call _form_generate_values to update the tx dict with acqurier specific values - cust_method_name = '%s_form_generate_values' % (acquirer.provider) + cust_method_name = '%s_form_generate_values' % (self.provider) if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) - values = method(cr, uid, id, values, context=context) + values = method(values) values.update({ - '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), - 'context': context, + 'tx_url': self._context.get('tx_url', self.get_form_action_url()), + 'submit_class': self._context.get('submit_class', 'btn btn-link'), + 'submit_txt': self._context.get('submit_txt'), + 'acquirer': self, + 'user': self.env.user, + 'context': self._context, 'type': values.get('type') or 'form', }) values.setdefault('return_url', False) - return acquirer.view_template_id.render(values, engine='ir.qweb') + return self.view_template_id.render(values, engine='ir.qweb') - def _registration_render(self, cr, uid, id, partner_id, qweb_context=None, context=None): - acquirer = self.browse(cr, uid, id, context=context) + @api.multi + def _registration_render(self, partner_id, qweb_context=None): if qweb_context is None: qweb_context = {} - qweb_context.update(id=id, partner_id=partner_id) - method_name = '_%s_registration_form_generate_values' % (acquirer.provider,) + qweb_context.update(id=self.ids[0], partner_id=partner_id) + method_name = '_%s_registration_form_generate_values' % (self.provider,) if hasattr(self, method_name): method = getattr(self, method_name) - qweb_context.update(method(cr, uid, id, qweb_context, context=context)) - return acquirer.registration_view_template_id.render(qweb_context, engine='ir.qweb') + qweb_context.update(method(qweb_context)) + return self.registration_view_template_id.render(qweb_context, engine='ir.qweb') - def s2s_process(self, cr, uid, id, data, context=None): - acquirer = self.browse(cr, uid, id, context=context) - cust_method_name = '%s_s2s_form_process' % (acquirer.provider) - if not self.s2s_validate(cr, uid, id, data, context=context): + @api.multi + def s2s_process(self, data): + cust_method_name = '%s_s2s_form_process' % (self.provider) + if not self.s2s_validate(data): return False if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) - return method(cr, uid, data, context=context) + return method(data) return True - def s2s_validate(self, cr, uid, id, data, context=None): - acquirer = self.browse(cr, uid, id, context=context) - cust_method_name = '%s_s2s_form_validate' % (acquirer.provider) + @api.multi + def s2s_validate(self, data): + cust_method_name = '%s_s2s_form_validate' % (self.provider) if hasattr(self, cust_method_name): method = getattr(self, cust_method_name) - return method(cr, uid, id, data, context=context) + return method(data) return True - def toggle_enviroment_value(self, cr, uid, ids, context=None): - acquirers = self.browse(cr, uid, ids, context=context) - prod_ids = [acquirer.id for acquirer in acquirers if acquirer.environment == 'prod'] - test_ids = [acquirer.id for acquirer in acquirers if acquirer.environment == 'test'] - self.write(cr, uid, prod_ids, {'environment': 'test'}, context=context) - self.write(cr, uid, test_ids, {'environment': 'prod'}, context=context) + @api.multi + def toggle_enviroment_value(self): + prod = self.filtered(lambda acquirer: acquirer.environment == 'prod') + prod.write({'environment': 'test'}) + (self-prod).write({'environment': 'prod'}) - def button_immediate_install(self, cr, uid, ids, context=None): - acquirer_id = self.browse(cr, uid, ids, context=context) - if acquirer_id.module_id and acquirer_id.module_state != 'installed': - acquirer_id.module_id.button_immediate_install() - context['active_id'] = ids[0] + @api.multi + def button_immediate_install(self): + # TDE FIXME: remove that brol + if self.module_id and self.module_state != 'installed': + self.module_id.button_immediate_install() + context = dict(self._context, active_id=self.ids[0]) return { 'view_type': 'form', 'view_mode': 'form', 'res_model': 'payment.acquirer', 'type': 'ir.actions.act_window', - 'res_id': ids[0], + 'res_id': self.ids[0], 'context': context, } -class PaymentTransaction(osv.Model): +class PaymentTransaction(models.Model): """ Transaction Model. Each specific acquirer can extend the model by adding its own fields. @@ -360,139 +359,69 @@ class PaymentTransaction(osv.Model): _order = 'id desc' _rec_name = 'reference' - def _lang_get(self, cr, uid, context=None): - return self.pool['res.lang'].get_installed(cr, uid, context=context) + @api.model + def _lang_get(self): + return self.env['res.lang'].get_installed() - def _default_partner_country_id(self, cr, uid, context=None): - comp = self.pool['res.company'].browse(cr, uid, context.get('company_id', 1), context=context) - return comp.country_id.id + @api.model + def _get_default_partner_country_id(self): + return self.env['res.company']._company_default_get('payment.transaction').country_id.id - _columns = { - 'create_date': fields.datetime('Creation Date', readonly=True), - 'date_validate': fields.datetime('Validation Date'), - 'acquirer_id': fields.many2one( - 'payment.acquirer', 'Acquirer', - required=True, - ), - 'type': fields.selection( - [('server2server', 'Server To Server'), ('form', 'Form'), ('form_save', 'Form with credentials storage')], - string='Type', required=True), - 'state': fields.selection( - [('draft', 'Draft'), ('pending', 'Pending'), - ('done', 'Done'), ('error', 'Error'), - ('cancel', 'Canceled') - ], 'Status', required=True, - track_visibility='onchange', copy=False), - 'state_message': fields.text('Message', - help='Field used to store error and/or validation messages for information'), - # payment - 'amount': fields.float('Amount', required=True, - digits=(16, 2), - track_visibility='always', - help='Amount'), - '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('Reference', required=True, help='Internal reference of the TX'), - 'acquirer_reference': fields.char('Acquirer 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', track_visibility='onchange',), - 'partner_name': fields.char('Partner Name'), - 'partner_lang': fields.selection(_lang_get, 'Language'), - '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', required=True), - 'partner_phone': fields.char('Phone'), - 'html_3ds': fields.char('3D Secure HTML'), + create_date = fields.Datetime('Creation Date', readonly=True) + date_validate = fields.Datetime('Validation Date') + acquirer_id = fields.Many2one('payment.acquirer', 'Acquirer', required=True) + type = fields.Selection([ + ('server2server', 'Server To Server'), + ('form', 'Form'), + ('form_save', 'Form with credentials storage')], 'Type', + default='form', required=True) + state = fields.Selection([ + ('draft', 'Draft'), + ('pending', 'Pending'), + ('done', 'Done'), + ('error', 'Error'), + ('cancel', 'Canceled')], 'Status', + copy=False, default='draft', required=True, track_visibility='onchange') + state_message = fields.Text('Message', help='Field used to store error and/or validation messages for information') + # payment + amount = fields.Float( + 'Amount', digits=(16, 2), required=True, track_visibility='always', + help='Amount') + 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( + 'Reference', default=lambda self: self.env['ir.sequence'].next_by_code('payment.transaction'), + required=True, help='Internal reference of the TX') + acquirer_reference = fields.Char('Acquirer 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', track_visibility='onchange') + partner_name = fields.Char('Partner Name') + partner_lang = fields.Selection(_lang_get, 'Language', default='en_US') + 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', default=_get_default_partner_country_id, required=True) + partner_phone = fields.Char('Phone') + html_3ds = fields.Char('3D Secure HTML') - 'callback_eval': fields.char('S2S Callback', help="""\ - Will be safe_eval with `self` being the current transaction. i.e.: - self.env['my.model'].payment_validated(self)""", oldname="s2s_cb_eval"), - 'payment_token_id': fields.many2one('payment.token', 'Payment Token', domain="[('acquirer_id', '=', acquirer_id)]"), - } + callback_eval = fields.Char('S2S Callback', help="""\ + Will be safe_eval with `self` being the current transaction. i.e.: + self.env['my.model'].payment_validated(self)""", oldname="s2s_cb_eval") + payment_token_id = fields.Many2one('payment.token', 'Payment Token', domain="[('acquirer_id', '=', acquirer_id)]") - def _check_reference(self, cr, uid, ids, context=None): - transaction = self.browse(cr, uid, ids[0], context=context) - if transaction.state not in ['cancel', 'error']: - if self.search(cr, uid, [('reference', '=', transaction.reference), ('id', '!=', transaction.id)], context=context, count=True): - return False - return True + @api.onchange('partner_id') + def _onchange_partner_id(self): + onchange_vals = self.on_change_partner_id(self.partner_id.id).get('value', {}) + self.write(onchange_vals) - _constraints = [ - (_check_reference, 'The payment transaction reference must be unique!', ['reference', 'state']), - ] - - _defaults = { - 'type': 'form', - 'state': 'draft', - 'partner_lang': 'en_US', - 'partner_country_id': _default_partner_country_id, - 'reference': lambda s, c, u, ctx=None: s.pool['ir.sequence'].next_by_code(c, u, 'payment.transaction', context=ctx), - } - - def create(self, cr, uid, values, context=None): - Acquirer = self.pool['payment.acquirer'] - - if values.get('partner_id'): # @TDENOTE: not sure - values.update(self.on_change_partner_id(cr, uid, None, values.get('partner_id'), context=context)['value']) - - # call custom create method if defined (i.e. ogone_create for ogone) - if values.get('acquirer_id'): - acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context) - - # compute fees - custom_method_name = '%s_compute_fees' % acquirer.provider - if hasattr(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('partner_country_id'), context=None) - values['fees'] = float_round(fees, 2) - - # custom create - custom_method_name = '%s_create' % acquirer.provider - if hasattr(self, custom_method_name): - values.update(getattr(self, custom_method_name)(cr, uid, values, context=context)) - - # Default value of reference is - tx_id = super(PaymentTransaction, self).create(cr, uid, values, context=context) - if not values.get('reference'): - self.write(cr, uid, [tx_id], {'reference': str(tx_id)}, context=context) - return tx_id - - def write(self, cr, uid, ids, values, context=None): - Acquirer = self.pool['payment.acquirer'] - if ('acquirer_id' in values or 'amount' in values) and 'fees' not in values: - # The acquirer or the amount has changed, and the fees are not explicitely forced. Fees must be recomputed. - if isinstance(ids, (int, long)): - ids = [ids] - for txn_id in ids: - vals = dict(values) - vals['fees'] = 0.0 - transaction = self.browse(cr, uid, txn_id, context=context) - if 'acquirer_id' in values: - acquirer = Acquirer.browse(cr, uid, values['acquirer_id'], context=context) if values['acquirer_id'] else None - else: - acquirer = transaction.acquirer_id - if acquirer: - custom_method_name = '%s_compute_fees' % acquirer.provider - if hasattr(Acquirer, custom_method_name): - amount = (values['amount'] if 'amount' in values else transaction.amount) or 0.0 - currency_id = values.get('currency_id') or transaction.currency_id.id - country_id = values.get('partner_country_id') or transaction.partner_country_id.id - fees = getattr(Acquirer, custom_method_name)(cr, uid, acquirer.id, amount, currency_id, country_id, context=None) - vals['fees'] = float_round(fees, 2) - res = super(PaymentTransaction, self).write(cr, uid, txn_id, vals, context=context) - return res - return super(PaymentTransaction, self).write(cr, uid, ids, values, context=context) - - def on_change_partner_id(self, cr, uid, ids, partner_id, context=None): + @api.multi + def on_change_partner_id(self, partner_id): partner = None if partner_id: - partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context) + partner = self.env['res.partner'].browse(partner_id) return {'value': { 'partner_name': partner and partner.name or False, 'partner_lang': partner and partner.lang or 'en_US', @@ -500,15 +429,73 @@ class PaymentTransaction(osv.Model): '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 self._default_partner_country_id(cr, uid, context=context), + 'partner_country_id': partner and partner.country_id.id or self._default_partner_country_id(), 'partner_phone': partner and partner.phone or False, }} return {} - def get_next_reference(self, cr, uid, reference, context=None): + @api.constrains('reference', 'state') + def _check_reference(self): + for transaction in self.filtered(lambda tx: tx.state not in ('cancel', 'error')): + if self.search_count([('reference', '=', transaction.reference)]) != 1: + raise exceptions.ValidationError(_('The payment transaction reference must be unique!')) + return True + + @api.model + def create(self, values): + if values.get('partner_id'): # @TDENOTE: not sure + values.update(self.on_change_partner_id(values['partner_id'])['value']) + + # call custom create method if defined (i.e. ogone_create for ogone) + if values.get('acquirer_id'): + acquirer = self.env['payment.acquirer'].browse(values['acquirer_id']) + + # compute fees + custom_method_name = '%s_compute_fees' % acquirer.provider + if hasattr(acquirer, custom_method_name): + fees = getattr(acquirer, custom_method_name)( + values.get('amount', 0.0), values.get('currency_id'), values.get('partner_country_id')) + values['fees'] = float_round(fees, 2) + + # custom create + custom_method_name = '%s_create' % acquirer.provider + if hasattr(acquirer, custom_method_name): + values.update(getattr(self, custom_method_name)(values)) + + # Default value of reference is + tx = super(PaymentTransaction, self).create(values) + if not values.get('reference'): + tx.write({'reference': str(tx.id)}) + return tx + + @api.multi + def write(self, values): + if ('acquirer_id' in values or 'amount' in values) and 'fees' not in values: + # The acquirer or the amount has changed, and the fees are not explicitly forced. Fees must be recomputed. + acquirer = None + if values.get('acquirer_id'): + acquirer = self.env['payment.acquirer'].browse(values['acquirer_id']) + for tx in self: + vals = dict(values, fees=0.0) + if not acquirer: + acquirer = tx.acquirer_id + custom_method_name = '%s_compute_fees' % acquirer.provider + # TDE FIXME: shouldn't we use fee_implemented ? + if hasattr(acquirer, custom_method_name): + fees = getattr(acquirer, custom_method_name)( + (values['amount'] if 'amount' in values else tx.amount) or 0.0, + values.get('currency_id') or tx.currency_id.id, + values.get('partner_country_id') or tx.partner_country_id.id) + vals['fees'] = float_round(fees, 2) + res = super(PaymentTransaction, tx).write(vals) + return res + return super(PaymentTransaction, self).write(values) + + @api.model + def get_next_reference(self, reference): ref_suffix = 1 init_ref = reference - while self.pool['payment.transaction'].search_count(cr, openerp.SUPERUSER_ID, [('reference', '=', reference)], context=context): + while self.env['payment.transaction'].sudo().search_count([('reference', '=', reference)]): reference = init_ref + '-' + str(ref_suffix) ref_suffix += 1 return reference @@ -517,37 +504,39 @@ class PaymentTransaction(osv.Model): # FORM RELATED METHODS # -------------------------------------------------- - def render(self, cr, uid, id, context=None): - tx = self.browse(cr, uid, id, context=context) + @api.multi + def render(self): values = { - 'reference': tx.reference, - 'amount': tx.amount, - 'currency_id': tx.currency_id.id, - 'currency': tx.currency_id, - 'partner': tx.partner_id, - 'partner_name': tx.partner_name, - 'partner_lang': tx.partner_lang, - 'partner_email': tx.partner_email, - 'partner_zip': tx.partner_zip, - 'partner_address': tx.partner_address, - 'partner_city': tx.partner_city, - 'partner_country_id': tx.partner_country_id.id, - 'partner_country': tx.partner_country_id, - 'partner_phone': tx.partner_phone, + 'reference': self.reference, + 'amount': self.amount, + 'currency_id': self.currency_id.id, + 'currency': self.currency_id, + 'partner': self.partner_id, + 'partner_name': self.partner_name, + 'partner_lang': self.partner_lang, + 'partner_email': self.partner_email, + 'partner_zip': self.partner_zip, + 'partner_address': self.partner_address, + 'partner_city': self.partner_city, + 'partner_country_id': self.partner_country_id.id, + 'partner_country': self.partner_country_id, + 'partner_phone': self.partner_phone, 'partner_state': None, } - return tx.acquirer_id.render(None, None, None, values=values) + return self.acquirer_id.render(None, None, None, values=values) - def form_feedback(self, cr, uid, data, acquirer_name, context=None): + @api.model + def form_feedback(self, data, acquirer_name): invalid_parameters, tx = None, None tx_find_method_name = '_%s_form_get_tx_from_data' % acquirer_name if hasattr(self, tx_find_method_name): - tx = getattr(self, tx_find_method_name)(cr, uid, data, context=context) + tx = getattr(self, tx_find_method_name)(data) + # TDE TODO: form_get_invalid_parameters from model to multi invalid_param_method_name = '_%s_form_get_invalid_parameters' % acquirer_name if hasattr(self, invalid_param_method_name): - invalid_parameters = getattr(self, invalid_param_method_name)(cr, uid, tx, data, context=context) + invalid_parameters = getattr(tx, invalid_param_method_name)(data) if invalid_parameters: _error_message = '%s: incorrect tx data:\n' % (acquirer_name) @@ -556,62 +545,53 @@ class PaymentTransaction(osv.Model): _logger.error(_error_message) return False + # TDE TODO: form_validate from model to multi feedback_method_name = '_%s_form_validate' % acquirer_name if hasattr(self, feedback_method_name): - return getattr(self, feedback_method_name)(cr, uid, tx, data, context=context) + return getattr(tx, feedback_method_name)(data) return True # -------------------------------------------------- # SERVER2SERVER RELATED METHODS # -------------------------------------------------- - def s2s_create(self, cr, uid, values, cc_values, context=None): - tx_id, tx_result = self.s2s_send(cr, uid, values, cc_values, context=context) - self.s2s_feedback(cr, uid, tx_id, tx_result, context=context) - return tx_id - def s2s_do_transaction(self, cr, uid, id, context=None, **kwargs): - tx = self.browse(cr, uid, id, context=context) - custom_method_name = '%s_s2s_do_transaction' % tx.acquirer_id.provider + @api.multi + def s2s_do_transaction(self, **kwargs): + custom_method_name = '%s_s2s_do_transaction' % self.acquirer_id.provider if hasattr(self, custom_method_name): - return getattr(self, custom_method_name)(cr, uid, id, context=context, **kwargs) + return getattr(self, custom_method_name)(**kwargs) - def s2s_get_tx_status(self, cr, uid, tx_id, context=None): + @api.multi + def s2s_get_tx_status(self): """ Get the tx status. """ - tx = self.browse(cr, uid, tx_id, context=context) - - invalid_param_method_name = '_%s_s2s_get_tx_status' % tx.acquirer_id.provider + invalid_param_method_name = '_%s_s2s_get_tx_status' % self.acquirer_id.provider if hasattr(self, invalid_param_method_name): - return getattr(self, invalid_param_method_name)(cr, uid, tx, context=context) + return getattr(self, invalid_param_method_name)() return True -class PaymentToken(osv.Model): +class PaymentToken(models.Model): _name = 'payment.token' _order = 'partner_id' - _columns = { - 'name': fields.char('Name', help='Name of the payment token'), - 'partner_id': fields.many2one('res.partner', 'Partner', required=True), - 'acquirer_id': fields.many2one('payment.acquirer', 'Acquirer Account', required=True), - 'acquirer_ref': fields.char('Acquirer Ref.', required=True), - 'active': fields.boolean('Active'), - 'payment_ids': fields.one2many('payment.transaction', 'payment_token_id', 'Payment Transactions'), - } + name = fields.Char('Name', help='Name of the payment token') + partner_id = fields.Many2one('res.partner', 'Partner', required=True) + acquirer_id = fields.Many2one('payment.acquirer', 'Acquirer Account', required=True) + acquirer_ref = fields.Char('Acquirer Ref.', required=True) + active = fields.Boolean('Active', default=True) + payment_ids = fields.One2many('payment.transaction', 'payment_token_id', 'Payment Transactions') - _defaults = { - 'active': True - } - - def create(self, cr, uid, values, context=None): + @api.model + def create(self, values): # call custom create method if defined (i.e. ogone_create for ogone) if values.get('acquirer_id'): - acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context) + acquirer = self.env['payment.acquirer'].browse(values['acquirer_id']) # custom create custom_method_name = '%s_create' % acquirer.provider if hasattr(self, custom_method_name): - values.update(getattr(self, custom_method_name)(cr, uid, values, context=context)) + values.update(getattr(self, custom_method_name)(values)) - return super(PaymentToken, self).create(cr, uid, values, context=context) + return super(PaymentToken, self).create(values) diff --git a/addons/payment/models/res_partner.py b/addons/payment/models/res_partner.py index 801415b3bcf..ef97997c267 100644 --- a/addons/payment/models/res_partner.py +++ b/addons/payment/models/res_partner.py @@ -1,20 +1,20 @@ -from openerp.osv import fields, osv +# coding: utf-8 + +from odoo import api, fields, models -class res_partner(osv.osv): +class res_partner(models.Model): _name = 'res.partner' _inherit = 'res.partner' - def _compute_payment_token_count(self, cr, uid, ids, field_names, arg, context=None): - result = {} - payment_data = self.pool['payment.token'].read_group( - cr, uid, [('partner_id', 'in', ids)], ['partner_id'], ['partner_id'], context=context) - mapped_data = dict([(payment['partner_id'][0], payment['partner_id_count']) for payment in payment_data]) - for partner in self.browse(cr, uid, ids, context=context): - result[partner.id] = mapped_data.get(partner.id, 0) - return result + payment_token_ids = fields.One2many('payment.token', 'partner_id', 'Payment Tokens') + payment_token_count = fields.Integer( + 'Count Payment Token', compute='_compute_payment_token_count') - _columns = { - 'payment_token_ids': fields.one2many('payment.token', 'partner_id', 'Payment Tokens'), - 'payment_token_count': fields.function(_compute_payment_token_count, string='Count Payment Token', type="integer"), - } + @api.depends('payment_token_ids') + def _compute_payment_token_count(self): + payment_data = self.env['payment.token'].read_group([ + ('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id']) + mapped_data = dict([(payment['partner_id'][0], payment['partner_id_count']) for payment in payment_data]) + for partner in self: + partner.payment_token_count = mapped_data.get(partner.id, 0) diff --git a/addons/payment/security/payment_security.xml b/addons/payment/security/payment_security.xml index 4c1f3403f2a..c5ad3f62fe7 100644 --- a/addons/payment/security/payment_security.xml +++ b/addons/payment/security/payment_security.xml @@ -1,5 +1,5 @@ - + @@ -20,4 +20,4 @@ - + diff --git a/addons/payment/tests/common.py b/addons/payment/tests/common.py index e90bc8d47d2..4caa6206dc8 100644 --- a/addons/payment/tests/common.py +++ b/addons/payment/tests/common.py @@ -1,23 +1,16 @@ # -*- coding: utf-8 -*- -from openerp.tests import common +from odoo.tests import common class PaymentAcquirerCommon(common.TransactionCase): def setUp(self): super(PaymentAcquirerCommon, self).setUp() - self.payment_acquirer = self.registry('payment.acquirer') - self.payment_transaction = self.registry('payment.transaction') - self.currency_euro_id = self.registry('res.currency').search( - 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) - 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] + self.currency_euro = self.env['res.currency'].search([('name', '=', 'EUR')], limit=1) + self.country_belgium = self.env['res.country'].search([('code', 'like', 'BE')], limit=1) + self.country_france = self.env['res.country'].search([('code', 'like', 'FR')], limit=1) # dict partner values self.buyer_values = { @@ -28,8 +21,8 @@ class PaymentAcquirerCommon(common.TransactionCase): 'partner_phone': '0032 12 34 56 78', 'partner_city': 'Sin City', 'partner_zip': '1000', - 'partner_country': self.env['res.country'].browse(self.country_belgium_id), - 'partner_country_id': self.country_belgium_id, + 'partner_country': self.env['res.country'].browse(self.country_belgium.id), + 'partner_country_id': self.country_belgium.id, 'partner_country_name': 'Belgium', 'billing_partner_name': 'Norbert Buyer', 'billing_partner_lang': 'en_US', @@ -38,22 +31,20 @@ class PaymentAcquirerCommon(common.TransactionCase): 'billing_partner_phone': '0032 12 34 56 78', 'billing_partner_city': 'Sin City', 'billing_partner_zip': '1000', - 'billing_partner_country': self.env['res.country'].browse(self.country_belgium_id), - 'billing_partner_country_id': self.country_belgium_id, + 'billing_partner_country': self.env['res.country'].browse(self.country_belgium.id), + 'billing_partner_country_id': self.country_belgium.id, 'billing_partner_country_name': 'Belgium', } # test partner - self.buyer_id = self.registry('res.partner').create( - self.cr, self.uid, { - 'name': 'Norbert Buyer', - 'lang': 'en_US', - 'email': 'norbert.buyer@example.com', - 'street': 'Huge Street', - 'street2': '2/543', - 'phone': '0032 12 34 56 78', - 'city': 'Sin City', - 'zip': '1000', - 'country_id': self.country_belgium_id, - } - ) + self.buyer = self.env['res.partner'].create({ + 'name': 'Norbert Buyer', + 'lang': 'en_US', + 'email': 'norbert.buyer@example.com', + 'street': 'Huge Street', + 'street2': '2/543', + 'phone': '0032 12 34 56 78', + 'city': 'Sin City', + 'zip': '1000', + 'country_id': self.country_belgium.id}) + self.buyer_id = self.buyer.id diff --git a/addons/payment/views/res_config_view.xml b/addons/payment/views/account_config_settings_views.xml similarity index 97% rename from addons/payment/views/res_config_view.xml rename to addons/payment/views/account_config_settings_views.xml index 084238e72af..f636c732f60 100644 --- a/addons/payment/views/res_config_view.xml +++ b/addons/payment/views/account_config_settings_views.xml @@ -1,5 +1,5 @@ - + @@ -15,4 +15,4 @@ - + diff --git a/addons/payment/views/account_payment.xml b/addons/payment/views/account_payment_views.xml similarity index 100% rename from addons/payment/views/account_payment.xml rename to addons/payment/views/account_payment_views.xml diff --git a/addons/payment/views/assets.xml b/addons/payment/views/payment_templates.xml similarity index 100% rename from addons/payment/views/assets.xml rename to addons/payment/views/payment_templates.xml diff --git a/addons/payment/views/payment_acquirer.xml b/addons/payment/views/payment_views.xml similarity index 99% rename from addons/payment/views/payment_acquirer.xml rename to addons/payment/views/payment_views.xml index 3f295ffb31a..65077de10bd 100644 --- a/addons/payment/views/payment_acquirer.xml +++ b/addons/payment/views/payment_views.xml @@ -1,5 +1,5 @@ - + @@ -368,4 +368,4 @@ groups='base.group_no_one'/> - + diff --git a/addons/payment/views/res_partner_view.xml b/addons/payment/views/res_partner_views.xml similarity index 97% rename from addons/payment/views/res_partner_view.xml rename to addons/payment/views/res_partner_views.xml index b0145045222..4708fec0c5e 100644 --- a/addons/payment/views/res_partner_view.xml +++ b/addons/payment/views/res_partner_views.xml @@ -1,5 +1,5 @@ - + @@ -18,4 +18,4 @@ - + diff --git a/addons/payment_adyen/__openerp__.py b/addons/payment_adyen/__openerp__.py index 331ce8d124d..1e26a8808ca 100644 --- a/addons/payment_adyen/__openerp__.py +++ b/addons/payment_adyen/__openerp__.py @@ -8,9 +8,9 @@ 'description': """Adyen Payment Acquirer""", 'depends': ['payment'], 'data': [ - 'views/adyen.xml', - 'views/payment_acquirer.xml', - 'data/adyen.xml', + 'views/payment_views.xml', + 'views/payment_adyen_templates.xml', + 'data/payment_acquirer_data.xml', ], 'installable': True, } diff --git a/addons/payment_adyen/controllers/main.py b/addons/payment_adyen/controllers/main.py index de05f72d66b..b6b4331e578 100644 --- a/addons/payment_adyen/controllers/main.py +++ b/addons/payment_adyen/controllers/main.py @@ -5,8 +5,8 @@ import logging import pprint import werkzeug -from openerp import http, SUPERUSER_ID -from openerp.http import request +from odoo import http +from odoo.http import request _logger = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class AdyenController(http.Controller): def adyen_return(self, **post): _logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug if post.get('authResult') not in ['CANCELLED']: - request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'adyen', context=request.context) + request.env['payment.transaction'].sudo().form_feedback(post, 'adyen') return_url = post.pop('return_url', '') if not return_url: custom = json.loads(post.pop('merchantReturnData', '{}')) @@ -31,9 +31,8 @@ class AdyenController(http.Controller): '/payment/adyen/notification', ], type='http', auth='none', methods=['POST'], csrf=False) def adyen_notification(self, **post): - tx_id = post.get('merchantReference') and request.registry['payment.transaction'].search(request.cr, SUPERUSER_ID, [('reference', 'in', [post.get('merchantReference')])], limit=1, context=request.context) - if post.get('eventCode') in ['AUTHORISATION'] and tx_id: - tx = request.registry['payment.transaction'].browse(request.cr, SUPERUSER_ID, tx_id, context=request.context) + tx = post.get('merchantReference') and request.env['payment.transaction'].sudo().search([('reference', 'in', [post.get('merchantReference')])], limit=1) + if post.get('eventCode') in ['AUTHORISATION'] and tx: states = (post.get('merchantReference'), post.get('success'), tx.state) if (post.get('success') == 'true' and tx.state == 'done') or (post.get('success') == 'false' and tx.state in ['cancel', 'error']): _logger.info('Notification from Adyen for the reference %s: received %s, state is %s', states) diff --git a/addons/payment_adyen/data/adyen.xml b/addons/payment_adyen/data/payment_acquirer_data.xml similarity index 97% rename from addons/payment_adyen/data/adyen.xml rename to addons/payment_adyen/data/payment_acquirer_data.xml index dbbc9bfd9bb..3d7ef2a1778 100644 --- a/addons/payment_adyen/data/adyen.xml +++ b/addons/payment_adyen/data/payment_acquirer_data.xml @@ -1,5 +1,5 @@ - + @@ -17,4 +17,4 @@ - + diff --git a/addons/payment_adyen/models/__init__.py b/addons/payment_adyen/models/__init__.py index fdc540ef04d..10b988e0c28 100644 --- a/addons/payment_adyen/models/__init__.py +++ b/addons/payment_adyen/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -import adyen +import payment diff --git a/addons/payment_adyen/models/adyen.py b/addons/payment_adyen/models/payment.py similarity index 65% rename from addons/payment_adyen/models/adyen.py rename to addons/payment_adyen/models/payment.py index f9f17126a13..edf19953bab 100644 --- a/addons/payment_adyen/models/adyen.py +++ b/addons/payment_adyen/models/payment.py @@ -9,46 +9,35 @@ import hmac import logging import urlparse -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment_adyen.controllers.main import AdyenController -from openerp.osv import osv, fields -from openerp.tools import float_round -from openerp.tools.translate import _ +from odoo import api, fields, models, tools, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment_adyen.controllers.main import AdyenController _logger = logging.getLogger(__name__) -class AcquirerAdyen(osv.Model): +class AcquirerAdyen(models.Model): _inherit = 'payment.acquirer' - def _get_adyen_urls(self, cr, uid, environment, context=None): - """ Adyen URLs + provider = fields.Selection(selection_add=[('adyen', 'Adyen')]) + 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') - - yhpp: hosted payment page: pay.shtml for single, select.shtml for multiple - """ + @api.model + def _get_adyen_urls(self, environment): + """ Adyen URLs: yhpp: hosted payment page: pay.shtml for single, select.shtml for multiple """ return { 'adyen_form_url': 'https://%s.adyen.com/hpp/pay.shtml' % ('live' if environment == 'prod' else environment), } - def _get_providers(self, cr, uid, context=None): - providers = super(AcquirerAdyen, self)._get_providers(cr, uid, context=context) - providers.append(['adyen', 'Adyen']) - return providers - - _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'), - } - - def _adyen_generate_merchant_sig_sha256(self, acquirer, inout, values): + @api.multi + def _adyen_generate_merchant_sig_sha256(self, inout, values): """ Generate the shasign for incoming or outgoing communications., when using the SHA-256 signature. - :param browse acquirer: the payment.acquirer browse record. It should - have a shakey in shaky out - :param string inout: 'in' (openerp contacting ogone) or 'out' (adyen - contacting openerp). In this last case only some + :param string inout: 'in' (odoo contacting ogone) or 'out' (adyen + contacting odoo). In this last case only some fields should be contained (see e-Commerce basic) :param dict values: transaction values :return string: shasign @@ -62,7 +51,7 @@ class AcquirerAdyen(osv.Model): return base64.b64encode(hm.digest()) assert inout in ('in', 'out') - assert acquirer.provider == 'adyen' + assert self.provider == 'adyen' if inout == 'in': # All the fields sent to Adyen must be included in the signature. ALL the fucking @@ -83,27 +72,26 @@ class AcquirerAdyen(osv.Model): 'pspReference', 'shopperLocale', 'skinCode', ] - hmac_key = binascii.a2b_hex(acquirer.adyen_skin_hmac_key.encode('ascii')) + hmac_key = binascii.a2b_hex(self.adyen_skin_hmac_key.encode('ascii')) raw_values = {k: values.get(k.encode('ascii'), '') for k in keys if k in values} raw_values_ordered = OrderedDict(sorted(raw_values.items(), key=lambda t: t[0])) return signParams(raw_values_ordered) - def _adyen_generate_merchant_sig(self, acquirer, inout, values): + @api.multi + def _adyen_generate_merchant_sig(self, inout, values): """ Generate the shasign for incoming or outgoing communications, when using the SHA-1 signature (deprecated by Adyen). - :param browse acquirer: the payment.acquirer browse record. It should - have a shakey in shaky out - :param string inout: 'in' (openerp contacting ogone) or 'out' (adyen - contacting openerp). In this last case only some + :param string inout: 'in' (odoo contacting ogone) or 'out' (adyen + contacting odoo). In this last case only some fields should be contained (see e-Commerce basic) :param dict values: transaction values :return string: shasign """ assert inout in ('in', 'out') - assert acquirer.provider == 'adyen' + assert self.provider == 'adyen' if inout == 'in': keys = "paymentAmount currencyCode shipBeforeDate merchantReference skinCode merchantAccount sessionValidity shopperEmail shopperReference recurringContract allowedMethods blockedMethods shopperStatement merchantReturnData billingAddressType deliveryAddressType offset".split() @@ -116,66 +104,67 @@ class AcquirerAdyen(osv.Model): return '' sign = ''.join('%s' % get_value(k) for k in keys).encode('ascii') - key = acquirer.adyen_skin_hmac_key.encode('ascii') + key = self.adyen_skin_hmac_key.encode('ascii') return base64.b64encode(hmac.new(key, sign, hashlib.sha1).digest()) - def adyen_form_generate_values(self, cr, uid, id, 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) + @api.multi + def adyen_form_generate_values(self, values): + base_url = self.env['ir.config_parameter'].get_param('web.base.url') # tmp import datetime from dateutil import relativedelta - if acquirer.provider == 'adyen' and len(acquirer.adyen_skin_hmac_key) == 64: + if self.provider == 'adyen' and len(self.adyen_skin_hmac_key) == 64: tmp_date = datetime.datetime.today() + relativedelta.relativedelta(days=1) values.update({ 'merchantReference': values['reference'], - 'paymentAmount': '%d' % int(float_round(values['amount'], 2) * 100), + 'paymentAmount': '%d' % int(tools.float_round(values['amount'], 2) * 100), 'currencyCode': values['currency'] and values['currency'].name or '', 'shipBeforeDate': tmp_date.strftime('%Y-%m-%d'), - 'skinCode': acquirer.adyen_skin_code, - 'merchantAccount': acquirer.adyen_merchant_account, + 'skinCode': self.adyen_skin_code, + 'merchantAccount': self.adyen_merchant_account, 'shopperLocale': values.get('partner_lang', ''), 'sessionValidity': tmp_date.isoformat('T')[:19] + "Z", 'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url), 'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url', '') else False, 'shopperEmail': values.get('partner_email', ''), }) - values['merchantSig'] = self._adyen_generate_merchant_sig_sha256(acquirer, 'in', values) + values['merchantSig'] = self._adyen_generate_merchant_sig_sha256('in', values) else: tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1) values.update({ 'merchantReference': values['reference'], - 'paymentAmount': '%d' % int(float_round(values['amount'], 2) * 100), + 'paymentAmount': '%d' % int(tools.float_round(values['amount'], 2) * 100), 'currencyCode': values['currency'] and values['currency'].name or '', 'shipBeforeDate': tmp_date, - 'skinCode': acquirer.adyen_skin_code, - 'merchantAccount': acquirer.adyen_merchant_account, + 'skinCode': self.adyen_skin_code, + 'merchantAccount': self.adyen_merchant_account, 'shopperLocale': values.get('partner_lang'), 'sessionValidity': tmp_date, 'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url), 'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url') else False, - 'merchantSig': self._adyen_generate_merchant_sig(acquirer, 'in', values), + 'merchantSig': self._adyen_generate_merchant_sig('in', values), }) return values - def adyen_get_form_action_url(self, cr, uid, id, context=None): - acquirer = self.browse(cr, uid, id, context=context) - return self._get_adyen_urls(cr, uid, acquirer.environment, context=context)['adyen_form_url'] + @api.multi + def adyen_get_form_action_url(self): + return self._get_adyen_urls(self.environment)['adyen_form_url'] -class TxAdyen(osv.Model): +class TxAdyen(models.Model): _inherit = 'payment.transaction' # -------------------------------------------------- # FORM RELATED METHODS # -------------------------------------------------- - def _adyen_form_get_tx_from_data(self, cr, uid, data, context=None): + @api.model + def _adyen_form_get_tx_from_data(self, data): reference, pspReference = data.get('merchantReference'), data.get('pspReference') if not reference or not pspReference: error_msg = _('Adyen: received data with missing reference (%s) or missing pspReference (%s)') % (reference, pspReference) @@ -183,22 +172,21 @@ class TxAdyen(osv.Model): raise ValidationError(error_msg) # find tx -> @TDENOTE use pspReference ? - tx_ids = self.pool['payment.transaction'].search(cr, uid, [('reference', '=', reference)], context=context) - if not tx_ids or len(tx_ids) > 1: + tx = self.env['payment.transaction'].search([('reference', '=', reference)]) + if not tx or len(tx) > 1: error_msg = _('Adyen: received data for reference %s') % (reference) - if not tx_ids: + if not tx: error_msg += _('; no order found') else: error_msg += _('; multiple order found') _logger.info(error_msg) raise ValidationError(error_msg) - tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context) # verify shasign if len(tx.acquirer_id.adyen_skin_hmac_key) == 64: - shasign_check = self.pool['payment.acquirer']._adyen_generate_merchant_sig_sha256(tx.acquirer_id, 'out', data) + shasign_check = tx.acquirer_id._adyen_generate_merchant_sig_sha256('out', data) else: - shasign_check = self.pool['payment.acquirer']._adyen_generate_merchant_sig(tx.acquirer_id, 'out', data) + shasign_check = tx.acquirer_id._adyen_generate_merchant_sig('out', data) if shasign_check != data.get('merchantSig'): error_msg = _('Adyen: invalid merchantSig, received %s, computed %s') % (data.get('merchantSig'), shasign_check) _logger.warning(error_msg) @@ -206,25 +194,25 @@ class TxAdyen(osv.Model): return tx - def _adyen_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): + def _adyen_form_get_invalid_parameters(self, data): 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)) + if self.acquirer_reference and data.get('pspReference') != self.acquirer_reference: + invalid_parameters.append(('pspReference', data.get('pspReference'), self.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)) + if data.get('skinCode') != self.acquirer_id.adyen_skin_code: + invalid_parameters.append(('skinCode', data.get('skinCode'), self.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): + def _adyen_form_validate(self, data): status = data.get('authResult', 'PENDING') if status == 'AUTHORISED': - tx.write({ + self.write({ 'state': 'done', 'acquirer_reference': data.get('pspReference'), # 'date_validate': data.get('payment_date', fields.datetime.now()), @@ -232,7 +220,7 @@ class TxAdyen(osv.Model): }) return True elif status == 'PENDING': - tx.write({ + self.write({ 'state': 'pending', 'acquirer_reference': data.get('pspReference'), }) @@ -240,7 +228,7 @@ class TxAdyen(osv.Model): else: error = _('Adyen: feedback error') _logger.info(error) - tx.write({ + self.write({ 'state': 'error', 'state_message': error }) diff --git a/addons/payment_adyen/tests/test_adyen.py b/addons/payment_adyen/tests/test_adyen.py index 710bdf4e9e0..2015df6922e 100644 --- a/addons/payment_adyen/tests/test_adyen.py +++ b/addons/payment_adyen/tests/test_adyen.py @@ -3,21 +3,14 @@ from lxml import objectify import urlparse -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment.tests.common import PaymentAcquirerCommon -from openerp.addons.payment_adyen.controllers.main import AdyenController -from openerp.tools import mute_logger +from odoo.addons.payment.tests.common import PaymentAcquirerCommon +from odoo.addons.payment_adyen.controllers.main import AdyenController 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') - - # get the adyen account - model, self.adyen_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment', 'payment_acquirer_adyen') # some CC (always use expiration date 06 / 2016, cvc 737, cid 7373 (amex)) self.amex = (('370000000000002', '7373')) @@ -36,40 +29,32 @@ class AdyenCommon(PaymentAcquirerCommon): self.mistercash = (('6703444444444449', None, 'user', 'password')) -class AdyenServer2Server(AdyenCommon): - - def test_00_tx_management(self): - cr, uid, context = self.cr, self.uid, {} - - 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) + adyen = self.env.ref('payment.payment_acquirer_adyen') self.assertEqual(adyen.environment, 'test', 'test without test environment') # ---------------------------------------- # Test: button direct rendering # ---------------------------------------- + base_url = self.env['ir.config_parameter'].get_param('web.base.url') form_values = { 'merchantAccount': 'OpenERPCOM', 'merchantReference': 'test_ref0', 'skinCode': 'cbqYWvVL', 'paymentAmount': '1', 'currencyCode': 'EUR', - 'resURL': '%s' % urlparse.urljoin(self.base_url, AdyenController._return_url), + 'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url), } # render the button - res = self.payment_acquirer.render( - cr, uid, self.adyen_id, - 'test_ref0', 0.01, self.currency_euro_id, + res = adyen.render( + 'test_ref0', 0.01, self.currency_euro.id, partner_id=None, - partner_values=self.buyer_values, - context=context) + partner_values=self.buyer_values) # check form result tree = objectify.fromstring(res) @@ -82,19 +67,3 @@ class AdyenForm(AdyenCommon): 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_adyen.models.adyen', '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', -# 'merchantReturnData': u'return_url=/shop/payment/validate', -# 'merchantSig': u'GaLRO8aMHFaQX3gQ5BVP/YETzeA=', -# 'paymentMethod': u'visa', -# 'pspReference': u'8813859935907337', -# 'shopperLocale': u'en_US', -# 'skinCode': u'cbqYWvVL'} \ No newline at end of file diff --git a/addons/payment_adyen/views/adyen.xml b/addons/payment_adyen/views/payment_adyen_templates.xml similarity index 98% rename from addons/payment_adyen/views/adyen.xml rename to addons/payment_adyen/views/payment_adyen_templates.xml index ff92d21dce2..4b8d178ef69 100644 --- a/addons/payment_adyen/views/adyen.xml +++ b/addons/payment_adyen/views/payment_adyen_templates.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/payment_adyen/views/payment_acquirer.xml b/addons/payment_adyen/views/payment_views.xml similarity index 97% rename from addons/payment_adyen/views/payment_acquirer.xml rename to addons/payment_adyen/views/payment_views.xml index fde4880a9fa..6914a28ff35 100644 --- a/addons/payment_adyen/views/payment_acquirer.xml +++ b/addons/payment_adyen/views/payment_views.xml @@ -1,5 +1,5 @@ - + @@ -19,4 +19,4 @@ - + diff --git a/addons/payment_authorize/__openerp__.py b/addons/payment_authorize/__openerp__.py index 8670588ce2a..4a8eafa8b73 100644 --- a/addons/payment_authorize/__openerp__.py +++ b/addons/payment_authorize/__openerp__.py @@ -8,10 +8,9 @@ 'description': """Authorize.Net Payment Acquirer""", 'depends': ['payment'], 'data': [ - 'views/authorize.xml', - 'views/payment_acquirer.xml', - 'data/authorize.xml', - 'views/payment_authorize_template.xml', + 'views/payment_views.xml', + 'views/payment_authorize_templates.xml', + 'data/payment_acquirer_data.xml', ], 'installable': True, } diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py index 93b3d105f9a..aec2aa1f777 100644 --- a/addons/payment_authorize/controllers/main.py +++ b/addons/payment_authorize/controllers/main.py @@ -3,8 +3,8 @@ import pprint import logging import urlparse -from openerp import http -from openerp.http import request +from odoo import http +from odoo.http import request _logger = logging.getLogger(__name__) diff --git a/addons/payment_authorize/data/authorize.xml b/addons/payment_authorize/data/payment_acquirer_data.xml similarity index 100% rename from addons/payment_authorize/data/authorize.xml rename to addons/payment_authorize/data/payment_acquirer_data.xml diff --git a/addons/payment_authorize/models/__init__.py b/addons/payment_authorize/models/__init__.py index 92e55eeff24..10b988e0c28 100644 --- a/addons/payment_authorize/models/__init__.py +++ b/addons/payment_authorize/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -import authorize +import payment diff --git a/addons/payment_authorize/models/authorize.py b/addons/payment_authorize/models/payment.py similarity index 88% rename from addons/payment_authorize/models/authorize.py rename to addons/payment_authorize/models/payment.py index 828fd614ded..b7488937e61 100644 --- a/addons/payment_authorize/models/authorize.py +++ b/addons/payment_authorize/models/payment.py @@ -6,10 +6,10 @@ import logging import time import urlparse -from openerp import api, fields, models -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment_authorize.controllers.main import AuthorizeController -from openerp.tools.float_utils import float_compare +from odoo import api, fields, models +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment_authorize.controllers.main import AuthorizeController +from odoo.tools.float_utils import float_compare _logger = logging.getLogger(__name__) @@ -17,6 +17,10 @@ _logger = logging.getLogger(__name__) class PaymentAcquirerAuthorize(models.Model): _inherit = 'payment.acquirer' + provider = fields.Selection(selection_add=[('authorize', 'Authorize.Net')]) + authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize') + authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize') + def _get_authorize_urls(self, environment): """ Authorize URLs """ if environment == 'prod': @@ -24,15 +28,6 @@ class PaymentAcquirerAuthorize(models.Model): else: return {'authorize_form_url': 'https://test.authorize.net/gateway/transact.dll'} - @api.model - def _get_providers(self): - providers = super(PaymentAcquirerAuthorize, self)._get_providers() - providers.append(['authorize', 'Authorize.Net']) - return providers - - authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize') - authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize') - def _authorize_generate_hashing(self, values): data = '^'.join([ values['x_login'], @@ -122,37 +117,37 @@ class TxAuthorize(models.Model): raise ValidationError(error_msg) return tx[0] - @api.model - def _authorize_form_get_invalid_parameters(self, tx, data): + @api.multi + def _authorize_form_get_invalid_parameters(self, data): invalid_parameters = [] if self.acquirer_reference and data.get('x_trans_id') != self.acquirer_reference: invalid_parameters.append(('Transaction Id', data.get('x_trans_id'), self.acquirer_reference)) # check what is buyed - if float_compare(float(data.get('x_amount', '0.0')), tx.amount, 2) != 0: - invalid_parameters.append(('Amount', data.get('x_amount'), '%.2f' % tx.amount)) + if float_compare(float(data.get('x_amount', '0.0')), self.amount, 2) != 0: + invalid_parameters.append(('Amount', data.get('x_amount'), '%.2f' % self.amount)) return invalid_parameters - @api.model - def _authorize_form_validate(self, tx, data): - if tx.state == 'done': - _logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % tx.reference) + @api.multi + def _authorize_form_validate(self, data): + if self.state == 'done': + _logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % self.reference) return True status_code = int(data.get('x_response_code', '0')) if status_code == self._authorize_valid_tx_status: - tx.write({ + self.write({ 'state': 'done', 'acquirer_reference': data.get('x_trans_id'), }) return True elif status_code == self._authorize_pending_tx_status: - tx.write({ + self.write({ 'state': 'pending', 'acquirer_reference': data.get('x_trans_id'), }) return True elif status_code == self._authorize_cancel_tx_status: - tx.write({ + self.write({ 'state': 'cancel', 'acquirer_reference': data.get('x_trans_id'), }) @@ -160,7 +155,7 @@ class TxAuthorize(models.Model): else: error = data.get('x_response_reason_text') _logger.info(error) - tx.write({ + self.write({ 'state': 'error', 'state_message': error, 'acquirer_reference': data.get('x_trans_id'), diff --git a/addons/payment_authorize/tests/__init__.py b/addons/payment_authorize/tests/__init__.py index 32a44646439..10e174a320e 100644 --- a/addons/payment_authorize/tests/__init__.py +++ b/addons/payment_authorize/tests/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- -from openerp.addons.payment_authorize.tests import test_authorize - +from . import test_authorize diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py index fa477692f4d..464a794d481 100644 --- a/addons/payment_authorize/tests/test_authorize.py +++ b/addons/payment_authorize/tests/test_authorize.py @@ -6,28 +6,27 @@ import time import urlparse from lxml import objectify -import openerp -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment.tests.common import PaymentAcquirerCommon -from openerp.addons.payment_authorize.controllers.main import AuthorizeController -from openerp.tools import mute_logger +import odoo +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment.tests.common import PaymentAcquirerCommon +from odoo.addons.payment_authorize.controllers.main import AuthorizeController +from odoo.tools import mute_logger -@openerp.tests.common.at_install(True) -@openerp.tests.common.post_install(True) +@odoo.tests.common.at_install(True) +@odoo.tests.common.post_install(True) class AuthorizeCommon(PaymentAcquirerCommon): def setUp(self): super(AuthorizeCommon, self).setUp() - self.base_url = self.env['ir.config_parameter'].get_param('web.base.url') # authorize only support USD in test environment self.currency_usd = self.env['res.currency'].search([('name', '=', 'USD')], limit=1)[0] # get the authorize account - model, self.authorize_id = self.env['ir.model.data'].get_object_reference('payment', 'payment_acquirer_authorize') + self.authorize = self.env.ref('payment.payment_acquirer_authorize') -@openerp.tests.common.at_install(True) -@openerp.tests.common.post_install(True) +@odoo.tests.common.at_install(True) +@odoo.tests.common.post_install(True) class AuthorizeForm(AuthorizeCommon): def _authorize_generate_hashing(self, values): @@ -40,25 +39,25 @@ class AuthorizeForm(AuthorizeCommon): return hmac.new(str(values['x_trans_key']), data, hashlib.md5).hexdigest() def test_10_Authorize_form_render(self): - authorize = self.env['payment.acquirer'].browse(self.authorize_id) - self.assertEqual(authorize.environment, 'test', 'test without test environment') + self.assertEqual(self.authorize.environment, 'test', 'test without test environment') # ---------------------------------------- # Test: button direct rendering # ---------------------------------------- + base_url = self.env['ir.config_parameter'].get_param('web.base.url') form_values = { - 'x_login': authorize.authorize_login, - 'x_trans_key': authorize.authorize_transaction_key, + 'x_login': self.authorize.authorize_login, + 'x_trans_key': self.authorize.authorize_transaction_key, 'x_amount': '320.0', 'x_show_form': 'PAYMENT_FORM', 'x_type': 'AUTH_CAPTURE', 'x_method': 'CC', - 'x_fp_sequence': '%s%s' % (authorize.id, int(time.time())), + 'x_fp_sequence': '%s%s' % (self.authorize.id, int(time.time())), 'x_version': '3.1', 'x_relay_response': 'TRUE', 'x_fp_timestamp': str(int(time.time())), - 'x_relay_url': '%s' % urlparse.urljoin(self.base_url, AuthorizeController._return_url), - 'x_cancel_url': '%s' % urlparse.urljoin(self.base_url, AuthorizeController._cancel_url), + 'x_relay_url': '%s' % urlparse.urljoin(base_url, AuthorizeController._return_url), + 'x_cancel_url': '%s' % urlparse.urljoin(base_url, AuthorizeController._cancel_url), 'return_url': None, 'x_currency_code': 'USD', 'x_invoice_num': 'SO004', @@ -84,10 +83,7 @@ class AuthorizeForm(AuthorizeCommon): form_values['x_fp_hash'] = self._authorize_generate_hashing(form_values) # render the button - cr, uid, context = self.env.cr, self.env.uid, {} - res = self.payment_acquirer.render( - cr, uid, self.authorize_id, 'SO004', 320.0, self.currency_usd.id, - values=self.buyer_values, context=context) + res = self.authorize.render('SO004', 320.0, self.currency_usd.id, values=self.buyer_values) # check form result tree = objectify.fromstring(res) self.assertEqual(tree.get('action'), 'https://test.authorize.net/gateway/transact.dll', 'Authorize: wrong form POST url') @@ -101,12 +97,10 @@ class AuthorizeForm(AuthorizeCommon): 'Authorize: wrong value for input %s: received %s instead of %s' % (values[1], values[2], form_values[values[1]]) ) - @mute_logger('openerp.addons.payment_authorize.models.authorize', 'ValidationError') + @mute_logger('openerp.addons.payment_authorize.models.payment', 'ValidationError') def test_20_authorize_form_management(self): - cr, uid, context = self.env.cr, self.env.uid, {} # be sure not to do stupid thing - authorize = self.env['payment.acquirer'].browse(self.authorize_id) - self.assertEqual(authorize.environment, 'test', 'test without test environment') + self.assertEqual(self.authorize.environment, 'test', 'test without test environment') # typical data posted by authorize after client has successfully paid authorize_post_data = { @@ -157,17 +151,17 @@ class AuthorizeForm(AuthorizeCommon): # should raise error about unknown tx with self.assertRaises(ValidationError): - self.payment_transaction.form_feedback(cr, uid, authorize_post_data, 'authorize', context=context) + self.env['payment.transaction'].form_feedback(authorize_post_data, 'authorize') tx = self.env['payment.transaction'].create({ 'amount': 320.0, - 'acquirer_id': self.authorize_id, + 'acquirer_id': self.authorize.id, 'currency_id': self.currency_usd.id, 'reference': 'SO004', 'partner_name': 'Norbert Buyer', - 'partner_country_id': self.country_france_id}) + 'partner_country_id': self.country_france.id}) # validate it - self.payment_transaction.form_feedback(cr, uid, authorize_post_data, 'authorize', context=context) + self.env['payment.transaction'].form_feedback(authorize_post_data, 'authorize') # check state self.assertEqual(tx.state, 'done', 'Authorize: validation did not put tx into done state') self.assertEqual(tx.acquirer_reference, authorize_post_data.get('x_trans_id'), 'Authorize: validation did not update tx payid') @@ -177,6 +171,6 @@ class AuthorizeForm(AuthorizeCommon): # simulate an error authorize_post_data['x_response_code'] = u'3' - self.payment_transaction.form_feedback(cr, uid, authorize_post_data, 'authorize', context=context) + self.env['payment.transaction'].form_feedback(authorize_post_data, 'authorize') # check state self.assertEqual(tx.state, 'error', 'Authorize: erroneous validation did not put tx into error state') diff --git a/addons/payment_authorize/views/payment_authorize_template.xml b/addons/payment_authorize/views/payment_authorize_template.xml deleted file mode 100644 index 18b2ab7368f..00000000000 --- a/addons/payment_authorize/views/payment_authorize_template.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/addons/payment_authorize/views/authorize.xml b/addons/payment_authorize/views/payment_authorize_templates.xml similarity index 93% rename from addons/payment_authorize/views/authorize.xml rename to addons/payment_authorize/views/payment_authorize_templates.xml index 04767c36ec1..9c39fcd1c8e 100644 --- a/addons/payment_authorize/views/authorize.xml +++ b/addons/payment_authorize/views/payment_authorize_templates.xml @@ -1,7 +1,6 @@ - + diff --git a/addons/payment_authorize/views/payment_acquirer.xml b/addons/payment_authorize/views/payment_views.xml similarity index 100% rename from addons/payment_authorize/views/payment_acquirer.xml rename to addons/payment_authorize/views/payment_views.xml diff --git a/addons/payment_buckaroo/__openerp__.py b/addons/payment_buckaroo/__openerp__.py index 4fd749fe9e7..3e56a451c52 100644 --- a/addons/payment_buckaroo/__openerp__.py +++ b/addons/payment_buckaroo/__openerp__.py @@ -8,9 +8,9 @@ 'description': """Buckaroo Payment Acquirer""", 'depends': ['payment'], 'data': [ - 'views/buckaroo.xml', - 'views/payment_acquirer.xml', - 'data/buckaroo.xml', + 'views/payment_views.xml', + 'views/payment_buckaroo_templates.xml', + 'data/payment_acquirer_data.xml', ], 'installable': True, } diff --git a/addons/payment_buckaroo/controllers/main.py b/addons/payment_buckaroo/controllers/main.py index 515d8cf2d1e..e5e61f6e8f3 100644 --- a/addons/payment_buckaroo/controllers/main.py +++ b/addons/payment_buckaroo/controllers/main.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -import json + import logging import pprint import werkzeug -from openerp import http, SUPERUSER_ID -from openerp.http import request +from odoo import http +from odoo.http import request _logger = logging.getLogger(__name__) @@ -25,7 +25,7 @@ class BuckarooController(http.Controller): def buckaroo_return(self, **post): """ Buckaroo.""" _logger.info('Buckaroo: entering form_feedback with post data %s', pprint.pformat(post)) # debug - request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'buckaroo', context=request.context) + request.env['payment.transaction'].sudo().form_feedback(post, 'buckaroo') post = dict((key.upper(), value) for key, value in post.items()) return_url = post.get('ADD_RETURNDATA') or '/' return werkzeug.utils.redirect(return_url) diff --git a/addons/payment_buckaroo/data/buckaroo.xml b/addons/payment_buckaroo/data/payment_acquirer_data.xml similarity index 97% rename from addons/payment_buckaroo/data/buckaroo.xml rename to addons/payment_buckaroo/data/payment_acquirer_data.xml index 57e28818212..8019baf3593 100644 --- a/addons/payment_buckaroo/data/buckaroo.xml +++ b/addons/payment_buckaroo/data/payment_acquirer_data.xml @@ -1,5 +1,5 @@ - + @@ -16,4 +16,4 @@ - + diff --git a/addons/payment_buckaroo/models/__init__.py b/addons/payment_buckaroo/models/__init__.py index 7e1780a0059..10b988e0c28 100644 --- a/addons/payment_buckaroo/models/__init__.py +++ b/addons/payment_buckaroo/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -import buckaroo +import payment diff --git a/addons/payment_buckaroo/models/buckaroo.py b/addons/payment_buckaroo/models/payment.py similarity index 65% rename from addons/payment_buckaroo/models/buckaroo.py rename to addons/payment_buckaroo/models/payment.py index 80cfbc2ddfb..82b532c9e15 100644 --- a/addons/payment_buckaroo/models/buckaroo.py +++ b/addons/payment_buckaroo/models/payment.py @@ -4,11 +4,10 @@ import logging import urllib import urlparse -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment_buckaroo.controllers.main import BuckarooController -from openerp.osv import osv, fields -from openerp.tools.float_utils import float_compare -from openerp.tools.translate import _ +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment_buckaroo.controllers.main import BuckarooController +from odoo.tools.float_utils import float_compare _logger = logging.getLogger(__name__) @@ -23,10 +22,14 @@ def normalize_keys_upper(data): return dict((key.upper(), val) for key, val in data.items()) -class AcquirerBuckaroo(osv.Model): +class AcquirerBuckaroo(models.Model): _inherit = 'payment.acquirer' - def _get_buckaroo_urls(self, cr, uid, environment, context=None): + provider = fields.Selection(selection_add=[('buckaroo', 'Buckaroo')]) + brq_websitekey = fields.Char('WebsiteKey', required_if_provider='buckaroo') + brq_secretkey = fields.Char('SecretKey', required_if_provider='buckaroo') + + def _get_buckaroo_urls(self, environment): """ Buckaroo URLs """ if environment == 'prod': @@ -38,29 +41,19 @@ class AcquirerBuckaroo(osv.Model): 'buckaroo_form_url': 'https://testcheckout.buckaroo.nl/html/', } - def _get_providers(self, cr, uid, context=None): - providers = super(AcquirerBuckaroo, self)._get_providers(cr, uid, context=context) - providers.append(['buckaroo', 'Buckaroo']) - return providers - - _columns = { - 'brq_websitekey': fields.char('WebsiteKey', required_if_provider='buckaroo'), - 'brq_secretkey': fields.char('SecretKey', required_if_provider='buckaroo'), - } - - def _buckaroo_generate_digital_sign(self, acquirer, inout, values): + def _buckaroo_generate_digital_sign(self, 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' (openerp contacting buckaroo) or 'out' (buckaroo - contacting openerp). + :param string inout: 'in' (odoo contacting buckaroo) or 'out' (buckaroo + contacting odoo). :param dict values: transaction values :return string: shasign """ assert inout in ('in', 'out') - assert acquirer.provider == 'buckaroo' + assert self.provider == 'buckaroo' keys = "add_returndata Brq_amount Brq_culture Brq_currency Brq_invoicenumber Brq_return Brq_returncancel Brq_returnerror Brq_returnreject brq_test Brq_websitekey".split() @@ -81,26 +74,25 @@ class AcquirerBuckaroo(osv.Model): items = sorted(values.items(), key=lambda (x, y): x.lower()) sign = ''.join('%s=%s' % (k, urllib.unquote_plus(v)) for k, v in items) else: - sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys) - #Add the pre-shared secret key at the end of the signature - sign = sign + acquirer.brq_secretkey + sign = ''.join('%s=%s' % (k, get_value(k)) for k in keys) + # Add the pre-shared secret key at the end of the signature + sign = sign + self.brq_secretkey if isinstance(sign, str): # TODO: remove me? should not be used sign = urlparse.parse_qsl(sign) shasign = sha1(sign.encode('utf-8')).hexdigest() return shasign - - def buckaroo_form_generate_values(self, cr, uid, id, 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) + @api.multi + def buckaroo_form_generate_values(self, values): + base_url = self.env['ir.config_parameter'].get_param('web.base.url') buckaroo_tx_values = dict(values) buckaroo_tx_values.update({ - 'Brq_websitekey': acquirer.brq_websitekey, + 'Brq_websitekey': self.brq_websitekey, 'Brq_amount': values['amount'], 'Brq_currency': values['currency'] and values['currency'].name or '', 'Brq_invoicenumber': values['reference'], - 'brq_test': False if acquirer.environment == 'prod' else True, + 'brq_test': False if self.environment == 'prod' else True, 'Brq_return': '%s' % urlparse.urljoin(base_url, BuckarooController._return_url), 'Brq_returncancel': '%s' % urlparse.urljoin(base_url, BuckarooController._cancel_url), 'Brq_returnerror': '%s' % urlparse.urljoin(base_url, BuckarooController._exception_url), @@ -108,14 +100,15 @@ class AcquirerBuckaroo(osv.Model): 'Brq_culture': (values.get('partner_lang') or 'en_US').replace('_', '-'), 'add_returndata': buckaroo_tx_values.pop('return_url', '') or '', }) - buckaroo_tx_values['Brq_signature'] = self._buckaroo_generate_digital_sign(acquirer, 'in', buckaroo_tx_values) + buckaroo_tx_values['Brq_signature'] = self._buckaroo_generate_digital_sign('in', buckaroo_tx_values) return buckaroo_tx_values - def buckaroo_get_form_action_url(self, cr, uid, id, context=None): - acquirer = self.browse(cr, uid, id, context=context) - return self._get_buckaroo_urls(cr, uid, acquirer.environment, context=context)['buckaroo_form_url'] + @api.multi + def buckaroo_get_form_action_url(self): + return self._get_buckaroo_urls(self.environment)['buckaroo_form_url'] -class TxBuckaroo(osv.Model): + +class TxBuckaroo(models.Model): _inherit = 'payment.transaction' # buckaroo status @@ -125,12 +118,12 @@ class TxBuckaroo(osv.Model): _buckaroo_error_tx_status = [490, 491, 492] _buckaroo_reject_tx_status = [690] - # -------------------------------------------------- # FORM RELATED METHODS # -------------------------------------------------- - def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None): + @api.model + def _buckaroo_form_get_tx_from_data(self, data): """ Given a data dict coming from buckaroo, verify it and find the related transaction record. """ origin_data = dict(data) @@ -141,56 +134,55 @@ class TxBuckaroo(osv.Model): _logger.info(error_msg) raise ValidationError(error_msg) - tx_ids = self.search(cr, uid, [('reference', '=', reference)], context=context) - if not tx_ids or len(tx_ids) > 1: + tx = self.search([('reference', '=', reference)]) + if not tx or len(tx) > 1: error_msg = _('Buckaroo: received data for reference %s') % (reference) - if not tx_ids: + if not tx: error_msg += _('; no order found') else: error_msg += _('; multiple order found') _logger.info(error_msg) raise ValidationError(error_msg) - tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context) - #verify shasign - shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out', origin_data) + # verify shasign + shasign_check = tx.acquirer_id._buckaroo_generate_digital_sign('out', origin_data) if shasign_check.upper() != shasign.upper(): error_msg = _('Buckaroo: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data) _logger.info(error_msg) raise ValidationError(error_msg) - return tx + return tx - def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): + def _buckaroo_form_get_invalid_parameters(self, data): invalid_parameters = [] data = normalize_keys_upper(data) - if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference: - invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference)) + if self.acquirer_reference and data.get('BRQ_TRANSACTIONS') != self.acquirer_reference: + invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), self.acquirer_reference)) # check what is buyed - if float_compare(float(data.get('BRQ_AMOUNT', '0.0')), tx.amount, 2) != 0: - invalid_parameters.append(('Amount', data.get('BRQ_AMOUNT'), '%.2f' % tx.amount)) - if data.get('BRQ_CURRENCY') != tx.currency_id.name: - invalid_parameters.append(('Currency', data.get('BRQ_CURRENCY'), tx.currency_id.name)) + if float_compare(float(data.get('BRQ_AMOUNT', '0.0')), self.amount, 2) != 0: + invalid_parameters.append(('Amount', data.get('BRQ_AMOUNT'), '%.2f' % self.amount)) + if data.get('BRQ_CURRENCY') != self.currency_id.name: + invalid_parameters.append(('Currency', data.get('BRQ_CURRENCY'), self.currency_id.name)) return invalid_parameters - def _buckaroo_form_validate(self, cr, uid, tx, data, context=None): + def _buckaroo_form_validate(self, data): data = normalize_keys_upper(data) - status_code = int(data.get('BRQ_STATUSCODE','0')) + status_code = int(data.get('BRQ_STATUSCODE', '0')) if status_code in self._buckaroo_valid_tx_status: - tx.write({ + self.write({ 'state': 'done', 'acquirer_reference': data.get('BRQ_TRANSACTIONS'), }) return True elif status_code in self._buckaroo_pending_tx_status: - tx.write({ + self.write({ 'state': 'pending', 'acquirer_reference': data.get('BRQ_TRANSACTIONS'), }) return True elif status_code in self._buckaroo_cancel_tx_status: - tx.write({ + self.write({ 'state': 'cancel', 'acquirer_reference': data.get('BRQ_TRANSACTIONS'), }) @@ -198,7 +190,7 @@ class TxBuckaroo(osv.Model): else: error = 'Buckaroo: feedback error' _logger.info(error) - tx.write({ + self.write({ 'state': 'error', 'state_message': error, 'acquirer_reference': data.get('BRQ_TRANSACTIONS'), diff --git a/addons/payment_buckaroo/tests/test_buckaroo.py b/addons/payment_buckaroo/tests/test_buckaroo.py index 9f8c311b64a..447e6c62529 100644 --- a/addons/payment_buckaroo/tests/test_buckaroo.py +++ b/addons/payment_buckaroo/tests/test_buckaroo.py @@ -3,35 +3,31 @@ from lxml import objectify import urlparse -import openerp -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment.tests.common import PaymentAcquirerCommon -from openerp.addons.payment_buckaroo.controllers.main import BuckarooController -from openerp.tools import mute_logger +import odoo +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment.tests.common import PaymentAcquirerCommon +from odoo.addons.payment_buckaroo.controllers.main import BuckarooController +from odoo.tools import mute_logger -@openerp.tests.common.at_install(False) -@openerp.tests.common.post_install(False) +@odoo.tests.common.at_install(False) +@odoo.tests.common.post_install(False) class BuckarooCommon(PaymentAcquirerCommon): def setUp(self): super(BuckarooCommon, self).setUp() - cr, uid = self.cr, self.uid - self.base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url') - # get the buckaroo account - model, self.buckaroo_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment', 'payment_acquirer_buckaroo') + self.buckaroo = self.env.ref('payment.payment_acquirer_buckaroo') -@openerp.tests.common.at_install(False) -@openerp.tests.common.post_install(False) +@odoo.tests.common.at_install(False) +@odoo.tests.common.post_install(False) class BuckarooForm(BuckarooCommon): def test_10_Buckaroo_form_render(self): - cr, uid, context = self.cr, self.uid, {} + base_url = self.env['ir.config_parameter'].get_param('web.base.url') # be sure not to do stupid things - buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None) - self.assertEqual(buckaroo.environment, 'test', 'test without test environment') + self.assertEqual(self.buckaroo.environment, 'test', 'test without test environment') # ---------------------------------------- # Test: button direct rendering @@ -39,26 +35,24 @@ class BuckarooForm(BuckarooCommon): form_values = { 'add_returndata': None, - 'Brq_websitekey': buckaroo.brq_websitekey, + 'Brq_websitekey': self.buckaroo.brq_websitekey, 'Brq_amount': '2240.0', 'Brq_currency': 'EUR', 'Brq_invoicenumber': 'SO004', 'Brq_signature': '1b8c10074c622d965272a91a9e88b5b3777d2474', # update me 'brq_test': 'True', - 'Brq_return': '%s' % urlparse.urljoin(self.base_url, BuckarooController._return_url), - 'Brq_returncancel': '%s' % urlparse.urljoin(self.base_url, BuckarooController._cancel_url), - 'Brq_returnerror': '%s' % urlparse.urljoin(self.base_url, BuckarooController._exception_url), - 'Brq_returnreject': '%s' % urlparse.urljoin(self.base_url, BuckarooController._reject_url), + 'Brq_return': '%s' % urlparse.urljoin(base_url, BuckarooController._return_url), + 'Brq_returncancel': '%s' % urlparse.urljoin(base_url, BuckarooController._cancel_url), + 'Brq_returnerror': '%s' % urlparse.urljoin(base_url, BuckarooController._exception_url), + 'Brq_returnreject': '%s' % urlparse.urljoin(base_url, BuckarooController._reject_url), 'Brq_culture': 'en-US', } # render the button - res = self.payment_acquirer.render( - cr, uid, self.buckaroo_id, - 'SO004', 2240.0, self.currency_euro_id, + res = self.buckaroo.render( + 'SO004', 2240.0, self.currency_euro.id, partner_id=None, - partner_values=self.buyer_values, - context=context) + partner_values=self.buyer_values) # check form result tree = objectify.fromstring(res) @@ -77,24 +71,20 @@ class BuckarooForm(BuckarooCommon): # ---------------------------------------- # create a new draft tx - tx_id = self.payment_transaction.create( - cr, uid, { - 'amount': 2240.0, - 'acquirer_id': self.buckaroo_id, - 'currency_id': self.currency_euro_id, - 'reference': 'SO004', - 'partner_id': self.buyer_id, - }, context=context - ) + tx = self.env['payment.transaction'].create({ + 'amount': 2240.0, + 'acquirer_id': self.buckaroo.id, + 'currency_id': self.currency_euro.id, + 'reference': 'SO004', + 'partner_id': self.buyer_id, + }) # render the button - res = self.payment_acquirer.render( - cr, uid, self.buckaroo_id, + res = self.buckaroo_id.render( 'should_be_erased', 2240.0, self.currency_euro, - tx_id=tx_id, + tx_id=tx.id, partner_id=None, - partner_values=self.buyer_values, - context=context) + partner_values=self.buyer_values) # check form result tree = objectify.fromstring(res) @@ -108,12 +98,10 @@ class BuckarooForm(BuckarooCommon): 'Buckaroo: wrong value for form 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_buckaroo.models.buckaroo', 'ValidationError') + @mute_logger('openerp.addons.payment_buckaroo.models.payment', 'ValidationError') def test_20_buckaroo_form_management(self): - cr, uid, context = self.cr, self.uid, {} # be sure not to do stupid thing - buckaroo = self.payment_acquirer.browse(self.cr, self.uid, self.buckaroo_id, None) - self.assertEqual(buckaroo.environment, 'test', 'test without test environment') + self.assertEqual(self.buckaroo.environment, 'test', 'test without test environment') # typical data posted by buckaroo after client has successfully paid buckaroo_post_data = { @@ -125,7 +113,7 @@ class BuckarooForm(BuckarooCommon): 'brq_payment': u'573311D081B04069BD6336001611DBD4', 'BRQ_PAYMENT_METHOD': u'paypal', 'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL', - 'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com', + 'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@odoo.com', 'BRQ_SERVICE_PAYPAL_PAYERFIRSTNAME': u'Jan', 'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester', 'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de', @@ -142,22 +130,19 @@ class BuckarooForm(BuckarooCommon): # should raise error about unknown tx with self.assertRaises(ValidationError): - self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context) + self.env['payment.transaction'].form_feedback(buckaroo_post_data, 'buckaroo') + + tx = self.env['payment.transaction'].create({ + 'amount': 2240.0, + 'acquirer_id': self.buckaroo.id, + 'currency_id': self.currency_euro.id, + 'reference': 'SO004', + 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france.id}) - tx_id = self.payment_transaction.create( - cr, uid, { - 'amount': 2240.0, - 'acquirer_id': self.buckaroo_id, - 'currency_id': self.currency_euro_id, - 'reference': 'SO004', - 'partner_name': 'Norbert Buyer', - 'partner_country_id': self.country_france_id, - }, context=context - ) # validate it - self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context) + tx.form_feedback(buckaroo_post_data, 'buckaroo') # check state - tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) self.assertEqual(tx.state, 'done', 'Buckaroo: validation did not put tx into done state') self.assertEqual(tx.acquirer_reference, buckaroo_post_data.get('BRQ_TRANSACTIONS'), 'Buckaroo: validation did not update tx payid') @@ -167,12 +152,12 @@ class BuckarooForm(BuckarooCommon): # now buckaroo post is ok: try to modify the SHASIGN buckaroo_post_data['BRQ_SIGNATURE'] = '54d928810e343acf5fb0c3ee75fd747ff159ef7a' with self.assertRaises(ValidationError): - self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context) + tx.form_feedback(buckaroo_post_data, 'buckaroo') # simulate an error buckaroo_post_data['BRQ_STATUSCODE'] = 2 buckaroo_post_data['BRQ_SIGNATURE'] = '4164b52adb1e6a2221d3d8a39d8c3e18a9ecb90b' - self.payment_transaction.form_feedback(cr, uid, buckaroo_post_data, 'buckaroo', context=context) + tx.form_feedback(buckaroo_post_data, 'buckaroo') + # check state - tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) self.assertEqual(tx.state, 'error', 'Buckaroo: erroneous validation did not put tx into error state') diff --git a/addons/payment_buckaroo/views/buckaroo.xml b/addons/payment_buckaroo/views/payment_buckaroo_templates.xml similarity index 98% rename from addons/payment_buckaroo/views/buckaroo.xml rename to addons/payment_buckaroo/views/payment_buckaroo_templates.xml index 0ce58e6850a..820f923eb21 100644 --- a/addons/payment_buckaroo/views/buckaroo.xml +++ b/addons/payment_buckaroo/views/payment_buckaroo_templates.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/payment_buckaroo/views/payment_acquirer.xml b/addons/payment_buckaroo/views/payment_views.xml similarity index 97% rename from addons/payment_buckaroo/views/payment_acquirer.xml rename to addons/payment_buckaroo/views/payment_views.xml index e9cecec2309..db49146c410 100644 --- a/addons/payment_buckaroo/views/payment_acquirer.xml +++ b/addons/payment_buckaroo/views/payment_views.xml @@ -1,5 +1,5 @@ - + @@ -18,4 +18,4 @@ - + diff --git a/addons/payment_ogone/__openerp__.py b/addons/payment_ogone/__openerp__.py index 3a4a807c983..1fd1ee9c55b 100644 --- a/addons/payment_ogone/__openerp__.py +++ b/addons/payment_ogone/__openerp__.py @@ -8,9 +8,9 @@ 'description': """Ogone Payment Acquirer""", 'depends': ['payment'], 'data': [ - 'views/ogone.xml', - 'views/payment_acquirer.xml', - 'data/ogone.xml', + 'views/payment_views.xml', + 'views/payment_ogone_templates.xml', + 'data/payment_acquirer_data.xml', ], 'installable': True, } diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py index 3137f619d2d..b17198cfe40 100644 --- a/addons/payment_ogone/controllers/main.py +++ b/addons/payment_ogone/controllers/main.py @@ -3,9 +3,9 @@ import logging import pprint import werkzeug -from openerp import http, SUPERUSER_ID -from openerp.http import request -from openerp.addons.payment.models.payment_acquirer import ValidationError +from odoo import http +from odoo.http import request +from odoo.addons.payment.models.payment_acquirer import ValidationError _logger = logging.getLogger(__name__) @@ -25,33 +25,24 @@ class OgoneController(http.Controller): 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, SUPERUSER_ID, request.context - request.registry['payment.transaction'].form_feedback(cr, uid, post, 'ogone', context=context) + request.env['payment.transaction'].sudo().form_feedback(post, 'ogone') return werkzeug.utils.redirect(post.pop('return_url', '/')) @http.route(['/payment/ogone/s2s/create_json'], type='json', auth='public', csrf=False) def ogone_s2s_create_json(self, **kwargs): - data = kwargs - acquirer_id = int(data.get('acquirer_id')) - acquirer = request.env['payment.acquirer'].browse(acquirer_id) - new_id = acquirer.s2s_process(data) + new_id = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs) return new_id @http.route(['/payment/ogone/s2s/create'], type='http', auth='public', methods=["POST"], csrf=False) def ogone_s2s_create(self, **post): - acquirer_id = int(post.get('acquirer_id')) - acquirer = request.env['payment.acquirer'].browse(acquirer_id) - acquirer.s2s_process(post) + request.env['payment.acquirer'].browse(int(post.get('acquirer_id'))).s2s_process(post) return werkzeug.utils.redirect(post.get('return_url', '/')) @http.route(['/payment/ogone/s2s/feedback'], auth='none', csrf=False) def feedback(self, **kwargs): - cr, uid, context = request.cr, SUPERUSER_ID, request.context - payment = request.registry.get('payment.transaction') try: - tx = payment._ogone_form_get_tx_from_data(cr, uid, kwargs, context=context) - payment._ogone_s2s_validate(tx) + tx = request.env['payment.transaction']._ogone_form_get_tx_from_data(kwargs) + tx._ogone_s2s_validate() except ValidationError: return 'ko' - return 'ok' diff --git a/addons/payment_ogone/data/ogone.xml b/addons/payment_ogone/data/payment_acquirer_data.xml similarity index 98% rename from addons/payment_ogone/data/ogone.xml rename to addons/payment_ogone/data/payment_acquirer_data.xml index 308d13e70b9..a78ea78107a 100644 --- a/addons/payment_ogone/data/ogone.xml +++ b/addons/payment_ogone/data/payment_acquirer_data.xml @@ -1,5 +1,5 @@ - + @@ -20,4 +20,4 @@ - + diff --git a/addons/payment_ogone/models/__init__.py b/addons/payment_ogone/models/__init__.py index 349d8f668e8..10b988e0c28 100644 --- a/addons/payment_ogone/models/__init__.py +++ b/addons/payment_ogone/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -import ogone +import payment diff --git a/addons/payment_ogone/models/ogone.py b/addons/payment_ogone/models/payment.py similarity index 68% rename from addons/payment_ogone/models/ogone.py rename to addons/payment_ogone/models/payment.py index b11b6f25969..7e37479b5e6 100644 --- a/addons/payment_ogone/models/ogone.py +++ b/addons/payment_ogone/models/payment.py @@ -1,39 +1,45 @@ # coding: utf-8 -import datetime from hashlib import sha1 -import logging from lxml import etree, objectify -from openerp.tools.translate import _ from pprint import pformat -import time from unicodedata import normalize from urllib import urlencode + +import datetime +import logging +import time import urllib2 import urlparse -from openerp import SUPERUSER_ID -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment_ogone.controllers.main import OgoneController -from openerp.addons.payment_ogone.data import ogone -from openerp.osv import osv, fields -from openerp.tools import float_round, DEFAULT_SERVER_DATE_FORMAT -from openerp.tools.float_utils import float_compare, float_repr -from openerp.tools.safe_eval import safe_eval +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment_ogone.controllers.main import OgoneController +from odoo.addons.payment_ogone.data import ogone +from odoo.tools import float_round, DEFAULT_SERVER_DATE_FORMAT +from odoo.tools.float_utils import float_compare, float_repr +from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) -class PaymentAcquirerOgone(osv.Model): +class PaymentAcquirerOgone(models.Model): _inherit = 'payment.acquirer' - def _get_ogone_urls(self, cr, uid, environment, context=None): + provider = fields.Selection(selection_add=[('ogone', 'Ogone')]) + 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') + ogone_alias_usage = fields.Char('Alias Usage', help="""If you want to use Ogone Aliases, + this default Alias Usage will be presented to + the customer as the reason you want to + keep his payment data""") + + def _get_ogone_urls(self, environment): """ Ogone URLS: - - - standard order: POST address for form-based - - @TDETODO: complete me - """ + - standard order: POST address for form-based """ return { 'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard_utf8.asp' % (environment,), 'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect_utf8.asp' % (environment,), @@ -41,38 +47,19 @@ class PaymentAcquirerOgone(osv.Model): 'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % (environment,), } - def _get_providers(self, cr, uid, context=None): - providers = super(PaymentAcquirerOgone, self)._get_providers(cr, uid, context=context) - providers.append(['ogone', 'Ogone']) - return providers - - _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('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'), - 'ogone_alias_usage': fields.char('Alias Usage', help="""If you want to use Ogone Aliases, - this default Alias Usage will be presented to - the customer as the reason you want to - keep his payment data""") - } - - def _ogone_generate_shasign(self, acquirer, inout, values): + def _ogone_generate_shasign(self, 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' (openerp contacting ogone) or 'out' (ogone - contacting openerp). In this last case only some + :param string inout: 'in' (odoo contacting ogone) or 'out' (ogone + contacting odoo). In this last case only some fields should be contained (see e-Commerce basic) :param dict values: transaction values :return string: shasign """ assert inout in ('in', 'out') - assert acquirer.provider == 'ogone' - key = getattr(acquirer, 'ogone_shakey_' + inout) + assert self.provider == 'ogone' + key = getattr(self, 'ogone_shakey_' + inout) def filter_key(key): if inout == 'in': @@ -147,12 +134,11 @@ class PaymentAcquirerOgone(osv.Model): shasign = sha1(sign).hexdigest() return shasign - def ogone_form_generate_values(self, cr, uid, id, 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) + def ogone_form_generate_values(self, values): + base_url = self.env['ir.config_parameter'].get_param('web.base.url') ogone_tx_values = dict(values) temp_ogone_tx_values = { - 'PSPID': acquirer.ogone_pspid, + 'PSPID': self.ogone_pspid, 'ORDERID': values['reference'], 'AMOUNT': float_repr(float_round(values['amount'], 2) * 100, 0), 'CURRENCY': values['currency'] and values['currency'].name or '', @@ -173,20 +159,18 @@ class PaymentAcquirerOgone(osv.Model): if values.get('type') == 'form_save': temp_ogone_tx_values.update({ 'ALIAS': 'ODOO-NEW-ALIAS-%s' % time.time(), # something unique, - 'ALIASUSAGE': values.get('alias_usage') or acquirer.ogone_alias_usage, + 'ALIASUSAGE': values.get('alias_usage') or self.ogone_alias_usage, }) - shasign = self._ogone_generate_shasign(acquirer, 'in', temp_ogone_tx_values) + shasign = self._ogone_generate_shasign('in', temp_ogone_tx_values) temp_ogone_tx_values['SHASIGN'] = shasign ogone_tx_values.update(temp_ogone_tx_values) return ogone_tx_values - def ogone_get_form_action_url(self, cr, uid, id, context=None): - acquirer = self.browse(cr, uid, id, context=context) - return self._get_ogone_urls(cr, uid, acquirer.environment, context=context)['ogone_standard_order_url'] + def ogone_get_form_action_url(self): + return self._get_ogone_urls(self.environment)['ogone_standard_order_url'] - def ogone_s2s_form_validate(self, cr, uid, id, data, context=None): + def ogone_s2s_form_validate(self, data): error = dict() - error_message = [] mandatory_fields = ["cc_number", "cc_cvc", "cc_holder_name", "cc_expiry", "cc_brand"] # Validation @@ -196,7 +180,7 @@ class PaymentAcquirerOgone(osv.Model): return False if error else True - def ogone_s2s_form_process(self, cr, uid, data, context=None): + def ogone_s2s_form_process(self, data): values = { 'cc_number': data.get('cc_number'), 'cc_cvc': int(data.get('cc_cvc')), @@ -206,11 +190,11 @@ class PaymentAcquirerOgone(osv.Model): 'acquirer_id': int(data.get('acquirer_id')), 'partner_id': int(data.get('partner_id')) } - pm_id = self.pool['payment.token'].create(cr, SUPERUSER_ID, values, context=context) + pm_id = self.env['payment.token'].sudo().create(values) return pm_id -class PaymentTxOgone(osv.Model): +class PaymentTxOgone(models.Model): _inherit = 'payment.transaction' # ogone status _ogone_valid_tx_status = [5, 9] @@ -222,7 +206,8 @@ class PaymentTxOgone(osv.Model): # FORM RELATED METHODS # -------------------------------------------------- - def _ogone_form_get_tx_from_data(self, cr, uid, data, context=None): + @api.model + def _ogone_form_get_tx_from_data(self, data): """ Given a data dict coming from ogone, verify it and find the related transaction record. Create a payment token if an alias is returned.""" reference, pay_id, shasign, alias = data.get('orderID'), data.get('PAYID'), data.get('SHASIGN'), data.get('ALIAS') @@ -232,19 +217,18 @@ class PaymentTxOgone(osv.Model): raise ValidationError(error_msg) # find tx -> @TDENOTE use paytid ? - tx_ids = self.search(cr, uid, [('reference', '=', reference)], context=context) - if not tx_ids or len(tx_ids) > 1: + tx = self.search([('reference', '=', reference)]) + if not tx or len(tx) > 1: error_msg = _('Ogone: received data for reference %s') % (reference) - if not tx_ids: + if not tx: error_msg += _('; no order found') else: error_msg += _('; multiple order found') _logger.info(error_msg) raise ValidationError(error_msg) - tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context) # verify shasign - shasign_check = self.pool['payment.acquirer']._ogone_generate_shasign(tx.acquirer_id, 'out', data) + shasign_check = tx.acquirer_id._ogone_generate_shasign('out', data) if shasign_check.upper() != shasign.upper(): error_msg = _('Ogone: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data) _logger.info(error_msg) @@ -255,37 +239,36 @@ class PaymentTxOgone(osv.Model): # alias was created on ogone server, store it if alias: - method_obj = self.pool['payment.token'] + Method = self.env['payment.token'] domain = [('acquirer_ref', '=', alias)] cardholder = data.get('CN') - if not method_obj.search_count(cr, uid, domain, context=context): + if not Method.search_count(domain): _logger.info('Ogone: saving alias %s for partner %s' % (data.get('CARDNO'), tx.partner_id)) - ref = method_obj.create(cr, uid, {'name': data.get('CARDNO') + (' - ' + cardholder if cardholder else ''), - 'partner_id': tx.partner_id.id, - 'acquirer_id': tx.acquirer_id.id, - 'acquirer_ref': alias - }) + ref = Method.create({'name': data.get('CARDNO') + (' - ' + cardholder if cardholder else ''), + 'partner_id': tx.partner_id.id, + 'acquirer_id': tx.acquirer_id.id, + 'acquirer_ref': alias}) tx.write({'payment_token_id': ref}) return tx - def _ogone_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): + def _ogone_form_get_invalid_parameters(self, data): invalid_parameters = [] # TODO: txn_id: should 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)) + if self.acquirer_reference and data.get('PAYID') != self.acquirer_reference: + invalid_parameters.append(('PAYID', data.get('PAYID'), self.acquirer_reference)) # check what is bought - 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)) + if float_compare(float(data.get('amount', '0.0')), self.amount, 2) != 0: + invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount)) + if data.get('currency') != self.currency_id.name: + invalid_parameters.append(('currency', data.get('currency'), self.currency_id.name)) return invalid_parameters - def _ogone_form_validate(self, cr, uid, tx, data, context=None): - if tx.state == 'done': - _logger.info('Ogone: trying to validate an already validated tx (ref %s)', tx.reference) + def _ogone_form_validate(self, data): + if self.state == 'done': + _logger.info('Ogone: trying to validate an already validated tx (ref %s)', self.reference) return True status = int(data.get('STATUS', '0')) @@ -295,25 +278,25 @@ class PaymentTxOgone(osv.Model): 'date_validate': datetime.datetime.strptime(data['TRXDATE'], '%m/%d/%y').strftime(DEFAULT_SERVER_DATE_FORMAT), 'acquirer_reference': data['PAYID'], } - if data.get('ALIAS') and tx.partner_id and tx.type == 'form_save' and not tx.payment_token_id: - pm_id = self.pool['payment.token'].create(cr, uid, { - 'partner_id': tx.partner_id.id, - 'acquirer_id': tx.acquirer_id.id, + if data.get('ALIAS') and self.partner_id and self.type == 'form_save' and not self.payment_token_id: + pm = self.env['payment.token'].create({ + 'partner_id': self.partner_id.id, + 'acquirer_id': self.acquirer_id.id, 'acquirer_ref': data.get('ALIAS'), 'name': '%s - %s' % (data.get('CARDNO'), data.get('CN')) - }, context=context) - vals.update(payment_token_id=pm_id) - tx.write(vals) - if tx.callback_eval: - safe_eval(tx.callback_eval, {'self': tx}) + }) + vals.update(payment_token_id=pm.id) + self.write(vals) + if self.callback_eval: + safe_eval(self.callback_eval, {'self': self}) return True elif status in self._ogone_cancel_tx_status: - tx.write({ + self.write({ 'state': 'cancel', 'acquirer_reference': data.get('PAYID'), }) elif status in self._ogone_pending_tx_status or status in self._ogone_wait_tx_status: - tx.write({ + self.write({ 'state': 'pending', 'acquirer_reference': data.get('PAYID'), }) @@ -324,7 +307,7 @@ class PaymentTxOgone(osv.Model): 'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERROR')), } _logger.info(error) - tx.write({ + self.write({ 'state': 'error', 'state_message': error, 'acquirer_reference': data.get('PAYID'), @@ -334,29 +317,29 @@ class PaymentTxOgone(osv.Model): # -------------------------------------------------- # S2S RELATED METHODS # -------------------------------------------------- - def ogone_s2s_do_transaction(self, cr, uid, id, context=None, **kwargs): + + def ogone_s2s_do_transaction(self, **kwargs): # TODO: create tx with s2s type - tx = self.browse(cr, uid, id, context=context) - account = tx.acquirer_id - reference = tx.reference or "ODOO-%s-%s" % (datetime.datetime.now().strftime('%y%m%d_%H%M%S'), tx.partner_id.id) + account = self.acquirer_id + reference = self.reference or "ODOO-%s-%s" % (datetime.datetime.now().strftime('%y%m%d_%H%M%S'), self.partner_id.id) data = { 'PSPID': account.ogone_pspid, 'USERID': account.ogone_userid, 'PSWD': account.ogone_password, 'ORDERID': reference, - 'AMOUNT': long(tx.amount * 100), - 'CURRENCY': tx.currency_id.name, + 'AMOUNT': long(self.amount * 100), + 'CURRENCY': self.currency_id.name, 'OPERATION': 'SAL', 'ECI': 2, # Recurring (from MOTO) - 'ALIAS': tx.payment_token_id.acquirer_ref, + 'ALIAS': self.payment_token_id.acquirer_ref, 'RTIMEOUT': 30, } if kwargs.get('3d_secure'): data.update({ 'FLAG3D': 'Y', - 'LANGUAGE': tx.partner_id.lang or 'en_US', + 'LANGUAGE': self.partner_id.lang or 'en_US', }) for url in 'accept decline exception'.split(): @@ -366,9 +349,9 @@ class PaymentTxOgone(osv.Model): key = '{0}URL'.format(url).upper() data[key] = val - data['SHASIGN'] = self.pool['payment.acquirer']._ogone_generate_shasign(tx.acquirer_id, 'in', data) + data['SHASIGN'] = self.acquirer_id._ogone_generate_shasign('in', data) - direct_order_url = 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % (tx.acquirer_id.environment) + direct_order_url = 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % (self.acquirer_id.environment) _logger.debug("Ogone data %s", pformat(data)) request = urllib2.Request(direct_order_url, urlencode(data)) @@ -382,51 +365,51 @@ class PaymentTxOgone(osv.Model): _logger.exception('Invalid xml response from ogone') raise - return self._ogone_s2s_validate_tree(tx, tree) + return self._ogone_s2s_validate_tree(tree) - def _ogone_s2s_validate(self, tx): - tree = self._ogone_s2s_get_tx_status(tx) - return self._ogone_s2s_validate_tree(tx, tree) + def _ogone_s2s_validate(self): + tree = self._ogone_s2s_get_tx_status() + return self._ogone_s2s_validate_tree(tree) - def _ogone_s2s_validate_tree(self, tx, tree, tries=2): - if tx.state not in ('draft', 'pending'): - _logger.info('Ogone: trying to validate an already validated tx (ref %s)', tx.reference) + def _ogone_s2s_validate_tree(self, tree, tries=2): + if self.state not in ('draft', 'pending'): + _logger.info('Ogone: trying to validate an already validated tx (ref %s)', self.reference) return True status = int(tree.get('STATUS') or 0) if status in self._ogone_valid_tx_status: - tx.write({ + self.write({ 'state': 'done', 'date_validate': datetime.date.today().strftime(DEFAULT_SERVER_DATE_FORMAT), 'acquirer_reference': tree.get('PAYID'), }) - if tree.get('ALIAS') and tx.partner_id and tx.type == 'form_save' and not tx.payment_token_id: - pm = tx.env['payment.token'].create({ - 'partner_id': tx.partner_id.id, - 'acquirer_id': tx.acquirer_id.id, + if tree.get('ALIAS') and self.partner_id and self.type == 'form_save' and not self.payment_token_id: + pm = self.env['payment.token'].create({ + 'partner_id': self.partner_id.id, + 'acquirer_id': self.acquirer_id.id, 'acquirer_ref': tree.get('ALIAS'), 'name': tree.get('CARDNO'), }) - tx.write({'payment_token_id': pm.id}) - if tx.callback_eval: - safe_eval(tx.callback_eval, {'self': tx}) + self.write({'payment_token_id': pm.id}) + if self.callback_eval: + safe_eval(self.callback_eval, {'self': self}) return True elif status in self._ogone_cancel_tx_status: - tx.write({ + self.write({ 'state': 'cancel', 'acquirer_reference': tree.get('PAYID'), }) elif status in self._ogone_pending_tx_status: - tx.write({ + self.write({ 'state': 'pending', 'acquirer_reference': tree.get('PAYID'), 'html_3ds': str(tree.HTML_ANSWER).decode('base64') }) elif (not status or status in self._ogone_wait_tx_status) and tries > 0: time.sleep(500) - tx.write({'acquirer_reference': tree.get('PAYID')}) - tree = self._ogone_s2s_get_tx_status(tx) - return self._ogone_s2s_validate_tree(tx, tree, tries - 1) + self.write({'acquirer_reference': tree.get('PAYID')}) + tree = self._ogone_s2s_get_tx_status(self) + return self._ogone_s2s_validate_tree(self, tree, tries - 1) else: error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % { 'error_str': tree.get('NCERRORPLUS'), @@ -434,25 +417,25 @@ class PaymentTxOgone(osv.Model): 'error_msg': ogone.OGONE_ERROR_MAP.get(tree.get('NCERROR')), } _logger.info(error) - tx.write({ + self.write({ 'state': 'error', 'state_message': error, 'acquirer_reference': tree.get('PAYID'), }) return False - def _ogone_s2s_get_tx_status(self, tx): - account = tx.acquirer_id + def _ogone_s2s_get_tx_status(self): + account = self.acquirer_id #reference = tx.reference or "ODOO-%s-%s" % (datetime.datetime.now().strftime('%Y%m%d_%H%M%S'), tx.partner_id.id) data = { - 'PAYID': tx.acquirer_reference, + 'PAYID': self.acquirer_reference, 'PSPID': account.ogone_pspid, 'USERID': account.ogone_userid, 'PSWD': account.ogone_password, } - query_direct_url = 'https://secure.ogone.com/ncol/%s/querydirect.asp' % (tx.acquirer_id.environment) + query_direct_url = 'https://secure.ogone.com/ncol/%s/querydirect.asp' % (self.acquirer_id.environment) _logger.debug("Ogone data %s", pformat(data)) request = urllib2.Request(query_direct_url, urlencode(data)) @@ -469,14 +452,14 @@ class PaymentTxOgone(osv.Model): return tree -class PaymentToken(osv.Model): +class PaymentToken(models.Model): _inherit = 'payment.token' - def ogone_create(self, cr, uid, values, context=None): + def ogone_create(self, values): if values.get('cc_number'): # create a alias via batch values['cc_number'] = values['cc_number'].replace(' ', '') - acquirer = self.pool['payment.acquirer'].browse(cr, uid, values['acquirer_id']) + acquirer = self.env['payment.acquirer'].browse(values['acquirer_id']) alias = 'ODOO-NEW-ALIAS-%s' % time.time() expiry = str(values['cc_expiry'][:2]) + str(values['cc_expiry'][-2:]) diff --git a/addons/payment_ogone/tests/test_ogone.py b/addons/payment_ogone/tests/test_ogone.py index 81f09873fa4..2651b00fefe 100644 --- a/addons/payment_ogone/tests/test_ogone.py +++ b/addons/payment_ogone/tests/test_ogone.py @@ -4,27 +4,23 @@ from lxml import objectify import time import urlparse -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment.tests.common import PaymentAcquirerCommon -from openerp.addons.payment_ogone.controllers.main import OgoneController -from openerp.tools import mute_logger +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment.tests.common import PaymentAcquirerCommon +from odoo.addons.payment_ogone.controllers.main import OgoneController +from odoo.tools import mute_logger 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') - # get the adyen account - model, self.ogone_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment', 'payment_acquirer_ogone') + self.ogone = self.env.ref('payment.payment_acquirer_ogone') def test_10_ogone_form_render(self): - cr, uid, context = self.cr, self.uid, {} + base_url = self.env['ir.config_parameter'].get_param('web.base.url') # be sure not to do stupid thing - ogone = self.payment_acquirer.browse(self.cr, self.uid, self.ogone_id, None) - self.assertEqual(ogone.environment, 'test', 'test without test environment') + self.assertEqual(self.ogone.environment, 'test', 'test without test environment') # ---------------------------------------- # Test: button direct rendering + shasign @@ -44,19 +40,17 @@ class OgonePayment(PaymentAcquirerCommon): 'OWNERTOWN': 'Sin City', 'OWNERTELNO': '0032 12 34 56 78', 'SHASIGN': '815f67b8ff70d234ffcf437c13a9fa7f807044cc', - 'ACCEPTURL': '%s' % urlparse.urljoin(self.base_url, OgoneController._accept_url), - 'DECLINEURL': '%s' % urlparse.urljoin(self.base_url, OgoneController._decline_url), - 'EXCEPTIONURL': '%s' % urlparse.urljoin(self.base_url, OgoneController._exception_url), - 'CANCELURL': '%s' % urlparse.urljoin(self.base_url, OgoneController._cancel_url), + '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), } # render the button - res = self.payment_acquirer.render( - cr, uid, self.ogone_id, - 'test_ref0', 0.01, self.currency_euro_id, + res = self.ogone.render( + 'test_ref0', 0.01, self.currency_euro.id, partner_id=None, - partner_values=self.buyer_values, - context=context) + partner_values=self.buyer_values) # check form result tree = objectify.fromstring(res) @@ -75,23 +69,18 @@ class OgonePayment(PaymentAcquirerCommon): # ---------------------------------------- # create a new draft tx - tx_id = self.payment_transaction.create( - cr, uid, { - 'amount': 0.01, - 'acquirer_id': self.ogone_id, - 'currency_id': self.currency_euro_id, - 'reference': 'test_ref0', - 'partner_id': self.buyer_id, - }, context=context - ) + tx = self.env['payment.transaction'].create({ + 'amount': 0.01, + 'acquirer_id': self.ogone.id, + 'currency_id': self.currency_euro.id, + 'reference': 'test_ref0', + 'partner_id': self.buyer_id}) # render the button - res = self.payment_acquirer.render( - cr, uid, self.ogone_id, + res = self.ogone.render( 'should_be_erased', 0.01, self.currency_euro, - tx_id=tx_id, + tx_id=tx.id, partner_id=None, - partner_values=self.buyer_values, - context=context) + partner_values=self.buyer_values) # check form result tree = objectify.fromstring(res) @@ -105,12 +94,10 @@ class OgonePayment(PaymentAcquirerCommon): 'ogone: wrong value for form 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_ogone.models.ogone', 'ValidationError') + @mute_logger('openerp.addons.payment_ogone.models.payment', '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.environment, 'test', 'test without test environment') + self.assertEqual(self.ogone.environment, 'test', 'test without test environment') # typical data posted by ogone after client has successfully paid ogone_post_data = { @@ -133,23 +120,19 @@ class OgonePayment(PaymentAcquirerCommon): # should raise error about unknown tx with self.assertRaises(ValidationError): - self.payment_transaction.ogone_form_feedback(cr, uid, ogone_post_data, context=context) + self.env['payment.transaction'].form_feedback(ogone_post_data) # create tx - tx_id = self.payment_transaction.create( - cr, uid, { - 'amount': 1.95, - 'acquirer_id': self.ogone_id, - 'currency_id': self.currency_euro_id, - 'reference': 'test_ref_2', - 'partner_name': 'Norbert Buyer', - 'partner_country_id': self.country_france_id, - }, context=context - ) + tx = self.env['payment.transaction'].create({ + 'amount': 1.95, + 'acquirer_id': self.ogone.id, + 'currency_id': self.currency_euro.id, + 'reference': 'test_ref_2', + 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france.id}) # validate it - self.payment_transaction.ogone_form_feedback(cr, uid, ogone_post_data, context=context) + tx.form_feedback(ogone_post_data) # check state - tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) self.assertEqual(tx.state, 'done', 'ogone: validation did not put tx into done state') self.assertEqual(tx.ogone_payid, ogone_post_data.get('PAYID'), 'ogone: validation did not update tx payid') @@ -159,67 +142,39 @@ class OgonePayment(PaymentAcquirerCommon): # now ogone post is ok: try to modify the SHASIGN ogone_post_data['SHASIGN'] = 'a4c16bae286317b82edb49188d3399249a784691' with self.assertRaises(ValidationError): - self.payment_transaction.ogone_form_feedback(cr, uid, ogone_post_data, context=context) + tx.form_feedback(ogone_post_data) # simulate an error ogone_post_data['STATUS'] = 2 ogone_post_data['SHASIGN'] = 'a4c16bae286317b82edb49188d3399249a784691' - self.payment_transaction.ogone_form_feedback(cr, uid, ogone_post_data, context=context) + tx.form_feedback(ogone_post_data) # check state - tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) self.assertEqual(tx.state, 'error', 'ogone: erroneous validation did not put tx into error state') 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.environment, 'test', 'test without test environment') + self.assertEqual(self.ogone.environment, 'test', 'test without test environment') # create a new draft tx - tx_id = self.payment_transaction.create( - cr, uid, { - 'amount': 0.01, - 'acquirer_id': self.ogone_id, - 'currency_id': self.currency_euro_id, - 'reference': test_ref, - 'partner_id': self.buyer_id, - 'type': 'server2server', - }, context=context - ) + tx = self.env['payment.transaction'].create({ + 'amount': 0.01, + 'acquirer_id': self.ogone.id, + 'currency_id': self.currency_euro.id, + 'reference': test_ref, + 'partner_id': self.buyer_id, + 'type': 'server2server', + }) # create an alias - res = self.payment_transaction.ogone_s2s_create_alias( - cr, uid, tx_id, { - 'expiry_date_mm': '01', - 'expiry_date_yy': '2015', - 'holder_name': 'Norbert Poilu', - 'number': '4000000000000002', - 'brand': 'VISA', - }, context=context) + res = tx.ogone_s2s_create_alias({ + 'expiry_date_mm': '01', + 'expiry_date_yy': '2015', + 'holder_name': 'Norbert Poilu', + 'number': '4000000000000002', + 'brand': 'VISA'}) # check an alias is set, containing at least OPENERP - tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) self.assertIn('OPENERP', tx.partner_reference, 'ogone: wrong partner reference after creating an alias') - res = self.payment_transaction.ogone_s2s_execute(cr, uid, tx_id, {}, context=context) - # print res - - -# { -# 'orderID': u'reference', -# 'STATUS': u'9', -# 'CARDNO': u'XXXXXXXXXXXX0002', -# 'PAYID': u'24998692', -# 'CN': u'Norbert Poilu', -# 'NCERROR': u'0', -# 'TRXDATE': u'11/05/13', -# 'IP': u'85.201.233.72', -# 'BRAND': u'VISA', -# 'ACCEPTANCE': u'test123', -# 'currency': u'EUR', -# 'amount': u'1.95', -# 'SHASIGN': u'EFDC56879EF7DE72CCF4B397076B5C9A844CB0FA', -# 'ED': u'0314', -# 'PM': u'CreditCard' -# } + res = tx.ogone_s2s_execute({}) diff --git a/addons/payment_ogone/views/ogone.xml b/addons/payment_ogone/views/payment_ogone_templates.xml similarity index 99% rename from addons/payment_ogone/views/ogone.xml rename to addons/payment_ogone/views/payment_ogone_templates.xml index 69ebe0d7e5f..8e9dd72c103 100644 --- a/addons/payment_ogone/views/ogone.xml +++ b/addons/payment_ogone/views/payment_ogone_templates.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/payment_ogone/views/payment_acquirer.xml b/addons/payment_ogone/views/payment_views.xml similarity index 98% rename from addons/payment_ogone/views/payment_acquirer.xml rename to addons/payment_ogone/views/payment_views.xml index 8efde9b908c..bffca767fbd 100644 --- a/addons/payment_ogone/views/payment_acquirer.xml +++ b/addons/payment_ogone/views/payment_views.xml @@ -1,5 +1,5 @@ - + @@ -22,4 +22,4 @@ - + diff --git a/addons/payment_paypal/__openerp__.py b/addons/payment_paypal/__openerp__.py index 2e53042ccc6..80274a5b64e 100644 --- a/addons/payment_paypal/__openerp__.py +++ b/addons/payment_paypal/__openerp__.py @@ -8,10 +8,10 @@ 'description': """Paypal Payment Acquirer""", 'depends': ['payment'], 'data': [ - 'views/paypal.xml', - 'views/payment_acquirer.xml', - 'views/res_config_view.xml', - 'data/paypal.xml', + 'views/payment_views.xml', + 'views/payment_paypal_templates.xml', + 'views/account_config_settings_views.xml', + 'data/payment_acquirer_data.xml', ], 'installable': True, } diff --git a/addons/payment_paypal/controllers/main.py b/addons/payment_paypal/controllers/main.py index 0048c3ffb26..a74ab8e1d29 100644 --- a/addons/payment_paypal/controllers/main.py +++ b/addons/payment_paypal/controllers/main.py @@ -6,8 +6,8 @@ import pprint import urllib2 import werkzeug -from openerp import http, SUPERUSER_ID -from openerp.http import request +from odoo import http +from odoo.http import request _logger = logging.getLogger(__name__) @@ -37,21 +37,18 @@ class PaypalController(http.Controller): Once data is validated, process it. """ res = False new_post = dict(post, cmd='_notify-validate') - cr, uid, context = request.cr, request.uid, request.context reference = post.get('item_number') tx = None if reference: - tx_ids = request.registry['payment.transaction'].search(cr, uid, [('reference', '=', reference)], context=context) - if tx_ids: - tx = request.registry['payment.transaction'].browse(cr, uid, tx_ids[0], context=context) - paypal_urls = request.registry['payment.acquirer']._get_paypal_urls(cr, uid, tx and tx.acquirer_id and tx.acquirer_id.environment or 'prod', context=context) + tx = request.env['payment.transaction'].search([('reference', '=', reference)]) + paypal_urls = tx.acquirer_id._get_paypal_urls(tx.acquirer_id.environment or 'prod') validate_url = paypal_urls['paypal_form_url'] urequest = urllib2.Request(validate_url, werkzeug.url_encode(new_post)) uopen = urllib2.urlopen(urequest) resp = uopen.read() if resp == 'VERIFIED': _logger.info('Paypal: validated data') - res = request.registry['payment.transaction'].form_feedback(cr, SUPERUSER_ID, post, 'paypal', context=context) + res = request.env['payment.transaction'].sudo().form_feedback(post, 'paypal') elif resp == 'INVALID': _logger.warning('Paypal: answered INVALID on data verification') else: @@ -76,7 +73,6 @@ class PaypalController(http.Controller): @http.route('/payment/paypal/cancel', type='http', auth="none", csrf=False) def paypal_cancel(self, **post): """ When the user cancels its Paypal payment: GET on this route """ - cr, uid, context = request.cr, SUPERUSER_ID, request.context _logger.info('Beginning Paypal cancel with post data %s', pprint.pformat(post)) # debug return_url = self._get_return_url(**post) return werkzeug.utils.redirect(return_url) diff --git a/addons/payment_paypal/data/paypal.xml b/addons/payment_paypal/data/payment_acquirer_data.xml similarity index 97% rename from addons/payment_paypal/data/paypal.xml rename to addons/payment_paypal/data/payment_acquirer_data.xml index fd474b27532..6ae2dd7df05 100644 --- a/addons/payment_paypal/data/paypal.xml +++ b/addons/payment_paypal/data/payment_acquirer_data.xml @@ -1,5 +1,5 @@ - + @@ -18,4 +18,4 @@ - + diff --git a/addons/payment_paypal/models/__init__.py b/addons/payment_paypal/models/__init__.py index a1fe670f56f..10b988e0c28 100644 --- a/addons/payment_paypal/models/__init__.py +++ b/addons/payment_paypal/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -import paypal +import payment diff --git a/addons/payment_paypal/models/payment.py b/addons/payment_paypal/models/payment.py new file mode 100644 index 00000000000..6919302293e --- /dev/null +++ b/addons/payment_paypal/models/payment.py @@ -0,0 +1,191 @@ +# coding: utf-8 + +import json +import logging +import urlparse + +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment_paypal.controllers.main import PaypalController +from odoo.tools.float_utils import float_compare + + +_logger = logging.getLogger(__name__) + + +class AcquirerPaypal(models.Model): + _inherit = 'payment.acquirer' + + provider = fields.Selection(selection_add=[('paypal', 'Paypal')]) + paypal_email_account = fields.Char('Paypal Email ID', required_if_provider='paypal') + paypal_seller_account = fields.Char( + 'Paypal Merchant ID', + help='The Merchant ID is used to ensure communications coming from Paypal are valid and secured.') + paypal_use_ipn = fields.Boolean('Use IPN', default=True, help='Paypal Instant Payment Notification') + # Server 2 server + paypal_api_enabled = fields.Boolean('Use Rest API', default=False) + paypal_api_username = fields.Char('Rest API Username') + paypal_api_password = fields.Char('Rest API Password') + paypal_api_access_token = fields.Char('Access Token') + paypal_api_access_token_validity = fields.Datetime('Access Token Validity') + # Default paypal fees + fees_dom_fixed = fields.Float(default=0.35) + fees_dom_var = fields.Float(default=3.4) + fees_int_fixed = fields.Float(default=0.35) + fees_int_var = fields.Float(default=3.9) + + @api.model + def _get_paypal_urls(self, environment): + """ Paypal URLS """ + if environment == 'prod': + return { + 'paypal_form_url': 'https://www.paypal.com/cgi-bin/webscr', + 'paypal_rest_url': 'https://api.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', + } + + @api.multi + def paypal_compute_fees(self, amount, currency_id, country_id): + """ Compute paypal fees. + + :param float amount: the amount to pay + :param integer country_id: an ID of a res.country, or None. This is + the customer's country, to be compared to + the acquirer company country. + :return float fees: computed fees + """ + if not self.fees_active: + return 0.0 + country = self.env['res.country'].browse(country_id) + if country and self.company_id.country_id.id == country.id: + percentage = self.fees_dom_var + fixed = self.fees_dom_fixed + else: + percentage = self.fees_int_var + fixed = self.fees_int_fixed + fees = (percentage / 100.0 * amount + fixed) / (1 - percentage / 100.0) + return fees + + @api.multi + def paypal_form_generate_values(self, values): + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + + paypal_tx_values = dict(values) + paypal_tx_values.update({ + 'cmd': '_xclick', + 'business': self.paypal_email_account, + 'item_name': '%s: %s' % (self.company_id.name, values['reference']), + 'item_number': values['reference'], + 'amount': values['amount'], + 'currency_code': values['currency'] and values['currency'].name or '', + 'address1': values.get('partner_address'), + 'city': values.get('partner_city'), + 'country': values.get('partner_country') and values.get('partner_country').name or '', + 'state': values.get('partner_state') and values.get('partner_state').name or '', + 'email': values.get('partner_email'), + 'zip_code': values.get('partner_zip'), + 'first_name': values.get('partner_first_name'), + 'last_name': values.get('partner_last_name'), + 'paypal_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), + 'handling': '%.2f' % paypal_tx_values.pop('fees', 0.0) if self.fees_active else False, + 'custom': json.dumps({'return_url': '%s' % paypal_tx_values.pop('return_url')}) if paypal_tx_values.get('return_url') else False, + }) + return paypal_tx_values + + @api.multi + def paypal_get_form_action_url(self): + return self._get_paypal_urls(self.environment)['paypal_form_url'] + + +class TxPaypal(models.Model): + _inherit = 'payment.transaction' + + paypal_txn_type = fields.Char('Transaction type') + + # -------------------------------------------------- + # FORM RELATED METHODS + # -------------------------------------------------- + + @api.model + def _paypal_form_get_tx_from_data(self, data): + reference, txn_id = data.get('item_number'), data.get('txn_id') + if not reference or not txn_id: + error_msg = _('Paypal: received data with missing reference (%s) or txn_id (%s)') % (reference, txn_id) + _logger.info(error_msg) + raise ValidationError(error_msg) + + # find tx -> @TDENOTE use txn_id ? + txs = self.env['payment.transaction'].search([('reference', '=', reference)]) + if not txs or len(txs) > 1: + error_msg = 'Paypal: received data for reference %s' % (reference) + if not txs: + error_msg += '; no order found' + else: + error_msg += '; multiple order found' + _logger.info(error_msg) + raise ValidationError(error_msg) + return txs[0] + + @api.multi + def _paypal_form_get_invalid_parameters(self, data): + invalid_parameters = [] + _logger.info('Received a notification from Paypal with IPN version %s', data.get('notify_version')) + if data.get('test_ipn'): + _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 self.acquirer_reference and data.get('txn_id') != self.acquirer_reference: + invalid_parameters.append(('txn_id', data.get('txn_id'), self.acquirer_reference)) + # check what is buyed + if float_compare(float(data.get('mc_gross', '0.0')), (self.amount + self.fees), 2) != 0: + invalid_parameters.append(('mc_gross', data.get('mc_gross'), '%.2f' % self.amount)) # mc_gross is amount + fees + if data.get('mc_currency') != self.currency_id.name: + invalid_parameters.append(('mc_currency', data.get('mc_currency'), self.currency_id.name)) + if 'handling_amount' in data and float_compare(float(data.get('handling_amount')), self.fees, 2) != 0: + invalid_parameters.append(('handling_amount', data.get('handling_amount'), self.fees)) + # check buyer + if self.payment_token_id and data.get('payer_id') != self.payment_token_id.acquirer_ref: + invalid_parameters.append(('payer_id', data.get('payer_id'), self.payment_token_id.acquirer_ref)) + # check seller + if data.get('receiver_id') and self.acquirer_id.paypal_seller_account and data['receiver_id'] != self.acquirer_id.paypal_seller_account: + invalid_parameters.append(('receiver_id', data.get('receiver_id'), self.acquirer_id.paypal_seller_account)) + if not data.get('receiver_id') or not self.acquirer_id.paypal_seller_account: + # Check receiver_email only if receiver_id was not checked. + # In Paypal, this is possible to configure as receiver_email a different email than the business email (the login email) + # In Odoo, there is only one field for the Paypal email: the business email. This isn't possible to set a receiver_email + # different than the business email. Therefore, if you want such a configuration in your Paypal, you are then obliged to fill + # the Merchant ID in the Paypal payment acquirer in Odoo, so the check is performed on this variable instead of the receiver_email. + # At least one of the two checks must be done, to avoid fraudsters. + if data.get('receiver_email') != self.acquirer_id.paypal_email_account: + invalid_parameters.append(('receiver_email', data.get('receiver_email'), self.acquirer_id.paypal_email_account)) + + return invalid_parameters + + @api.multi + def _paypal_form_validate(self, data): + status = data.get('payment_status') + res = { + 'acquirer_reference': data.get('txn_id'), + 'paypal_txn_type': data.get('payment_type'), + } + if status in ['Completed', 'Processed']: + _logger.info('Validated Paypal payment for tx %s: set as done' % (self.reference)) + res.update(state='done', date_validate=data.get('payment_date', fields.datetime.now())) + return self.write(res) + elif status in ['Pending', 'Expired']: + _logger.info('Received notification for Paypal payment %s: set as pending' % (self.reference)) + res.update(state='pending', state_message=data.get('pending_reason', '')) + return self.write(res) + else: + error = 'Received unrecognized status for Paypal payment %s: %s, set as error' % (self.reference, status) + _logger.info(error) + res.update(state='error', state_message=error) + return self.write(res) diff --git a/addons/payment_paypal/models/paypal.py b/addons/payment_paypal/models/paypal.py deleted file mode 100644 index a92086c49c9..00000000000 --- a/addons/payment_paypal/models/paypal.py +++ /dev/null @@ -1,393 +0,0 @@ -# coding: utf-8 - -import base64 -import json -import logging -import urlparse -import werkzeug.urls -import urllib2 - -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment_paypal.controllers.main import PaypalController -from openerp.osv import osv, fields -from openerp.tools.float_utils import float_compare -from openerp import SUPERUSER_ID -from openerp.tools.translate import _ - -_logger = logging.getLogger(__name__) - - -class AcquirerPaypal(osv.Model): - _inherit = 'payment.acquirer' - - def _get_paypal_urls(self, cr, uid, environment, context=None): - """ Paypal URLS """ - if environment == 'prod': - return { - 'paypal_form_url': 'https://www.paypal.com/cgi-bin/webscr', - 'paypal_rest_url': 'https://api.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', - } - - def _get_providers(self, cr, uid, context=None): - providers = super(AcquirerPaypal, self)._get_providers(cr, uid, context=context) - providers.append(['paypal', 'Paypal']) - return providers - - _columns = { - 'paypal_email_account': fields.char('Paypal Email ID', required_if_provider='paypal'), - 'paypal_seller_account': fields.char( - 'Paypal Merchant ID', - help='The Merchant ID is used to ensure communications coming from Paypal are valid and secured.'), - 'paypal_use_ipn': fields.boolean('Use IPN', help='Paypal Instant Payment Notification'), - # Server 2 server - 'paypal_api_enabled': fields.boolean('Use Rest API'), - 'paypal_api_username': fields.char('Rest API Username'), - 'paypal_api_password': fields.char('Rest API Password'), - 'paypal_api_access_token': fields.char('Access Token'), - 'paypal_api_access_token_validity': fields.datetime('Access Token Validity'), - } - - _defaults = { - 'paypal_use_ipn': True, - 'fees_active': False, - 'fees_dom_fixed': 0.35, - 'fees_dom_var': 3.4, - 'fees_int_fixed': 0.35, - 'fees_int_var': 3.9, - 'paypal_api_enabled': False, - } - - def paypal_compute_fees(self, cr, uid, id, amount, currency_id, country_id, context=None): - """ Compute paypal fees. - - :param float amount: the amount to pay - :param integer country_id: an ID of a res.country, or None. This is - the customer's country, to be compared to - the acquirer company country. - :return float fees: computed fees - """ - acquirer = self.browse(cr, uid, id, context=context) - if not acquirer.fees_active: - return 0.0 - country = self.pool['res.country'].browse(cr, uid, country_id, context=context) - if country and acquirer.company_id.country_id.id == country.id: - percentage = acquirer.fees_dom_var - fixed = acquirer.fees_dom_fixed - else: - percentage = acquirer.fees_int_var - fixed = acquirer.fees_int_fixed - fees = (percentage / 100.0 * amount + fixed ) / (1 - percentage / 100.0) - return fees - - def paypal_form_generate_values(self, cr, uid, id, values, context=None): - base_url = self.pool['ir.config_parameter'].get_param(cr, SUPERUSER_ID, 'web.base.url') - acquirer = self.browse(cr, uid, id, context=context) - - paypal_tx_values = dict(values) - paypal_tx_values.update({ - 'cmd': '_xclick', - 'business': acquirer.paypal_email_account, - 'item_name': '%s: %s' % (acquirer.company_id.name, values['reference']), - 'item_number': values['reference'], - 'amount': values['amount'], - 'currency_code': values['currency'] and values['currency'].name or '', - 'address1': values.get('partner_address'), - 'city': values.get('partner_city'), - 'country': values.get('partner_country') and values.get('partner_country').name or '', - 'state': values.get('partner_state') and values.get('partner_state').name or '', - 'email': values.get('partner_email'), - 'zip_code': values.get('partner_zip'), - 'first_name': values.get('partner_first_name'), - 'last_name': values.get('partner_last_name'), - 'paypal_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), - 'handling': '%.2f' % paypal_tx_values.pop('fees', 0.0) if acquirer.fees_active else False, - 'custom': json.dumps({'return_url': '%s' % paypal_tx_values.pop('return_url')}) if paypal_tx_values.get('return_url') else False, - }) - return paypal_tx_values - - def paypal_get_form_action_url(self, cr, uid, id, context=None): - acquirer = self.browse(cr, uid, id, context=context) - return self._get_paypal_urls(cr, uid, acquirer.environment, context=context)['paypal_form_url'] - - def _paypal_s2s_get_access_token(self, cr, uid, ids, context=None): - """ - Note: see # see http://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem - for explanation why we use Authorization header instead of urllib2 - password manager - """ - res = dict.fromkeys(ids, False) - parameters = werkzeug.url_encode({'grant_type': 'client_credentials'}) - - for acquirer in self.browse(cr, uid, ids, context=context): - tx_url = self._get_paypal_urls(cr, uid, acquirer.environment)['paypal_rest_url'] - request = urllib2.Request(tx_url, parameters) - - # add other headers (https://developer.paypal.com/webapps/developer/docs/integration/direct/make-your-first-call/) - request.add_header('Accept', 'application/json') - request.add_header('Accept-Language', 'en_US') - - # add authorization header - base64string = base64.encodestring('%s:%s' % ( - acquirer.paypal_api_username, - acquirer.paypal_api_password) - ).replace('\n', '') - request.add_header("Authorization", "Basic %s" % base64string) - - request = urllib2.urlopen(request) - result = request.read() - res[acquirer.id] = json.loads(result).get('access_token') - request.close() - return res - - -class TxPaypal(osv.Model): - _inherit = 'payment.transaction' - - _columns = { - 'paypal_txn_type': fields.char('Transaction type'), - } - - # -------------------------------------------------- - # FORM RELATED METHODS - # -------------------------------------------------- - - 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: - error_msg = _('Paypal: received data with missing reference (%s) or txn_id (%s)') % (reference, txn_id) - _logger.info(error_msg) - raise ValidationError(error_msg) - - # find tx -> @TDENOTE use txn_id ? - tx_ids = self.pool['payment.transaction'].search(cr, uid, [('reference', '=', reference)], context=context) - if not tx_ids or len(tx_ids) > 1: - error_msg = 'Paypal: received data for reference %s' % (reference) - if not tx_ids: - error_msg += '; no order found' - else: - error_msg += '; multiple order found' - _logger.info(error_msg) - raise ValidationError(error_msg) - return self.browse(cr, uid, tx_ids[0], context=context) - - def _paypal_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): - invalid_parameters = [] - _logger.info('Received a notification from Paypal with IPN version %s', data.get('notify_version')) - if data.get('test_ipn'): - _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 + 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 '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 tx.payment_token_id and data.get('payer_id') != tx.payment_token_id.acquirer_ref: - invalid_parameters.append(('payer_id', data.get('payer_id'), tx.payment_token_id.acquirer_ref)) - # check seller - if data.get('receiver_id') and tx.acquirer_id.paypal_seller_account and data['receiver_id'] != tx.acquirer_id.paypal_seller_account: - invalid_parameters.append(('receiver_id', data.get('receiver_id'), tx.acquirer_id.paypal_seller_account)) - if not data.get('receiver_id') or not tx.acquirer_id.paypal_seller_account: - # Check receiver_email only if receiver_id was not checked. - # In Paypal, this is possible to configure as receiver_email a different email than the business email (the login email) - # In Odoo, there is only one field for the Paypal email: the business email. This isn't possible to set a receiver_email - # different than the business email. Therefore, if you want such a configuration in your Paypal, you are then obliged to fill - # the Merchant ID in the Paypal payment acquirer in Odoo, so the check is performed on this variable instead of the receiver_email. - # At least one of the two checks must be done, to avoid fraudsters. - if data.get('receiver_email') != tx.acquirer_id.paypal_email_account: - invalid_parameters.append(('receiver_email', data.get('receiver_email'), tx.acquirer_id.paypal_email_account)) - - return invalid_parameters - - def _paypal_form_validate(self, cr, uid, tx, data, context=None): - status = data.get('payment_status') - res = { - 'acquirer_reference': data.get('txn_id'), - 'paypal_txn_type': data.get('payment_type'), - } - if status in ['Completed', 'Processed']: - _logger.info('Validated Paypal payment for tx %s: set as done' % (tx.reference)) - res.update(state='done', date_validate=data.get('payment_date', fields.datetime.now())) - return tx.write(res) - elif status in ['Pending', 'Expired']: - _logger.info('Received notification for Paypal payment %s: set as pending' % (tx.reference)) - res.update(state='pending', state_message=data.get('pending_reason', '')) - return tx.write(res) - else: - error = 'Received unrecognized status for Paypal payment %s: %s, set as error' % (tx.reference, status) - _logger.info(error) - res.update(state='error', state_message=error) - return tx.write(res) - - # -------------------------------------------------- - # SERVER2SERVER RELATED METHODS - # -------------------------------------------------- - - def _paypal_try_url(self, request, tries=3, context=None): - """ Try to contact Paypal. Due to some issues, internal service errors - seem to be quite frequent. Several tries are done before considering - the communication as failed. - - .. versionadded:: pre-v8 saas-3 - .. warning:: - - Experimental code. You should not use it before OpenERP v8 official - release. - """ - done, res = False, None - while (not done and tries): - try: - res = urllib2.urlopen(request) - done = True - except urllib2.HTTPError as e: - res = e.read() - e.close() - if tries and res and json.loads(res)['name'] == 'INTERNAL_SERVICE_ERROR': - _logger.warning('Failed contacting Paypal, retrying (%s remaining)' % tries) - tries = tries - 1 - if not res: - pass - # raise openerp.exceptions. - result = res.read() - res.close() - return result - - def _paypal_s2s_send(self, cr, uid, values, cc_values, context=None): - """ - .. versionadded:: pre-v8 saas-3 - .. warning:: - - Experimental code. You should not use it before OpenERP v8 official - release. - """ - tx_id = self.create(cr, uid, values, context=context) - tx = self.browse(cr, uid, tx_id, context=context) - - headers = { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer %s' % tx.acquirer_id._paypal_s2s_get_access_token()[tx.acquirer_id.id], - } - data = { - 'intent': 'sale', - 'transactions': [{ - 'amount': { - 'total': '%.2f' % tx.amount, - 'currency': tx.currency_id.name, - }, - 'description': tx.reference, - }] - } - if cc_values: - data['payer'] = { - 'payment_method': 'credit_card', - 'funding_instruments': [{ - 'credit_card': { - 'number': cc_values['number'], - 'type': cc_values['brand'], - 'expire_month': cc_values['expiry_mm'], - 'expire_year': cc_values['expiry_yy'], - 'cvv2': cc_values['cvc'], - 'first_name': tx.partner_name, - 'last_name': tx.partner_name, - 'billing_address': { - 'line1': tx.partner_address, - 'city': tx.partner_city, - 'country_code': tx.partner_country_id.code, - 'postal_code': tx.partner_zip, - } - } - }] - } - else: - # TODO: complete redirect URLs - data['redirect_urls'] = { - # 'return_url': 'http://example.com/your_redirect_url/', - # 'cancel_url': 'http://example.com/your_cancel_url/', - }, - data['payer'] = { - 'payment_method': 'paypal', - } - data = json.dumps(data) - - request = urllib2.Request('https://api.sandbox.paypal.com/v1/payments/payment', data, headers) - result = self._paypal_try_url(request, tries=3, context=context) - return (tx_id, result) - - def _paypal_s2s_get_invalid_parameters(self, cr, uid, tx, data, context=None): - """ - .. versionadded:: pre-v8 saas-3 - .. warning:: - - Experimental code. You should not use it before OpenERP v8 official - release. - """ - invalid_parameters = [] - return invalid_parameters - - def _paypal_s2s_validate(self, cr, uid, tx, data, context=None): - """ - .. versionadded:: pre-v8 saas-3 - .. warning:: - - Experimental code. You should not use it before OpenERP v8 official - release. - """ - values = json.loads(data) - status = values.get('state') - if status in ['approved']: - _logger.info('Validated Paypal s2s payment for tx %s: set as done' % (tx.reference)) - tx.write({ - 'state': 'done', - 'date_validate': values.get('udpate_time', fields.datetime.now()), - 'paypal_txn_id': values['id'], - }) - return True - elif status in ['pending', 'expired']: - _logger.info('Received notification for Paypal s2s payment %s: set as pending' % (tx.reference)) - tx.write({ - 'state': 'pending', - # 'state_message': data.get('pending_reason', ''), - 'paypal_txn_id': values['id'], - }) - return True - else: - error = 'Received unrecognized status for Paypal s2s payment %s: %s, set as error' % (tx.reference, status) - _logger.info(error) - tx.write({ - 'state': 'error', - # 'state_message': error, - 'paypal_txn_id': values['id'], - }) - return False - - def _paypal_s2s_get_tx_status(self, cr, uid, tx, context=None): - """ - .. versionadded:: pre-v8 saas-3 - .. warning:: - - Experimental code. You should not use it before OpenERP v8 official - release. - """ - # TDETODO: check tx.paypal_txn_id is set - headers = { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer %s' % tx.acquirer_id._paypal_s2s_get_access_token()[tx.acquirer_id.id], - } - url = 'https://api.sandbox.paypal.com/v1/payments/payment/%s' % (tx.paypal_txn_id) - request = urllib2.Request(url, headers=headers) - data = self._paypal_try_url(request, tries=3, context=context) - return self.s2s_feedback(cr, uid, tx.id, data, context=context) diff --git a/addons/payment_paypal/tests/test_paypal.py b/addons/payment_paypal/tests/test_paypal.py index e0e6db55947..5f122e8b296 100644 --- a/addons/payment_paypal/tests/test_paypal.py +++ b/addons/payment_paypal/tests/test_paypal.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment.tests.common import PaymentAcquirerCommon -from openerp.addons.payment_paypal.controllers.main import PaypalController -from openerp.tools import mute_logger +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment.tests.common import PaymentAcquirerCommon +from odoo.addons.payment_paypal.controllers.main import PaypalController +from odoo.tools import mute_logger from lxml import objectify import urlparse @@ -13,12 +13,8 @@ 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') - # get the paypal account - model, self.paypal_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment', 'payment_acquirer_paypal') - # tde+seller@openerp.com - tde+buyer@openerp.com - tde+buyer-it@openerp.com + self.paypal = self.env.ref('payment.payment_acquirer_paypal') # some CC self.amex = (('378282246310005', '123'), ('371449635398431', '123')) @@ -36,26 +32,23 @@ class PaypalCommon(PaymentAcquirerCommon): class PaypalForm(PaypalCommon): def test_10_paypal_form_render(self): - cr, uid, context = self.cr, self.uid, {} + base_url = self.env['ir.config_parameter'].get_param('web.base.url') # be sure not to do stupid things - self.payment_acquirer.write(cr, uid, self.paypal_id, {'paypal_email_account': 'tde+paypal-facilitator@openerp.com', 'fees_active': False}, context) - paypal = self.payment_acquirer.browse(cr, uid, self.paypal_id, context) - self.assertEqual(paypal.environment, 'test', 'test without test environment') + self.paypal.write({'paypal_email_account': 'tde+paypal-facilitator@odoo.com', 'fees_active': False}) + self.assertEqual(self.paypal.environment, 'test', 'test without test environment') # ---------------------------------------- # 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, - values=self.buyer_values, - context=context) + res = self.paypal.render( + 'test_ref0', 0.01, self.currency_euro.id, + values=self.buyer_values) form_values = { 'cmd': '_xclick', - 'business': 'tde+paypal-facilitator@openerp.com', + 'business': 'tde+paypal-facilitator@odoo.com', 'item_name': 'YourCompany: test_ref0', 'item_number': 'test_ref0', 'first_name': 'Norbert', @@ -67,9 +60,9 @@ class PaypalForm(PaypalCommon): 'zip': '1000', 'country': 'Belgium', 'email': 'norbert.buyer@example.com', - 'return': '%s' % urlparse.urljoin(self.base_url, PaypalController._return_url), - 'notify_url': '%s' % urlparse.urljoin(self.base_url, PaypalController._notify_url), - 'cancel_return': '%s' % urlparse.urljoin(self.base_url, PaypalController._cancel_url), + '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), } # check form result @@ -85,26 +78,22 @@ class PaypalForm(PaypalCommon): ) def test_11_paypal_form_with_fees(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.environment, 'test', 'test without test environment') + self.assertEqual(self.paypal.environment, 'test', 'test without test environment') # update acquirer: compute fees - self.payment_acquirer.write(cr, uid, self.paypal_id, { + self.paypal.write({ '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, - 'test_ref0', 12.50, self.currency_euro_id, - values=self.buyer_values, - context=context) + res = self.paypal.render( + 'test_ref0', 12.50, self.currency_euro.id, + values=self.buyer_values) # check form result handling_found = False @@ -116,12 +105,10 @@ class PaypalForm(PaypalCommon): self.assertEqual(form_input.get('value'), '1.57', 'paypal: wrong computed fees') self.assertTrue(handling_found, 'paypal: fees_active did not add handling input in rendered form') - @mute_logger('openerp.addons.payment_paypal.models.paypal', 'ValidationError') + @mute_logger('openerp.addons.payment_paypal.models.payment', 'ValidationError') def test_20_paypal_form_management(self): - cr, uid, context = self.cr, self.uid, {} # be sure not to do stupid things - paypal = self.payment_acquirer.browse(cr, uid, self.paypal_id, context) - self.assertEqual(paypal.environment, 'test', 'test without test environment') + self.assertEqual(self.paypal.environment, 'test', 'test without test environment') # typical data posted by paypal after client has successfully paid paypal_post_data = { @@ -160,7 +147,7 @@ class PaypalForm(PaypalCommon): 'address_status': u'unconfirmed', 'mc_currency': u'EUR', 'shipping': u'0.00', - 'payer_email': u'tde+buyer@openerp.com', + 'payer_email': u'tde+buyer@odoo.com', 'payment_type': u'instant', 'mc_gross': u'1.95', 'ipn_track_id': u'866df2ccd444b', @@ -169,39 +156,35 @@ class PaypalForm(PaypalCommon): # should raise error about unknown tx with self.assertRaises(ValidationError): - self.payment_transaction.form_feedback(cr, uid, paypal_post_data, 'paypal', context=context) + self.env['payment.transaction'].form_feedback(paypal_post_data, 'paypal') # create tx - tx_id = self.payment_transaction.create( - cr, uid, { - 'amount': 1.95, - 'acquirer_id': self.paypal_id, - 'currency_id': self.currency_euro_id, - 'reference': 'test_ref_2', - 'partner_name': 'Norbert Buyer', - 'partner_country_id': self.country_france_id, - }, context=context - ) + tx = self.env['payment.transaction'].create({ + 'amount': 1.95, + 'acquirer_id': self.paypal.id, + 'currency_id': self.currency_euro.id, + 'reference': 'test_ref_2', + 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france.id}) + # validate it - self.payment_transaction.form_feedback(cr, uid, paypal_post_data, 'paypal', context=context) + tx.form_feedback(paypal_post_data, 'paypal') # check - tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) self.assertEqual(tx.state, 'pending', 'paypal: wrong state after receiving a valid pending notification') self.assertEqual(tx.state_message, 'multi_currency', 'paypal: wrong state message after receiving a valid pending notification') self.assertEqual(tx.acquirer_reference, '08D73520KX778924N', 'paypal: wrong txn_id after receiving a valid pending notification') self.assertFalse(tx.date_validate, 'paypal: validation date should not be updated whenr receiving pending notification') # update tx - self.payment_transaction.write(cr, uid, [tx_id], { + tx.write({ 'state': 'draft', - 'acquirer_reference': False, - }, context=context) + 'acquirer_reference': False}) + # update notification from paypal paypal_post_data['payment_status'] = 'Completed' # validate it - self.payment_transaction.form_feedback(cr, uid, paypal_post_data, 'paypal', context=context) + tx.form_feedback(paypal_post_data, 'paypal') # check - tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) self.assertEqual(tx.state, 'done', 'paypal: wrong state after receiving a valid pending notification') self.assertEqual(tx.acquirer_reference, '08D73520KX778924N', 'paypal: wrong txn_id after receiving a valid pending notification') self.assertEqual(tx.date_validate, '2013-11-18 03:21:19', 'paypal: wrong validation date') diff --git a/addons/payment_paypal/views/res_config_view.xml b/addons/payment_paypal/views/account_config_settings_views.xml similarity index 96% rename from addons/payment_paypal/views/res_config_view.xml rename to addons/payment_paypal/views/account_config_settings_views.xml index 30e79a4992d..cfe6ce12748 100644 --- a/addons/payment_paypal/views/res_config_view.xml +++ b/addons/payment_paypal/views/account_config_settings_views.xml @@ -1,5 +1,5 @@ - + @@ -12,4 +12,4 @@ - + diff --git a/addons/payment_paypal/views/paypal.xml b/addons/payment_paypal/views/payment_paypal_templates.xml similarity index 99% rename from addons/payment_paypal/views/paypal.xml rename to addons/payment_paypal/views/payment_paypal_templates.xml index 6491f0e8ed5..495aac7158e 100644 --- a/addons/payment_paypal/views/paypal.xml +++ b/addons/payment_paypal/views/payment_paypal_templates.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/payment_paypal/views/payment_acquirer.xml b/addons/payment_paypal/views/payment_views.xml similarity index 98% rename from addons/payment_paypal/views/payment_acquirer.xml rename to addons/payment_paypal/views/payment_views.xml index a624366c1ed..01a28e9c04f 100644 --- a/addons/payment_paypal/views/payment_acquirer.xml +++ b/addons/payment_paypal/views/payment_views.xml @@ -1,5 +1,5 @@ - + @@ -37,4 +37,4 @@ - + diff --git a/addons/payment_payumoney/__openerp__.py b/addons/payment_payumoney/__openerp__.py index 62e92bb78d8..ce4a57eb68c 100644 --- a/addons/payment_payumoney/__openerp__.py +++ b/addons/payment_payumoney/__openerp__.py @@ -12,9 +12,9 @@ """, 'depends': ['payment'], 'data': [ - 'views/payumoney.xml', - 'views/payment_acquirer.xml', - 'data/payment_payumoney_data.xml', + 'views/payment_views.xml', + 'views/payment_payumoney_templates.xml', + 'data/payment_acquirer_data.xml', ], 'license': 'OEEL-1', } diff --git a/addons/payment_payumoney/data/payment_payumoney_data.xml b/addons/payment_payumoney/data/payment_acquirer_data.xml similarity index 100% rename from addons/payment_payumoney/data/payment_payumoney_data.xml rename to addons/payment_payumoney/data/payment_acquirer_data.xml diff --git a/addons/payment_payumoney/models/__init__.py b/addons/payment_payumoney/models/__init__.py index 29abdbcb773..8ba584ff326 100644 --- a/addons/payment_payumoney/models/__init__.py +++ b/addons/payment_payumoney/models/__init__.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import payment_acquirer -import payment_transaction +import payment diff --git a/addons/payment_payumoney/models/payment.py b/addons/payment_payumoney/models/payment.py new file mode 100644 index 00000000000..38d89c2c7f9 --- /dev/null +++ b/addons/payment_payumoney/models/payment.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import hashlib +import urlparse + +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.tools.float_utils import float_compare + +import logging + +_logger = logging.getLogger(__name__) + + +class PaymentAcquirerPayumoney(models.Model): + _inherit = 'payment.acquirer' + + provider = fields.Selection(selection_add=[('payumoney', 'PayUmoney')]) + payumoney_merchant_key = fields.Char(string='Merchant Key', required_if_provider='payumoney') + payumoney_merchant_salt = fields.Char(string='Merchant Salt', required_if_provider='payumoney') + + def _get_payumoney_urls(self, environment): + """ PayUmoney URLs""" + if environment == 'prod': + return {'payumoney_form_url': 'https://secure.payu.in/_payment'} + else: + return {'payumoney_form_url': 'https://test.payu.in/_payment'} + + def _payumoney_generate_sign(self, inout, values): + """ Generate the shasign for incoming or outgoing communications. + :param self: the self browse record. It should have a shakey in shakey out + :param string inout: 'in' (odoo contacting payumoney) or 'out' (payumoney + contacting odoo). + :param dict values: transaction values + + :return string: shasign + """ + if inout not in ('in', 'out'): + raise Exception("Type must be 'in' or 'out'") + + if inout == 'in': + keys = "key|txnid|amount|productinfo|firstname|email|udf1|||||||||".split('|') + sign = ''.join('%s|' % (values.get(k) or '') for k in keys) + sign += self.payumoney_merchant_salt or '' + else: + keys = "|status||||||||||udf1|email|firstname|productinfo|amount|txnid".split('|') + sign = ''.join('%s|' % (values.get(k) or '') for k in keys) + sign = self.payumoney_merchant_salt + sign + self.payumoney_merchant_key + + shasign = hashlib.sha512(sign).hexdigest() + return shasign + + @api.multi + def payumoney_form_generate_values(self, values): + self.ensure_one() + base_url = self.env['ir.config_parameter'].get_param('web.base.url') + payumoney_values = dict(values, + key=self.payumoney_merchant_key, + txnid=values['reference'], + amount=values['amount'], + productinfo=values['reference'], + firstname=values.get('partner_name'), + email=values.get('partner_email'), + phone=values.get('partner_phone'), + service_provider='payu_paisa', + surl='%s' % urlparse.urljoin(base_url, '/payment/payumoney/return'), + furl='%s' % urlparse.urljoin(base_url, '/payment/payumoney/error'), + curl='%s' % urlparse.urljoin(base_url, '/payment/payumoney/cancel') + ) + + payumoney_values['udf1'] = payumoney_values.pop('return_url', '/') + payumoney_values['hash'] = self._payumoney_generate_sign('in', payumoney_values) + return payumoney_values + + @api.multi + def payumoney_get_form_action_url(self): + self.ensure_one() + return self._get_payumoney_urls(self.environment)['payumoney_form_url'] + + +class PaymentTransactionPayumoney(models.Model): + _inherit = 'payment.transaction' + + @api.model + def _payumoney_form_get_tx_from_data(self, data): + """ Given a data dict coming from payumoney, verify it and find the related + transaction record. """ + reference = data.get('txnid') + pay_id = data.get('mihpayid') + shasign = data.get('hash') + if not reference or not pay_id or not shasign: + raise ValidationError(_('PayUmoney: received data with missing reference (%s) or pay_id (%s) or shashign (%s)') % (reference, pay_id, shasign)) + + transaction = self.search([('reference', '=', reference)]) + + if not transaction: + error_msg = (_('PayUmoney: received data for reference %s; no order found') % (reference)) + raise ValidationError(error_msg) + elif len(transaction) > 1: + error_msg = (_('PayUmoney: received data for reference %s; multiple orders found') % (reference)) + raise ValidationError(error_msg) + + #verify shasign + shasign_check = transaction.acquirer_id._payumoney_generate_sign('out', data) + if shasign_check.upper() != shasign.upper(): + raise ValidationError(_('PayUmoney: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)) + return transaction + + @api.multi + def _payumoney_form_get_invalid_parameters(self, data): + invalid_parameters = [] + + if self.acquirer_reference and data.get('mihpayid') != self.acquirer_reference: + invalid_parameters.append( + ('Transaction Id', data.get('mihpayid'), self.acquirer_reference)) + #check what is buyed + if float_compare(float(data.get('amount', '0.0')), self.amount, 2) != 0: + invalid_parameters.append( + ('Amount', data.get('amount'), '%.2f' % self.amount)) + + return invalid_parameters + + @api.multi + def _payumoney_form_validate(self, data): + status = data.get('status') + transaction_status = { + 'success': { + 'state': 'done', + 'acquirer_reference': data.get('payuMoneyId'), + 'date_validate': fields.Datetime.now(), + }, + 'pending': { + 'state': 'pending', + 'acquirer_reference': data.get('payuMoneyId'), + 'date_validate': fields.Datetime.now(), + }, + 'failure': { + 'state': 'cancel', + 'acquirer_reference': data.get('payuMoneyId'), + 'date_validate': fields.Datetime.now(), + }, + 'error': { + 'state': 'error', + 'state_message': data.get('error_Message') or _('PayUmoney: feedback error'), + 'acquirer_reference': data.get('payuMoneyId'), + 'date_validate': fields.Datetime.now(), + } + } + vals = transaction_status.get(status, False) + if not vals: + vals = transaction_status['error'] + _logger.info(vals['state_message']) + return self.write(vals) diff --git a/addons/payment_payumoney/models/payment_acquirer.py b/addons/payment_payumoney/models/payment_acquirer.py deleted file mode 100644 index 0abe333e73d..00000000000 --- a/addons/payment_payumoney/models/payment_acquirer.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import hashlib -import urlparse - -from odoo import api, fields, models, _ - - -class PaymentAcquirerPayumoney(models.Model): - _inherit = 'payment.acquirer' - - payumoney_merchant_key = fields.Char(string='Merchant Key', required_if_provider='payumoney') - payumoney_merchant_salt = fields.Char(string='Merchant Salt', required_if_provider='payumoney') - - def _get_payumoney_urls(self, environment): - """ PayUmoney URLs""" - if environment == 'prod': - return {'payumoney_form_url': 'https://secure.payu.in/_payment'} - else: - return {'payumoney_form_url': 'https://test.payu.in/_payment'} - - @api.model - def _get_providers(self): - providers = super(PaymentAcquirerPayumoney, self)._get_providers() - providers.append(['payumoney', 'PayUmoney']) - return providers - - def _payumoney_generate_sign(self, inout, values): - """ Generate the shasign for incoming or outgoing communications. - :param self: the self browse record. It should have a shakey in shakey out - :param string inout: 'in' (odoo contacting payumoney) or 'out' (payumoney - contacting odoo). - :param dict values: transaction values - - :return string: shasign - """ - if inout not in ('in', 'out'): - raise Exception("Type must be 'in' or 'out'") - - if inout == 'in': - keys = "key|txnid|amount|productinfo|firstname|email|udf1|||||||||".split('|') - sign = ''.join('%s|' % (values.get(k) or '') for k in keys) - sign += self.payumoney_merchant_salt or '' - else: - keys = "|status||||||||||udf1|email|firstname|productinfo|amount|txnid".split('|') - sign = ''.join('%s|' % (values.get(k) or '') for k in keys) - sign = self.payumoney_merchant_salt + sign + self.payumoney_merchant_key - - shasign = hashlib.sha512(sign).hexdigest() - return shasign - - @api.multi - def payumoney_form_generate_values(self, values): - self.ensure_one() - base_url = self.env['ir.config_parameter'].get_param('web.base.url') - payumoney_values = dict(values, - key=self.payumoney_merchant_key, - txnid=values['reference'], - amount=values['amount'], - productinfo=values['reference'], - firstname=values.get('partner_name'), - email=values.get('partner_email'), - phone=values.get('partner_phone'), - service_provider='payu_paisa', - surl='%s' % urlparse.urljoin(base_url, '/payment/payumoney/return'), - furl='%s' % urlparse.urljoin(base_url, '/payment/payumoney/error'), - curl='%s' % urlparse.urljoin(base_url, '/payment/payumoney/cancel') - ) - - payumoney_values['udf1'] = payumoney_values.pop('return_url', '/') - payumoney_values['hash'] = self._payumoney_generate_sign('in', payumoney_values) - return payumoney_values - - @api.multi - def payumoney_get_form_action_url(self): - self.ensure_one() - return self._get_payumoney_urls(self.environment)['payumoney_form_url'] diff --git a/addons/payment_payumoney/models/payment_transaction.py b/addons/payment_payumoney/models/payment_transaction.py deleted file mode 100644 index b502307b467..00000000000 --- a/addons/payment_payumoney/models/payment_transaction.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -import logging - -from odoo import api, fields, models, _ -from odoo.addons.payment.models.payment_acquirer import ValidationError -from odoo.tools.float_utils import float_compare - - -_logger = logging.getLogger(__name__) - - -class PaymentTransactionPayumoney(models.Model): - _inherit = 'payment.transaction' - - @api.model - def _payumoney_form_get_tx_from_data(self, data): - """ Given a data dict coming from payumoney, verify it and find the related - transaction record. """ - reference = data.get('txnid') - pay_id = data.get('mihpayid') - shasign = data.get('hash') - if not reference or not pay_id or not shasign: - raise ValidationError(_('PayUmoney: received data with missing reference (%s) or pay_id (%s) or shashign (%s)') % (reference, pay_id, shasign)) - - transaction = self.search([('reference', '=', reference)]) - - if not transaction: - error_msg = (_('PayUmoney: received data for reference %s; no order found') % (reference)) - raise ValidationError(error_msg) - elif len(transaction) > 1: - error_msg = (_('PayUmoney: received data for reference %s; multiple orders found') % (reference)) - raise ValidationError(error_msg) - - #verify shasign - shasign_check = transaction.acquirer_id._payumoney_generate_sign('out', data) - if shasign_check.upper() != shasign.upper(): - raise ValidationError(_('PayUmoney: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)) - return transaction - - @api.model - def _payumoney_form_get_invalid_parameters(self, transaction, data): - invalid_parameters = [] - - if transaction.acquirer_reference and data.get('mihpayid') != transaction.acquirer_reference: - invalid_parameters.append( - ('Transaction Id', data.get('mihpayid'), transaction.acquirer_reference)) - #check what is buyed - if float_compare(float(data.get('amount', '0.0')), transaction.amount, 2) != 0: - invalid_parameters.append( - ('Amount', data.get('amount'), '%.2f' % transaction.amount)) - - return invalid_parameters - - @api.model - def _payumoney_form_validate(self, transaction, data): - status = data.get('status') - transaction_status = { - 'success': { - 'state': 'done', - 'acquirer_reference': data.get('payuMoneyId'), - 'date_validate': fields.Datetime.now(), - }, - 'pending': { - 'state': 'pending', - 'acquirer_reference': data.get('payuMoneyId'), - 'date_validate': fields.Datetime.now(), - }, - 'failure': { - 'state': 'cancel', - 'acquirer_reference': data.get('payuMoneyId'), - 'date_validate': fields.Datetime.now(), - }, - 'error': { - 'state': 'error', - 'state_message': data.get('error_Message') or _('PayUmoney: feedback error'), - 'acquirer_reference': data.get('payuMoneyId'), - 'date_validate': fields.Datetime.now(), - } - } - vals = transaction_status.get(status, False) - if not vals: - vals = transaction_status['error'] - _logger.info(vals['state_message']) - return transaction.write(vals) diff --git a/addons/payment_payumoney/views/payumoney.xml b/addons/payment_payumoney/views/payment_payumoney_templates.xml similarity index 100% rename from addons/payment_payumoney/views/payumoney.xml rename to addons/payment_payumoney/views/payment_payumoney_templates.xml diff --git a/addons/payment_payumoney/views/payment_acquirer.xml b/addons/payment_payumoney/views/payment_views.xml similarity index 100% rename from addons/payment_payumoney/views/payment_acquirer.xml rename to addons/payment_payumoney/views/payment_views.xml diff --git a/addons/payment_sips/__openerp__.py b/addons/payment_sips/__openerp__.py index 08298fbfd3f..a8c608d8774 100644 --- a/addons/payment_sips/__openerp__.py +++ b/addons/payment_sips/__openerp__.py @@ -29,9 +29,9 @@ Works with Worldline keys version 2.0, contains implementation of payments acquirer using Worldline SIPS.""", 'depends': ['payment'], 'data': [ - 'views/sips.xml', - 'views/payment_acquirer.xml', - 'data/sips.xml', + 'views/payment_views.xml', + 'views/payment_sips_templates.xml', + 'data/payment_acquirer_data.xml', ], 'installable': True, } diff --git a/addons/payment_sips/controllers/main.py b/addons/payment_sips/controllers/main.py index 0bf4a5f52b3..41ad8508210 100644 --- a/addons/payment_sips/controllers/main.py +++ b/addons/payment_sips/controllers/main.py @@ -4,8 +4,8 @@ import json import logging import werkzeug -from openerp import http -from openerp.http import request +from odoo import http +from odoo.http import request _logger = logging.getLogger(__name__) @@ -18,27 +18,20 @@ class SipsController(http.Controller): """ Extract the return URL from the data coming from sips. """ return_url = post.pop('return_url', '') if not return_url: - tx_obj = request.registry['payment.transaction'] - data = tx_obj._sips_data_to_object(post.get('Data')) + Tx = request.env['payment.transaction'] + data = Tx._sips_data_to_object(post.get('Data')) custom = json.loads(data.pop('returnContext', False) or '{}') return_url = custom.get('return_url', '/') return return_url def sips_validate_data(self, **post): - res = False - env = request.env - tx_obj = env['payment.transaction'] - acquirer_obj = env['payment.acquirer'] - - sips = acquirer_obj.search([('provider', '=', 'sips')], limit=1) - + sips = request.env['payment.acquirer'].search([('provider', '=', 'sips')], limit=1) security = sips._sips_generate_shasign(post) if security == post['Seal']: _logger.debug('Sips: validated data') - res = tx_obj.sudo().form_feedback(post, 'sips') - else: - _logger.warning('Sips: data are corrupted') - return res + return request.env['payment.transaction'].sudo().form_feedback(post, 'sips') + _logger.warning('Sips: data are corrupted') + return False @http.route([ '/payment/sips/ipn/'], diff --git a/addons/payment_sips/data/sips.xml b/addons/payment_sips/data/payment_acquirer_data.xml similarity index 97% rename from addons/payment_sips/data/sips.xml rename to addons/payment_sips/data/payment_acquirer_data.xml index 89ec787ea5e..e841c411f5b 100644 --- a/addons/payment_sips/data/sips.xml +++ b/addons/payment_sips/data/payment_acquirer_data.xml @@ -1,5 +1,5 @@ - + @@ -15,4 +15,4 @@ - + diff --git a/addons/payment_sips/models/__init__.py b/addons/payment_sips/models/__init__.py index 365abd07d65..10b988e0c28 100644 --- a/addons/payment_sips/models/__init__.py +++ b/addons/payment_sips/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -import sips +import payment diff --git a/addons/payment_sips/models/sips.py b/addons/payment_sips/models/payment.py similarity index 83% rename from addons/payment_sips/models/sips.py rename to addons/payment_sips/models/payment.py index df669fd3804..7ce02a9d147 100644 --- a/addons/payment_sips/models/sips.py +++ b/addons/payment_sips/models/payment.py @@ -4,13 +4,12 @@ import json import logging from hashlib import sha256 import urlparse -import unicodedata -from openerp import models, fields, api -from openerp.tools.float_utils import float_compare -from openerp.tools.translate import _ -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.addons.payment_sips.controllers.main import SipsController +from odoo import models, fields, api +from odoo.tools.float_utils import float_compare +from odoo.tools.translate import _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment_sips.controllers.main import SipsController _logger = logging.getLogger(__name__) @@ -36,12 +35,11 @@ CURRENCY_CODES = { class AcquirerSips(models.Model): _inherit = 'payment.acquirer' - # Fields - sips_merchant_id = fields.Char('SIPS API User Password', - required_if_provider='sips') + + provider = fields.Selection(selection_add=[('sips', 'Sips')]) + sips_merchant_id = fields.Char('SIPS API User Password', required_if_provider='sips') sips_secret = fields.Char('SIPS Secret', size=64, required_if_provider='sips') - # Methods def _get_sips_urls(self, environment): """ Worldline SIPS URLS """ url = { @@ -50,12 +48,6 @@ class AcquirerSips(models.Model): return {'sips_form_url': url.get(environment, url['test']), } - @api.model - def _get_providers(self): - providers = super(AcquirerSips, self)._get_providers() - providers.append(['sips', 'Sips']) - return providers - def _sips_generate_shasign(self, values): """ Generate the shasign for incoming or outgoing communications. :param dict values: transaction values @@ -124,7 +116,6 @@ class AcquirerSips(models.Model): class TxSips(models.Model): _inherit = 'payment.transaction' - # sips status _sips_valid_tx_status = ['00'] _sips_wait_tx_status = ['90', '99'] _sips_refused_tx_status = ['05', '14', '34', '54', '75', '97'] @@ -135,6 +126,7 @@ class TxSips(models.Model): # -------------------------------------------------- # FORM RELATED METHODS # -------------------------------------------------- + def _sips_data_to_object(self, data): res = {} for element in data.split('|'): @@ -165,25 +157,25 @@ class TxSips(models.Model): raise ValidationError(error_msg) return payment_tx - @api.model - def _sips_form_get_invalid_parameters(self, tx, data): + @api.multi + def _sips_form_get_invalid_parameters(self, data): invalid_parameters = [] data = self._sips_data_to_object(data.get('Data')) # TODO: txn_id: should be false at draft, set afterwards, and verified with txn details - if tx.acquirer_reference and data.get('transactionReference') != tx.acquirer_reference: - invalid_parameters.append(('transactionReference', data.get('transactionReference'), tx.acquirer_reference)) + if self.acquirer_reference and data.get('transactionReference') != self.acquirer_reference: + invalid_parameters.append(('transactionReference', data.get('transactionReference'), self.acquirer_reference)) # check what is bought - if float_compare(float(data.get('amount', '0.0')) / 100, tx.amount, 2) != 0: - invalid_parameters.append(('amount', data.get('amount'), '%.2f' % tx.amount)) - if tx.partner_reference and data.get('customerId') != tx.partner_reference: - invalid_parameters.append(('customerId', data.get('customerId'), tx.partner_reference)) + if float_compare(float(data.get('amount', '0.0')) / 100, self.amount, 2) != 0: + invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount)) + if self.partner_reference and data.get('customerId') != self.partner_reference: + invalid_parameters.append(('customerId', data.get('customerId'), self.partner_reference)) return invalid_parameters - @api.model - def _sips_form_validate(self, tx, data): + @api.multi + def _sips_form_validate(self, data): data = self._sips_data_to_object(data.get('Data')) status = data.get('responseCode') data = { @@ -195,35 +187,35 @@ class TxSips(models.Model): res = False if status in self._sips_valid_tx_status: msg = 'Payment for tx ref: %s, got response [%s], set as done.' % \ - (tx.reference, status) + (self.reference, status) _logger.info(msg) data.update(state='done', state_message=msg) res = True elif status in self._sips_error_tx_status: msg = 'Payment for tx ref: %s, got response [%s], set as ' \ - 'error.' % (tx.reference, status) + 'error.' % (self.reference, status) data.update(state='error', state_message=msg) elif status in self._sips_wait_tx_status: msg = 'Received wait status for payment ref: %s, got response ' \ - '[%s], set as error.' % (tx.reference, status) + '[%s], set as error.' % (self.reference, status) data.update(state='error', state_message=msg) elif status in self._sips_refused_tx_status: msg = 'Received refused status for payment ref: %s, got response' \ - ' [%s], set as error.' % (tx.reference, status) + ' [%s], set as error.' % (self.reference, status) data.update(state='error', state_message=msg) elif status in self._sips_pending_tx_status: msg = 'Payment ref: %s, got response [%s] set as pending.' \ - % (tx.reference, status) + % (self.reference, status) data.update(state='pending', state_message=msg) elif status in self._sips_cancel_tx_status: msg = 'Received notification for payment ref: %s, got response ' \ - '[%s], set as cancel.' % (tx.reference, status) + '[%s], set as cancel.' % (self.reference, status) data.update(state='cancel', state_message=msg) else: msg = 'Received unrecognized status for payment ref: %s, got ' \ - 'response [%s], set as error.' % (tx.reference, status) + 'response [%s], set as error.' % (self.reference, status) data.update(state='error', state_message=msg) _logger.info(msg) - tx.write(data) + self.write(data) return res diff --git a/addons/payment_sips/views/sips.xml b/addons/payment_sips/views/payment_sips_templates.xml similarity index 97% rename from addons/payment_sips/views/sips.xml rename to addons/payment_sips/views/payment_sips_templates.xml index 2aeda71c3aa..1d8b487d379 100644 --- a/addons/payment_sips/views/sips.xml +++ b/addons/payment_sips/views/payment_sips_templates.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/payment_sips/views/payment_acquirer.xml b/addons/payment_sips/views/payment_views.xml similarity index 97% rename from addons/payment_sips/views/payment_acquirer.xml rename to addons/payment_sips/views/payment_views.xml index 9336cdd7ae4..4e5585af458 100644 --- a/addons/payment_sips/views/payment_acquirer.xml +++ b/addons/payment_sips/views/payment_views.xml @@ -1,5 +1,5 @@ - + @@ -17,4 +17,4 @@ - + diff --git a/addons/payment_stripe/__openerp__.py b/addons/payment_stripe/__openerp__.py index 80e5f5395c7..60e629dde85 100644 --- a/addons/payment_stripe/__openerp__.py +++ b/addons/payment_stripe/__openerp__.py @@ -8,10 +8,9 @@ 'description': """Stripe Payment Acquirer""", 'depends': ['payment'], 'data': [ - 'views/stripe.xml', - 'views/payment_acquirer.xml', - 'views/payment_stripe_template.xml', - 'data/payment_stripe_data.xml', + 'views/payment_views.xml', + 'views/payment_stripe_templates.xml', + 'data/payment_acquirer_data.xml', ], 'images': ['static/description/icon.png'], 'installable': True, diff --git a/addons/payment_stripe/data/payment_stripe_data.xml b/addons/payment_stripe/data/payment_acquirer_data.xml similarity index 100% rename from addons/payment_stripe/data/payment_stripe_data.xml rename to addons/payment_stripe/data/payment_acquirer_data.xml diff --git a/addons/payment_stripe/models/__init__.py b/addons/payment_stripe/models/__init__.py index fa88cddcbb1..10b988e0c28 100644 --- a/addons/payment_stripe/models/__init__.py +++ b/addons/payment_stripe/models/__init__.py @@ -1,5 +1,3 @@ # -*- coding: utf-8 -*- -import payment_transaction -import payment_acquirer -import payment_token +import payment diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py new file mode 100644 index 00000000000..59f9b1a8c82 --- /dev/null +++ b/addons/payment_stripe/models/payment.py @@ -0,0 +1,206 @@ +# coding: utf-8 + +import logging +import requests + +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval + +_logger = logging.getLogger(__name__) + +# Force the API version to avoid breaking in case of update on Stripe side +# cf https://stripe.com/docs/api#versioning +# changelog https://stripe.com/docs/upgrades#api-changelog +STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'} + + +class PaymentAcquirerStripe(models.Model): + _inherit = 'payment.acquirer' + + provider = fields.Selection(selection_add=[('stripe', 'Stripe')]) + stripe_secret_key = fields.Char(required_if_provider='stripe') + stripe_publishable_key = fields.Char(required_if_provider='stripe') + stripe_image_url = fields.Char( + "Checkout Image URL", + help="A relative or absolute URL pointing to a square image of your " + "brand or product. As defined in your Stripe profile. See: " + "https://stripe.com/docs/checkout") + + @api.multi + def stripe_form_generate_values(self, tx_values): + self.ensure_one() + stripe_tx_values = dict(tx_values) + temp_stripe_tx_values = { + 'amount': tx_values.get('amount'), + 'currency': tx_values.get('currency') and tx_values.get('currency').name or '', + 'address_line1': tx_values['partner_address'], + 'address_city': tx_values['partner_city'], + 'address_country': tx_values['partner_country'] and tx_values['partner_country'].name or '', + 'email': tx_values['partner_email'], + 'address_zip': tx_values['partner_zip'], + 'name': tx_values['partner_name'], + 'phone': tx_values['partner_phone'], + } + + temp_stripe_tx_values['returndata'] = stripe_tx_values.pop('return_url', '') + stripe_tx_values.update(temp_stripe_tx_values) + return stripe_tx_values + + @api.model + def _get_stripe_api_url(self): + return 'api.stripe.com/v1' + + @api.model + def stripe_s2s_form_process(self, data): + payment_token = self.env['payment.token'].sudo().create({ + 'cc_number': data['cc_number'], + 'cc_holder_name': data['cc_holder_name'], + 'cc_expiry': data['cc_expiry'], + 'cc_brand': data['cc_brand'], + 'cvc': data['cvc'], + 'acquirer_id': int(data['acquirer_id']), + 'partner_id': int(data['partner_id']) + }) + return payment_token.id + + @api.multi + def stripe_s2s_form_validate(self, data): + self.ensure_one() + + # mandatory fields + for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: + if not data.get(field_name): + return False + return True + + +class PaymentTransactionStripe(models.Model): + _inherit = 'payment.transaction' + + def _create_stripe_charge(self, acquirer_ref=None, tokenid=None): + api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url()) + charge_params = { + 'amount': int(self.amount*100), # Stripe takes amount in cents (https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support) + 'currency': self.currency_id.name, + 'metadata[reference]': self.reference + } + if acquirer_ref: + charge_params['customer'] = acquirer_ref + if tokenid: + charge_params['card'] = str(tokenid) + r = requests.post(api_url_charge, + auth=(self.acquirer_id.stripe_secret_key, ''), + params=charge_params, + headers=STRIPE_HEADERS) + return r.json() + + @api.multi + def stripe_s2s_do_transaction(self, **kwargs): + self.ensure_one() + result = self._create_stripe_charge(acquirer_ref=self.payment_token_id.acquirer_ref) + return self._stripe_s2s_validate_tree(result) + + @api.model + def _stripe_form_get_tx_from_data(self, data): + """ Given a data dict coming from stripe, verify it and find the related + transaction record. """ + reference = data['metadata']['reference'] + if not reference: + error_msg = _('Stripe: invalid reply received from provider, missing reference') + _logger.error(error_msg, data['metadata']) + raise ValidationError(error_msg) + tx = self.search([('reference', '=', reference)]) + if not tx: + error_msg = (_('Stripe: no order found for reference %s') % reference) + _logger.error(error_msg) + raise ValidationError(error_msg) + elif len(tx) > 1: + error_msg = (_('Stripe: %s orders found for reference %s') % (len(tx), reference)) + _logger.error(error_msg) + raise ValidationError(error_msg) + return tx[0] + + @api.multi + def _stripe_s2s_validate_tree(self, tree): + self.ensure_one() + if self.state not in ('draft', 'pending'): + _logger.info('Stripe: trying to validate an already validated tx (ref %s)', self.reference) + return True + + status = tree.get('status') + if status == 'succeeded': + self.write({ + 'state': 'done', + 'date_validate': fields.datetime.now(), + 'acquirer_reference': tree.get('id'), + }) + if self.callback_eval: + safe_eval(self.callback_eval, {'self': self}) + return True + else: + error = tree['error']['message'] + _logger.warn(error) + self.sudo().write({ + 'state': 'error', + 'state_message': error, + 'acquirer_reference': tree.get('id'), + 'date_validate': fields.datetime.now(), + }) + return False + + @api.multi + def _stripe_form_get_invalid_parameters(self, data): + invalid_parameters = [] + reference = data['metadata']['reference'] + if reference != self.reference: + invalid_parameters.append(('Reference', reference, self.reference)) + return invalid_parameters + + @api.multi + def _stripe_form_validate(self, data): + return self._stripe_s2s_validate_tree(data) + + +class PaymentTokenStripe(models.Model): + _inherit = 'payment.token' + + @api.model + def stripe_create(self, values): + res = None + payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) + url_token = 'https://%s/tokens' % payment_acquirer._get_stripe_api_url() + url_customer = 'https://%s/customers' % payment_acquirer._get_stripe_api_url() + if values['cc_number']: + payment_params = { + 'card[number]': values['cc_number'].replace(' ', ''), + 'card[exp_month]': str(values['cc_expiry'][:2]), + 'card[exp_year]': str(values['cc_expiry'][-2:]), + 'card[cvc]': values['cvc'], + } + r = requests.post(url_token, + auth=(payment_acquirer.stripe_secret_key, ''), + params=payment_params, + headers=STRIPE_HEADERS) + token = r.json() + if token.get('id'): + customer_params = { + 'source': token['id'] + } + r = requests.post(url_customer, + auth=(payment_acquirer.stripe_secret_key, ''), + params=customer_params, + headers=STRIPE_HEADERS) + customer = r.json() + res = { + 'acquirer_ref': customer['id'], + 'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], values['cc_holder_name']) + } + elif token.get('error'): + raise UserError(token['error']['message']) + + # pop credit card info to info sent to create + for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: + values.pop(field_name) + return res diff --git a/addons/payment_stripe/models/payment_acquirer.py b/addons/payment_stripe/models/payment_acquirer.py deleted file mode 100644 index 4dad6b15e5f..00000000000 --- a/addons/payment_stripe/models/payment_acquirer.py +++ /dev/null @@ -1,72 +0,0 @@ -# coding: utf-8 - -from odoo import api, fields, models - -# Force the API version to avoid breaking in case of update on Stripe side -# cf https://stripe.com/docs/api#versioning -# changelog https://stripe.com/docs/upgrades#api-changelog -STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'} - - -class PaymentAcquirerStripe(models.Model): - _inherit = 'payment.acquirer' - - stripe_secret_key = fields.Char(required_if_provider='stripe') - stripe_publishable_key = fields.Char(required_if_provider='stripe') - stripe_image_url = fields.Char("Checkout Image URL", - help="A relative or absolute URL pointing to a square image of your " - "brand or product. As defined in your Stripe profile. See: " - "https://stripe.com/docs/checkout") - - @api.model - def _get_providers(self): - providers = super(PaymentAcquirerStripe, self)._get_providers() - providers.append(['stripe', 'Stripe']) - return providers - - @api.multi - def stripe_form_generate_values(self, tx_values): - self.ensure_one() - stripe_tx_values = dict(tx_values) - temp_stripe_tx_values = { - 'amount': tx_values.get('amount'), - 'currency': tx_values.get('currency') and tx_values.get('currency').name or '', - 'address_line1': tx_values['partner_address'], - 'address_city': tx_values['partner_city'], - 'address_country': tx_values['partner_country'] and tx_values['partner_country'].name or '', - 'email': tx_values['partner_email'], - 'address_zip': tx_values['partner_zip'], - 'name': tx_values['partner_name'], - 'phone': tx_values['partner_phone'], - } - - temp_stripe_tx_values['returndata'] = stripe_tx_values.pop('return_url', '') - stripe_tx_values.update(temp_stripe_tx_values) - return stripe_tx_values - - @api.model - def _get_stripe_api_url(self): - return 'api.stripe.com/v1' - - @api.model - def stripe_s2s_form_process(self, data): - payment_token = self.env['payment.token'].sudo().create({ - 'cc_number': data['cc_number'], - 'cc_holder_name': data['cc_holder_name'], - 'cc_expiry': data['cc_expiry'], - 'cc_brand': data['cc_brand'], - 'cvc': data['cvc'], - 'acquirer_id': int(data['acquirer_id']), - 'partner_id': int(data['partner_id']) - }) - return payment_token.id - - @api.multi - def stripe_s2s_form_validate(self, data): - self.ensure_one() - - # mandatory fields - for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: - if not data.get(field_name): - return False - return True diff --git a/addons/payment_stripe/models/payment_token.py b/addons/payment_stripe/models/payment_token.py deleted file mode 100644 index ca2921c8a72..00000000000 --- a/addons/payment_stripe/models/payment_token.py +++ /dev/null @@ -1,50 +0,0 @@ -# coding: utf-8 - -import requests - -from odoo import api, models -from odoo.exceptions import UserError -from . import payment_acquirer as pa - - -class PaymentTokenStripe(models.Model): - _inherit = 'payment.token' - - @api.model - def stripe_create(self, values): - res = None - payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) - url_token = 'https://%s/tokens' % payment_acquirer._get_stripe_api_url() - url_customer = 'https://%s/customers' % payment_acquirer._get_stripe_api_url() - if values['cc_number']: - payment_params = { - 'card[number]': values['cc_number'].replace(' ', ''), - 'card[exp_month]': str(values['cc_expiry'][:2]), - 'card[exp_year]': str(values['cc_expiry'][-2:]), - 'card[cvc]': values['cvc'], - } - r = requests.post(url_token, - auth=(payment_acquirer.stripe_secret_key, ''), - params=payment_params, - headers=pa.STRIPE_HEADERS) - token = r.json() - if token.get('id'): - customer_params = { - 'source': token['id'] - } - r = requests.post(url_customer, - auth=(payment_acquirer.stripe_secret_key, ''), - params=customer_params, - headers=pa.STRIPE_HEADERS) - customer = r.json() - res = { - 'acquirer_ref': customer['id'], - 'name': 'XXXXXXXXXXXX%s - %s' % (values['cc_number'][-4:], values['cc_holder_name']) - } - elif token.get('error'): - raise UserError(token['error']['message']) - - # pop credit card info to info sent to create - for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: - values.pop(field_name) - return res diff --git a/addons/payment_stripe/models/payment_transaction.py b/addons/payment_stripe/models/payment_transaction.py deleted file mode 100644 index 5682123d71d..00000000000 --- a/addons/payment_stripe/models/payment_transaction.py +++ /dev/null @@ -1,101 +0,0 @@ -# coding: utf-8 - -import json -import logging -import requests -import urllib - -from odoo import api, fields, models, _ -from odoo.addons.payment.models.payment_acquirer import ValidationError -from odoo.tools.safe_eval import safe_eval -from . import payment_acquirer as pa - - -_logger = logging.getLogger(__name__) - - -class PaymentTransactionStripe(models.Model): - _inherit = 'payment.transaction' - - def _create_stripe_charge(self, acquirer_ref=None, tokenid=None): - api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url()) - charge_params = { - 'amount': int(self.amount*100), # Stripe takes amount in cents (https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support) - 'currency': self.currency_id.name, - 'metadata[reference]': self.reference - } - if acquirer_ref: - charge_params['customer'] = acquirer_ref - if tokenid: - charge_params['card'] = str(tokenid) - r = requests.post(api_url_charge, - auth=(self.acquirer_id.stripe_secret_key, ''), - params=charge_params, - headers=pa.STRIPE_HEADERS) - return r.json() - - @api.multi - def stripe_s2s_do_transaction(self, **kwargs): - self.ensure_one() - result = self._create_stripe_charge(acquirer_ref=self.payment_token_id.acquirer_ref) - return self._stripe_s2s_validate_tree(result) - - @api.model - def _stripe_form_get_tx_from_data(self, data): - """ Given a data dict coming from stripe, verify it and find the related - transaction record. """ - reference = data['metadata']['reference'] - if not reference: - error_msg = _('Stripe: invalid reply received from provider, missing reference') - _logger.error(error_msg, data['metadata']) - raise ValidationError(error_msg) - tx = self.search([('reference', '=', reference)]) - if not tx: - error_msg = (_('Stripe: no order found for reference %s') % reference) - _logger.error(error_msg) - raise ValidationError(error_msg) - elif len(tx) > 1: - error_msg = (_('Stripe: %s orders found for reference %s') % (len(tx), reference)) - _logger.error(error_msg) - raise ValidationError(error_msg) - return tx[0] - - @api.multi - def _stripe_s2s_validate_tree(self, tree): - self.ensure_one() - if self.state not in ('draft', 'pending'): - _logger.info('Stripe: trying to validate an already validated tx (ref %s)', self.reference) - return True - - status = tree.get('status') - if status == 'succeeded': - self.write({ - 'state': 'done', - 'date_validate': fields.datetime.now(), - 'acquirer_reference': tree.get('id'), - }) - if self.callback_eval: - safe_eval(self.callback_eval, {'self': self}) - return True - else: - error = tree['error']['message'] - _logger.warn(error) - self.sudo().write({ - 'state': 'error', - 'state_message': error, - 'acquirer_reference': tree.get('id'), - 'date_validate': fields.datetime.now(), - }) - return False - - @api.model - def _stripe_form_get_invalid_parameters(self, tx, data): - invalid_parameters = [] - reference = data['metadata']['reference'] - if reference != tx.reference: - invalid_parameters.append(('Reference', reference, tx.reference)) - return invalid_parameters - - @api.model - def _stripe_form_validate(self, tx, data): - return tx._stripe_s2s_validate_tree(data) diff --git a/addons/payment_stripe/tests/test_stripe.py b/addons/payment_stripe/tests/test_stripe.py index 496ede5c8fd..ef2f6627cd8 100644 --- a/addons/payment_stripe/tests/test_stripe.py +++ b/addons/payment_stripe/tests/test_stripe.py @@ -12,9 +12,7 @@ class StripeCommon(PaymentAcquirerCommon): def setUp(self): super(StripeCommon, self).setUp() - self.currency_euro = self.env['res.currency'].search([('name', '=', 'EUR')], limit=1) - # get the stripe account - self.stripe_id = self.env.ref('payment.payment_acquirer_stripe').id + self.stripe = self.env.ref('payment.payment_acquirer_stripe') @odoo.tests.common.at_install(True) @@ -22,18 +20,17 @@ class StripeCommon(PaymentAcquirerCommon): class StripeTest(StripeCommon): def test_10_stripe_s2s(self): - stripe = self.env['payment.acquirer'].browse(self.stripe_id) - self.assertEqual(stripe.environment, 'test', 'test without test environment') + self.assertEqual(self.stripe.environment, 'test', 'test without test environment') # Add Stripe credentials - stripe.write({ + self.stripe.write({ 'stripe_secret_key': 'sk_test_bldAlqh1U24L5HtRF9mBFpK7', 'stripe_publishable_key': 'pk_test_0TKSyYSZS9AcS4keZ2cxQQCW', }) # Create payment meethod for Stripe payment_token = self.env['payment.token'].create({ - 'acquirer_id': self.stripe_id, + 'acquirer_id': self.stripe.id, 'partner_id': self.buyer_id, 'cc_number': '4242424242424242', 'cc_expiry': '02 / 26', @@ -46,7 +43,7 @@ class StripeTest(StripeCommon): tx = self.env['payment.transaction'].create({ 'reference': 'test_ref_%s' % fields.date.today(), 'currency_id': self.currency_euro.id, - 'acquirer_id': self.stripe_id, + 'acquirer_id': self.stripe.id, 'partner_id': self.buyer_id, 'payment_token_id': payment_token.id, 'type': 'server2server', @@ -58,8 +55,7 @@ class StripeTest(StripeCommon): self.assertEqual(tx.state, 'done', 'Stripe: Transcation has been discarded.') def test_20_stripe_form_render(self): - stripe = self.env['payment.acquirer'].browse(self.stripe_id) - self.assertEqual(stripe.environment, 'test', 'test without test environment') + self.assertEqual(self.stripe.environment, 'test', 'test without test environment') # ---------------------------------------- # Test: button direct rendering @@ -77,7 +73,7 @@ class StripeTest(StripeCommon): } # render the button - res = stripe.render('SO404', 320.0, self.currency_euro.id, values=self.buyer_values) + res = self.stripe.render('SO404', 320.0, self.currency_euro.id, values=self.buyer_values) post_url = "https://checkout.stripe.com/checkout.js" email = "norbert.buyer@example.com" # check form result @@ -91,8 +87,7 @@ class StripeTest(StripeCommon): ) def test_30_stripe_form_management(self): - stripe = self.env['payment.acquirer'].browse(self.stripe_id) - self.assertEqual(stripe.environment, 'test', 'test without test environment') + self.assertEqual(self.stripe.environment, 'test', 'test without test environment') # typical data posted by Stripe after client has successfully paid stripe_post_data = { @@ -153,11 +148,11 @@ class StripeTest(StripeCommon): tx = self.env['payment.transaction'].create({ 'amount': 4700, - 'acquirer_id': self.stripe_id, + 'acquirer_id': self.stripe.id, 'currency_id': self.currency_euro.id, 'reference': 'SO100', 'partner_name': 'Norbert Buyer', - 'partner_country_id': self.country_france_id}) + 'partner_country_id': self.country_france.id}) # validate it tx.form_feedback(stripe_post_data, 'stripe') @@ -168,7 +163,7 @@ class StripeTest(StripeCommon): # simulate an error stripe_post_data['status'] = 'error' stripe_post_data.update({u'error': {u'message': u"Your card's expiration year is invalid.", u'code': u'invalid_expiry_year', u'type': u'card_error', u'param': u'exp_year'}}) - with mute_logger('openerp.addons.payment_stripe.models.payment_transaction'): + with mute_logger('openerp.addons.payment_stripe.models.payment'): tx.form_feedback(stripe_post_data, 'stripe') # check state self.assertEqual(tx.state, 'error', 'Stipe: erroneous validation did not put tx into error state') diff --git a/addons/payment_stripe/views/payment_stripe_template.xml b/addons/payment_stripe/views/payment_stripe_template.xml deleted file mode 100644 index 74a7d7e03e2..00000000000 --- a/addons/payment_stripe/views/payment_stripe_template.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/addons/payment_stripe/views/stripe.xml b/addons/payment_stripe/views/payment_stripe_templates.xml similarity index 95% rename from addons/payment_stripe/views/stripe.xml rename to addons/payment_stripe/views/payment_stripe_templates.xml index 978f1ffea42..310a26b3531 100644 --- a/addons/payment_stripe/views/stripe.xml +++ b/addons/payment_stripe/views/payment_stripe_templates.xml @@ -64,5 +64,11 @@ + + diff --git a/addons/payment_stripe/views/payment_acquirer.xml b/addons/payment_stripe/views/payment_views.xml similarity index 100% rename from addons/payment_stripe/views/payment_acquirer.xml rename to addons/payment_stripe/views/payment_views.xml diff --git a/addons/payment_transfer/__openerp__.py b/addons/payment_transfer/__openerp__.py index c5fa2f171b8..d26949df288 100644 --- a/addons/payment_transfer/__openerp__.py +++ b/addons/payment_transfer/__openerp__.py @@ -8,8 +8,8 @@ 'description': """Transfer Payment Acquirer""", 'depends': ['payment'], 'data': [ - 'views/transfer.xml', - 'data/transfer.xml', + 'views/payment_transfer_templates.xml', + 'data/payment_acquirer_data.xml', ], 'installable': True, 'auto_install': True, diff --git a/addons/payment_transfer/controllers/main.py b/addons/payment_transfer/controllers/main.py index 055858222e0..6411414c597 100644 --- a/addons/payment_transfer/controllers/main.py +++ b/addons/payment_transfer/controllers/main.py @@ -3,8 +3,8 @@ import logging import pprint import werkzeug -from openerp import http, SUPERUSER_ID -from openerp.http import request +from odoo import http +from odoo.http import request _logger = logging.getLogger(__name__) @@ -16,7 +16,6 @@ class OgoneController(http.Controller): '/payment/transfer/feedback', ], type='http', auth='none', csrf=False) def transfer_form_feedback(self, **post): - cr, uid, context = request.cr, SUPERUSER_ID, request.context _logger.info('Beginning form_feedback with post data %s', pprint.pformat(post)) # debug - request.registry['payment.transaction'].form_feedback(cr, uid, post, 'transfer', context) + request.env['payment.transaction'].sudo().form_feedback(post, 'transfer') return werkzeug.utils.redirect(post.pop('return_url', '/')) diff --git a/addons/payment_transfer/data/transfer.xml b/addons/payment_transfer/data/payment_acquirer_data.xml similarity index 97% rename from addons/payment_transfer/data/transfer.xml rename to addons/payment_transfer/data/payment_acquirer_data.xml index 141dc84f7d5..cab245767cb 100644 --- a/addons/payment_transfer/data/transfer.xml +++ b/addons/payment_transfer/data/payment_acquirer_data.xml @@ -1,5 +1,5 @@ - + @@ -17,4 +17,4 @@ - + diff --git a/addons/payment_transfer/models/__init__.py b/addons/payment_transfer/models/__init__.py index da51c9c1854..10b988e0c28 100644 --- a/addons/payment_transfer/models/__init__.py +++ b/addons/payment_transfer/models/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -import payment_acquirer +import payment diff --git a/addons/payment_transfer/models/payment.py b/addons/payment_transfer/models/payment.py new file mode 100644 index 00000000000..2ec43959bd6 --- /dev/null +++ b/addons/payment_transfer/models/payment.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models, _ +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.tools.float_utils import float_compare + +import logging +import pprint + +_logger = logging.getLogger(__name__) + + +class TransferPaymentAcquirer(models.Model): + _inherit = 'payment.acquirer' + + provider = fields.Selection(selection_add=[('transfer', 'Wire Transfer')]) + + def transfer_get_form_action_url(self): + return '/payment/transfer/feedback' + + def _format_transfer_data(self): + company_id = self.env.user.company_id.id + # filter only bank accounts marked as visible + journals = self.env['account.journal'].search([('type', '=', 'bank'), ('display_on_footer', '=', True), ('company_id', '=', company_id)]) + accounts = journals.mapped('bank_account_id').name_get() + bank_title = _('Bank Accounts') if len(accounts) > 1 else _('Bank Account') + bank_accounts = ''.join(['
    '] + ['
  • %s
  • ' % name for id, name in accounts] + ['
']) + post_msg = '''
+

Please use the following transfer details

+

%(bank_title)s

+%(bank_accounts)s +

Communication

+

Please use the order name as communication reference.

+
''' % { + 'bank_title': bank_title, + 'bank_accounts': bank_accounts, + } + return post_msg + + @api.model + def create(self, values): + """ Hook in create to create a default post_msg. This is done in create + to have access to the name and other creation values. If no post_msg + or a void post_msg is given at creation, generate a default one. """ + if values.get('provider') == 'transfer' and not values.get('post_msg'): + values['post_msg'] = self._format_transfer_data() + return super(TransferPaymentAcquirer, self).create(values) + + +class TransferPaymentTransaction(models.Model): + _inherit = 'payment.transaction' + + @api.model + def _transfer_form_get_tx_from_data(self, data): + reference, amount, currency_name = data.get('reference'), data.get('amount'), data.get('currency_name') + tx = self.search([('reference', '=', reference)]) + + if not tx or len(tx) > 1: + error_msg = _('received data for reference %s') % (pprint.pformat(reference)) + if not tx: + error_msg += _('; no order found') + else: + error_msg += _('; multiple order found') + _logger.info(error_msg) + raise ValidationError(error_msg) + + return tx + + def _transfer_form_get_invalid_parameters(self, data): + invalid_parameters = [] + + if float_compare(float(data.get('amount', '0.0')), self.amount, 2) != 0: + invalid_parameters.append(('amount', data.get('amount'), '%.2f' % self.amount)) + if data.get('currency') != self.currency_id.name: + invalid_parameters.append(('currency', data.get('currency'), self.currency_id.name)) + + return invalid_parameters + + def _transfer_form_validate(self, data): + _logger.info('Validated transfer payment for tx %s: set as pending' % (self.reference)) + return self.write({'state': 'pending'}) diff --git a/addons/payment_transfer/models/payment_acquirer.py b/addons/payment_transfer/models/payment_acquirer.py deleted file mode 100644 index a8d47098c6c..00000000000 --- a/addons/payment_transfer/models/payment_acquirer.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- - -from openerp.addons.payment.models.payment_acquirer import ValidationError -from openerp.osv import osv -from openerp.tools.float_utils import float_compare -from openerp.tools.translate import _ - -import logging -import pprint - -_logger = logging.getLogger(__name__) - - -class TransferPaymentAcquirer(osv.Model): - _inherit = 'payment.acquirer' - - def _get_providers(self, cr, uid, context=None): - providers = super(TransferPaymentAcquirer, self)._get_providers(cr, uid, context=context) - providers.append(['transfer', _('Wire Transfer')]) - return providers - - def transfer_get_form_action_url(self, cr, uid, id, context=None): - return '/payment/transfer/feedback' - - def _format_transfer_data(self, cr, uid, context=None): - company_id = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.id - # filter only bank accounts marked as visible - journal_ids = self.pool['account.journal'].search(cr, uid, [('type', '=', 'bank'), ('display_on_footer', '=', True), ('company_id', '=', company_id)], context=context) - accounts = self.pool['account.journal'].browse(cr, uid, journal_ids, context=context).mapped('bank_account_id').name_get() - bank_title = _('Bank Accounts') if len(accounts) > 1 else _('Bank Account') - bank_accounts = ''.join(['
    '] + ['
  • %s
  • ' % name for id, name in accounts] + ['
']) - post_msg = '''
-

Please use the following transfer details

-

%(bank_title)s

-%(bank_accounts)s -

Communication

-

Please use the order name as communication reference.

-
''' % { - 'bank_title': bank_title, - 'bank_accounts': bank_accounts, - } - return post_msg - - def create(self, cr, uid, values, context=None): - """ Hook in create to create a default post_msg. This is done in create - to have access to the name and other creation values. If no post_msg - or a void post_msg is given at creation, generate a default one. """ - if values.get('provider') == 'transfer' and not values.get('post_msg'): - values['post_msg'] = self._format_transfer_data(cr, uid, context=context) - return super(TransferPaymentAcquirer, self).create(cr, uid, values, context=context) - - -class TransferPaymentTransaction(osv.Model): - _inherit = 'payment.transaction' - - def _transfer_form_get_tx_from_data(self, cr, uid, data, context=None): - reference, amount, currency_name = data.get('reference'), data.get('amount'), data.get('currency_name') - tx_ids = self.search( - cr, uid, [ - ('reference', '=', reference), - ], context=context) - - if not tx_ids or len(tx_ids) > 1: - error_msg = _('received data for reference %s') % (pprint.pformat(reference)) - if not tx_ids: - error_msg += _('; no order found') - else: - error_msg += _('; multiple order found') - _logger.info(error_msg) - raise ValidationError(error_msg) - - return self.browse(cr, uid, tx_ids[0], context=context) - - def _transfer_form_get_invalid_parameters(self, cr, uid, tx, data, context=None): - invalid_parameters = [] - - 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 _transfer_form_validate(self, cr, uid, tx, data, context=None): - _logger.info('Validated transfer payment for tx %s: set as pending' % (tx.reference)) - return tx.write({'state': 'pending'}) diff --git a/addons/payment_transfer/views/transfer.xml b/addons/payment_transfer/views/payment_transfer_templates.xml similarity index 98% rename from addons/payment_transfer/views/transfer.xml rename to addons/payment_transfer/views/payment_transfer_templates.xml index 787cf848f74..0716d99c349 100644 --- a/addons/payment_transfer/views/transfer.xml +++ b/addons/payment_transfer/views/payment_transfer_templates.xml @@ -1,5 +1,5 @@ - + - + diff --git a/addons/website_payment/controllers/main.py b/addons/website_payment/controllers/main.py index d2d86734c3a..a3487e5052d 100644 --- a/addons/website_payment/controllers/main.py +++ b/addons/website_payment/controllers/main.py @@ -46,7 +46,7 @@ class website_payment(http.Controller): partner_id = user.partner_id.id if user.partner_id.id != request.website.partner_id.id else False - payment_form = acquirer.render(reference, float(amount), currency.id, values={'return_url': '/website_payment/confirm', 'partner_id': partner_id})[0] + payment_form = acquirer.render(reference, float(amount), currency.id, values={'return_url': '/website_payment/confirm', 'partner_id': partner_id}) values = { 'reference': reference, 'acquirer': acquirer, diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 991105f8774..25770f915a5 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -703,19 +703,20 @@ class WebsiteSale(http.Controller): acquirers = request.env['payment.acquirer'].search( [('website_published', '=', True), ('company_id', '=', order.company_id.id)] ) - values['acquirers'] = list(acquirers) - acquirer_buttons = acquirers.with_context(submit_class='btn btn-primary', submit_txt=_('Pay Now')).sudo().render( - '/', - order.amount_total, - order.pricelist_id.currency_id.id, - values={ - 'return_url': '/shop/payment/validate', - 'partner_id': shipping_partner_id, - 'billing_partner_id': order.partner_invoice_id.id, - } - ) - for index, button in enumerate(acquirer_buttons): - values['acquirers'][index].button = button + values['acquirers'] = [] + for acquirer in acquirers: + acquirer_button = acquirer.with_context(submit_class='btn btn-primary', submit_txt=_('Pay Now')).sudo().render( + '/', + order.amount_total, + order.pricelist_id.currency_id.id, + values={ + 'return_url': '/shop/payment/validate', + 'partner_id': shipping_partner_id, + 'billing_partner_id': order.partner_invoice_id.id, + } + ) + acquirer.button = acquirer_button + values['acquirers'].append(acquirer) return request.website.render("website_sale.payment", values) @@ -772,7 +773,7 @@ class WebsiteSale(http.Controller): 'partner_id': order.partner_shipping_id.id or order.partner_invoice_id.id, 'billing_partner_id': order.partner_invoice_id.id, }, - )[0] + ) @http.route('/shop/payment/get_status/', type='json', auth="public", website=True) def payment_get_status(self, sale_order_id, **post):