[MERGE] payment_*: migrate to new API
Base payment module and then all specific acquirers are migrated to the new API. Each module is migrated in two steps : - [MOV] files according to the guidelines - [MIG] python code to the new API Some of the payment acquirers were already in new API. Small linting has been applied on those, like renaming openerp to odoo or using selection_add instead of a custom selection method. In payment and payment_paypal prototype code about a first and unused s2s implementation has been removed.
This commit is contained in:
@@ -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',
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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='<name>' attribute on fields it is possible
|
||||
@@ -34,15 +33,15 @@ class PaymentAcquirer(osv.Model):
|
||||
|
||||
Methods that should be added in an acquirer-specific implementation:
|
||||
|
||||
- ``<name>_form_generate_values(self, cr, uid, id, reference, amount, currency,
|
||||
partner_id=False, partner_values=None, tx_custom_values=None, context=None)``:
|
||||
- ``<name>_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.
|
||||
- ``<name>_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.
|
||||
- ``<name>_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).
|
||||
- ``<name>_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.
|
||||
- ``<name>_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='<i>Pending,</i> 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='<i>Done,</i> 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='<i>Cancel,</i> 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='<i>Error,</i> 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': '<i>Pending,</i> Your online payment has been successfully processed. But your order is not validated yet.',
|
||||
'done_msg': '<i>Done,</i> Your online payment has been successfully processed. Thank you for your order.',
|
||||
'cancel_msg': '<i>Cancel,</i> Your payment has been cancelled.',
|
||||
'error_msg': "<i>Error,</i> 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="<provider>"' attribute, then it
|
||||
required if record.provider is <provider>. """
|
||||
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 <name>_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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="payment_transaction_user_rule" model="ir.rule">
|
||||
@@ -20,4 +20,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Add payment options to sale.order and invoice forms -->
|
||||
<record model="ir.ui.view" id="payment_acquirer_installation">
|
||||
@@ -15,4 +15,4 @@
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Payment menu in settings -->
|
||||
@@ -368,4 +368,4 @@
|
||||
groups='base.group_no_one'/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Add creditcard to res.partner -->
|
||||
@@ -18,4 +18,4 @@
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_adyen" model="payment.acquirer">
|
||||
@@ -17,4 +17,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import adyen
|
||||
import payment
|
||||
|
||||
@@ -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
|
||||
})
|
||||
@@ -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'}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<template id="adyen_acquirer_button">
|
||||
@@ -33,4 +33,4 @@
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="acquirer_form_adyen" model="ir.ui.view">
|
||||
@@ -19,4 +19,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import authorize
|
||||
import payment
|
||||
|
||||
+20
-25
@@ -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'),
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.addons.payment_authorize.tests import test_authorize
|
||||
|
||||
from . import test_authorize
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="payment_authorize_redirect" name="Payment Authorize">
|
||||
<script type="text/javascript">
|
||||
window.location.href = '<t t-esc="return_url"/>';
|
||||
</script>
|
||||
</template>
|
||||
</odoo>
|
||||
+5
-1
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<template id="authorize_acquirer_button">
|
||||
<form t-if="acquirer" t-att-action="tx_url" method="post" target="_self">
|
||||
<input type='hidden' name='x_login' t-att-value='x_login'/>
|
||||
@@ -48,5 +47,10 @@
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<template id="payment_authorize_redirect" name="Payment Authorize">
|
||||
<script type="text/javascript">
|
||||
window.location.href = '<t t-esc="return_url"/>';
|
||||
</script>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_buckaroo" model="payment.acquirer">
|
||||
@@ -16,4 +16,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import buckaroo
|
||||
import payment
|
||||
|
||||
+49
-57
@@ -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'),
|
||||
@@ -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')
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<template id="buckaroo_acquirer_button">
|
||||
@@ -31,4 +31,4 @@
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="acquirer_form_buckaroo" model="ir.ui.view">
|
||||
@@ -18,4 +18,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_ogone" model="payment.acquirer">
|
||||
@@ -20,4 +20,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import ogone
|
||||
import payment
|
||||
|
||||
@@ -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:])
|
||||
@@ -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({})
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<template id="ogone_acquirer_button">
|
||||
@@ -89,4 +89,4 @@
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="acquirer_form_ogone" model="ir.ui.view">
|
||||
@@ -22,4 +22,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_paypal" model="payment.acquirer">
|
||||
@@ -18,4 +18,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import paypal
|
||||
import payment
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Add payment options to sale.order and invoice forms -->
|
||||
<record model="ir.ui.view" id="payment_paypal_option_config">
|
||||
@@ -12,4 +12,4 @@
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<template id="paypal_acquirer_button">
|
||||
@@ -40,4 +40,4 @@
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="acquirer_form_paypal" model="ir.ui.view">
|
||||
@@ -37,4 +37,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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']
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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/'],
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<record id="payment.payment_acquirer_sips" model="payment.acquirer">
|
||||
@@ -15,4 +15,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sips
|
||||
import payment
|
||||
|
||||
@@ -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
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
|
||||
<template id="sips_acquirer_button">
|
||||
@@ -17,4 +17,4 @@
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="acquirer_form_sips" model="ir.ui.view">
|
||||
@@ -17,4 +17,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import payment_transaction
|
||||
import payment_acquirer
|
||||
import payment_token
|
||||
import payment
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="payment_stripe_redirect" name="Payment Stripe">
|
||||
<script type="text/javascript">
|
||||
window.location.href = '<t t-esc="return_url"/>';
|
||||
</script>
|
||||
</template>
|
||||
</odoo>
|
||||
+6
@@ -64,5 +64,11 @@
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<template id="payment_stripe_redirect" name="Payment Stripe">
|
||||
<script type="text/javascript">
|
||||
window.location.href = '<t t-esc="return_url"/>';
|
||||
</script>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
|
||||
@@ -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', '/'))
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="payment.payment_acquirer_transfer" model="payment.acquirer">
|
||||
@@ -17,4 +17,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import payment_acquirer
|
||||
import payment
|
||||
|
||||
@@ -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(['<ul>'] + ['<li>%s</li>' % name for id, name in accounts] + ['</ul>'])
|
||||
post_msg = '''<div>
|
||||
<h3>Please use the following transfer details</h3>
|
||||
<h4>%(bank_title)s</h4>
|
||||
%(bank_accounts)s
|
||||
<h4>Communication</h4>
|
||||
<p>Please use the order name as communication reference.</p>
|
||||
</div>''' % {
|
||||
'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'})
|
||||
@@ -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(['<ul>'] + ['<li>%s</li>' % name for id, name in accounts] + ['</ul>'])
|
||||
post_msg = '''<div>
|
||||
<h3>Please use the following transfer details</h3>
|
||||
<h4>%(bank_title)s</h4>
|
||||
%(bank_accounts)s
|
||||
<h4>Communication</h4>
|
||||
<p>Please use the order name as communication reference.</p>
|
||||
</div>''' % {
|
||||
'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'})
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<template id="transfer_acquirer_button">
|
||||
@@ -20,4 +20,4 @@
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
@@ -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,
|
||||
|
||||
@@ -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/<int:sale_order_id>', type='json', auth="public", website=True)
|
||||
def payment_get_status(self, sale_order_id, **post):
|
||||
|
||||
Reference in New Issue
Block a user