[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:
Thibault Delavallée
2016-07-06 15:17:06 +02:00
92 changed files with 1581 additions and 2049 deletions
+7 -7
View File
@@ -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',
],
+18 -21
View File
@@ -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
+304 -324
View File
@@ -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)
+14 -14
View File
@@ -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)
+2 -2
View File
@@ -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>
+19 -28
View File
@@ -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
@@ -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>
@@ -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>
+3 -3
View File
@@ -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 -6
View File
@@ -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)
@@ -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 -1
View File
@@ -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
})
+8 -39
View File
@@ -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'}
@@ -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>
@@ -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>
+3 -4
View File
@@ -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,
}
+2 -2
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
import authorize
import payment
@@ -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 -2
View File
@@ -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>
@@ -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>
+3 -3
View File
@@ -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,
}
+4 -4
View File
@@ -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)
@@ -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 -1
View File
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
import buckaroo
import payment
@@ -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'),
+46 -61
View File
@@ -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')
@@ -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>
@@ -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>
+3 -3
View File
@@ -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,
}
+8 -17
View File
@@ -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'
@@ -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 -1
View File
@@ -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:])
+52 -97
View File
@@ -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({})
@@ -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>
@@ -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>
+4 -4
View File
@@ -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,
}
+5 -9
View File
@@ -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)
@@ -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 -1
View File
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
import paypal
import payment
+191
View File
@@ -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)
-393
View File
@@ -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)
+38 -55
View File
@@ -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')
@@ -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>
@@ -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>
@@ -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>
+3 -3
View File
@@ -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 -2
View File
@@ -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
+154
View File
@@ -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)
+3 -3
View File
@@ -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,
}
+8 -15
View File
@@ -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/'],
@@ -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 -1
View File
@@ -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
@@ -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>
@@ -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>
+3 -4
View File
@@ -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 -3
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import payment_transaction
import payment_acquirer
import payment_token
import payment
+206
View File
@@ -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)
+11 -16
View File
@@ -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>
@@ -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>
+2 -2
View File
@@ -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 -4
View File
@@ -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', '/'))
@@ -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 -1
View File
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
import payment_acquirer
import payment
+81
View File
@@ -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'})
@@ -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>
+1 -1
View File
@@ -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,
+15 -14
View File
@@ -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):