Merge branch 'master-sheetal-06072018' into 'master-patch-july-2018'

Master sheetal 06072018

See merge request flectra-hq/flectra!96
This commit is contained in:
Parthiv Patel 2018-07-13 09:34:32 +00:00
commit 3881ab378e
44 changed files with 364 additions and 67 deletions

View File

@ -78,6 +78,8 @@ class AuthSignupHome(Home):
login, request.env.user.login, request.httprequest.remote_addr)
request.env['res.users'].sudo().reset_password(login)
qcontext['message'] = _("An email has been sent with credentials to reset your password")
except UserError as e:
qcontext['error'] = e.name or e.value
except SignupError:
qcontext['error'] = _("Could not reset your password")
_logger.exception('error when resetting password')

View File

@ -45,6 +45,9 @@
</div>
<div style="padding:0px;width:600px;margin:auto; margin-top: 10px; background: #fff repeat top /100%;color:#777777">
${user.signature | safe}
<p style="font-size: 11px; margin-top: 10px;">
<strong>Sent by ${user.company_id.name} using <a href="www.flectrahq.com" style="text-decoration:none; color: #875A7B;">Flectra</a></strong>
</p>
</div>]]></field>
<field name="user_signature" eval="False"/>
</record>
@ -81,6 +84,9 @@
</div>
<div style="padding:0px;width:600px;margin:auto; margin-top: 10px; background: #fff repeat top /100%;color:#777777">
${user.signature | safe}
<p style="font-size: 11px; margin-top: 10px;">
<strong>Sent by ${user.company_id.name} using <a href="www.flectrahq.com" style="text-decoration:none; color: #875A7B;">Flectra</a></strong>
</p>
</div>]]></field>
<field name="user_signature" eval="False"/>
</record>
@ -122,6 +128,9 @@
</div>
<div style="padding:0px;width:600px;margin:auto; margin-top: 10px; background: #fff repeat top /100%;color:#777777">
${user.signature | safe}
<p style="font-size: 11px; margin-top: 10px;">
<strong>Sent by ${user.company_id.name} using <a href="www.flectrahq.com" style="text-decoration:none; color: #875A7B;">Flectra</a></strong>
</p>
</div></field>
<field name="user_signature" eval="False"/>
</record>

View File

@ -32,6 +32,7 @@ class ResPartner(models.Model):
signup_url = fields.Char(compute='_compute_signup_url', string='Signup URL')
@api.multi
@api.depends('signup_token', 'signup_expiration')
def _compute_signup_valid(self):
dt = now()
for partner in self:

View File

@ -155,5 +155,5 @@ class ResUsers(models.Model):
sup = super(ResUsers, self)
if not default or not default.get('email'):
# avoid sending email to the user we are duplicating
sup = super(ResUsers, self.with_context(reset_password=False))
sup = super(ResUsers, self.with_context(no_reset_password=True))
return sup.copy(default=default)

View File

@ -53,7 +53,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
navigator.userAgent.match(/iPod/i) ||
navigator.userAgent.match(/BlackBerry/i) ||
navigator.userAgent.match(/Windows Phone/i);
this.isChromeMobile = isMobile && window.chrome;
this.isChromeMobile = isMobile && navigator.userAgent.match(/Chrome/i);
// Creates an input who will receive the barcode scanner value.
if (this.isChromeMobile) {
@ -61,13 +61,17 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
name: 'barcode',
type: 'text',
css: {
'position': 'absolute',
'opacity': 0,
'position': 'fixed',
'top': '50%',
'transform': 'translateY(-50%)',
'z-index': '-1',
},
});
// Avoid to show autocomplete for a non appearing input
this.$barcodeInput.attr('autocomplete', 'off');
}
this.__removeBarcodeField = _.debounce(this._removeBarcodeField, this.inputTimeOut);
this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut);
},
handle_buffered_keys: function() {
@ -230,7 +234,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
this.max_time_between_keys_in_ms);
}
// if the barcode input doesn't receive keydown for a while, remove it.
this.__removeBarcodeField();
this.__blurBarcodeInput();
}
},
@ -245,21 +249,22 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
var barcodeValue = this.$barcodeInput.val();
if (barcodeValue.match(this.regexp)) {
core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]);
this.$barcodeInput.val('');
this._blurBarcodeInput();
}
},
/**
* Remove the temporary input created to store the barcode value.
* If nothing happens, this input will be removed, so the focus will be lost
* and the virtual keyboard on mobile devices will be closed.
* Removes the value and focus from the barcode input.
* If nothing happens, the focus will be lost and
* the virtual keyboard on mobile devices will be closed.
*
* @private
*/
_removeBarcodeField: function () {
_blurBarcodeInput: function () {
if (this.$barcodeInput) {
// Reset the value and remove from the DOM.
this.$barcodeInput.val('').remove();
// Close the virtual keyboard on mobile browsers
// FIXME: actually we can't prevent keyboard from opening
this.$barcodeInput.val('').blur();
}
},

View File

@ -146,10 +146,10 @@ FormController.include({
* @param {Object} activeBarcode: options sent by the field who use barcode features
* @returns {Deferred}
*/
_barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode) {
_barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode, quantity) {
var changes = {};
var candidateChanges = {};
candidateChanges[activeBarcode.quantity] = candidate.data[activeBarcode.quantity] + 1;
candidateChanges[activeBarcode.quantity] = quantity ? quantity : candidate.data[activeBarcode.quantity] + 1;
changes[activeBarcode.fieldName] = {
operation: 'UPDATE',
id: candidate.id,
@ -283,6 +283,9 @@ FormController.include({
function (reserved) {return barcode.indexOf(reserved) === 0;});
var hasCommand = false;
var defs = [];
if (! $.contains(target, self.el)) {
return;
}
for (var k in self.activeBarcode) {
var activeBarcode = self.activeBarcode[k];
// Handle the case where there are several barcode widgets on the same page. Since the
@ -306,6 +309,8 @@ FormController.include({
}
return self.alive($.when.apply($, defs)).then(function () {
if (!prefixed) {
// remember the barcode scanned for the quantity listener
self.current_barcode = barcode;
// redraw the view if we scanned a real barcode (required if
// we manually apply the change in JS, e.g. incrementing the
// quantity)
@ -321,6 +326,9 @@ FormController.include({
_quantityListener: function (event) {
var character = String.fromCharCode(event.which);
if (! $.contains(event.target, this.el)) {
return;
}
// only catch the event if we're not focused in
// another field and it's a number
if (!$(event.target).is('body, .modal') || !/[0-9]/.test(character)) {
@ -355,9 +363,10 @@ FormController.include({
title: _t('Set quantity'),
buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () {
var new_qty = this.$content.find('.o_set_qty_input').val();
var values = {};
values[activeBarcode.quantity] = parseFloat(new_qty);
return self.model.notifyChanges(activeBarcode.candidate.id, values).then(function () {
var record = self.model.get(self.handle);
return self._barcodeSelectedCandidate(activeBarcode.candidate, record,
self.current_barcode, activeBarcode, parseFloat(new_qty))
.then(function () {
self.update({}, {reload: false});
});
}}, {text: _t('Discard'), close: true}],

View File

@ -18,6 +18,9 @@ var BarcodeParser = Class.extend({
// only when those data have been loaded
load: function(){
var self = this;
if (!this.nomenclature_id) {
return;
}
var id = this.nomenclature_id[0];
rpc.query({
model: 'barcode.nomenclature',

View File

@ -404,4 +404,136 @@ QUnit.test('specification of widget barcode_handler', function (assert) {
barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = delay;
delete fieldRegistry.map.test_barcode_handler;
});
QUnit.test('specification of widget barcode_handler with keypress and notifyChange', function (assert) {
assert.expect(6);
var done = assert.async();
var delay = barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms;
barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = 0;
this.data.order.onchanges = {
_barcode_scanned: function () {},
};
// Define a specific barcode_handler widget for this test case
var TestBarcodeHandler = AbstractField.extend({
init: function () {
this._super.apply(this, arguments);
this.trigger_up('activeBarcode', {
name: 'test',
fieldName: 'line_ids',
notifyChange: false,
setQuantityWithKeypress: true,
quantity: 'quantity',
commands: {
barcode: '_barcodeAddX2MQuantity',
}
});
},
});
fieldRegistry.add('test_barcode_handler', TestBarcodeHandler);
var form = createView({
View: FormView,
model: 'order',
data: this.data,
arch: '<form>' +
'<field name="_barcode_scanned" widget="test_barcode_handler"/>' +
'<field name="line_ids">' +
'<tree>' +
'<field name="product_id"/>' +
'<field name="product_barcode" invisible="1"/>' +
'<field name="quantity"/>' +
'</tree>' +
'</field>' +
'</form>',
mockRPC: function (route, args) {
assert.step(args.method);
return this._super.apply(this, arguments);
},
res_id: 1,
viewOptions: {
mode: 'edit',
},
});
_.each(['1','2','3','4','5','6','7','8','9','0','Enter'], triggerKeypressEvent);
// Quantity listener should open a dialog.
triggerKeypressEvent('5');
setTimeout(function () {
var keycode = $.ui.keyCode.ENTER;
assert.strictEqual($('.modal .modal-body').length, 1, 'should open a modal with a quantity as input');
assert.strictEqual($('.modal .modal-body .o_set_qty_input').val(), '5', 'the quantity by default in the modal shoud be 5');
$('.modal .modal-body .o_set_qty_input').val('7');
$('.modal .modal-body .o_set_qty_input').trigger($.Event('keypress', {which: keycode, keyCode: keycode}));
assert.strictEqual(form.$('.o_data_row .o_data_cell:nth(1)').text(), '7',
"quantity checked should be 7");
assert.verifySteps(['read', 'read']);
form.destroy();
barcodeEvents.BarcodeEvents.max_time_between_keys_in_ms = delay;
delete fieldRegistry.map.test_barcode_handler;
done();
});
});
QUnit.test('barcode_scanned only trigger error for active view', function (assert) {
assert.expect(2);
this.data.order_line.fields._barcode_scanned = {string: 'Barcode scanned', type: 'char'};
var form = createView({
View: FormView,
model: 'order',
data: this.data,
arch: '<form>' +
'<field name="_barcode_scanned" widget="barcode_handler"/>' +
'<field name="line_ids">' +
'<tree>' +
'<field name="product_id"/>' +
'<field name="product_barcode" invisible="1"/>' +
'<field name="quantity"/>' +
'</tree>' +
'</field>' +
'</form>',
archs: {
"order_line,false,form":
'<form string="order line">' +
'<field name="_barcode_scanned" widget="barcode_handler"/>' +
'<field name="product_id"/>' +
'</form>',
},
res_id: 1,
intercepts: {
warning: function (event) {
assert.step(event.name);
}
},
viewOptions: {
mode: 'edit',
},
});
form.$('.o_data_row:first').click();
// We do not trigger on the body since modal and
// form view are both inside it.
function modalTriggerKeypressEvent(char) {
var keycode;
if (char === "Enter") {
keycode = $.ui.keyCode.ENTER;
} else {
keycode = char.charCodeAt(0);
}
return $('.modal').trigger($.Event('keypress', {which: keycode, keyCode: keycode}));
}
_.each(['O','-','B','T','N','.','c','a','n','c','e','l','Enter'], modalTriggerKeypressEvent);
assert.verifySteps(['warning'], "only one event should be triggered");
form.destroy();
});
});

View File

@ -10,7 +10,7 @@ from flectra.tools.translate import _
class Partner(models.Model):
_inherit = 'res.partner'
country_enforce_cities = fields.Boolean(related='country_id.enforce_cities')
country_enforce_cities = fields.Boolean(related='country_id.enforce_cities', readonly=True)
city_id = fields.Many2one('res.city', string='City')
@api.onchange('city_id')

View File

@ -17,7 +17,8 @@ For example, in legal reports, some countries need to split the street into seve
with the street name, the house number, and room number.
""",
'data': [
'views/base_address_extended.xml'
'views/base_address_extended.xml',
'data/base_address_extended_data.xml',
],
'depends': ['base'],
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<data noupdate="1">
<record id="base.nl" model="res.country">
<field eval="'%(street_name)s %(street_number)s/%(street_number2)s'" name="street_format" />
</record>
</data>
</flectra>

View File

@ -89,6 +89,7 @@ class WebsitePayment(http.Controller):
'amount': float(amount),
'currency_id': currency_id,
'partner_id': partner_id,
'type': 'form_save' if acquirer.save_token != 'none' and partner_id else 'form',
}
tx = request.env['payment.transaction'].sudo().create(values)
@ -117,7 +118,8 @@ class WebsitePayment(http.Controller):
'amount': float(amount),
'currency_id': int(currency_id),
'partner_id': partner_id,
'payment_token_id': pm_id
'payment_token_id': pm_id,
'type': 'form_save' if token.acquirer_id.save_token != 'none' and partner_id else 'form',
}
tx = request.env['payment.transaction'].sudo().create(values)

View File

@ -3,8 +3,7 @@
<template id="default_acquirer_button">
<input type="hidden" name="data_set" t-att-data-action-url="tx_url"/>
<input type="hidden" name="csrf_token"
t-att-value="request.csrf_token()"/>
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<t t-if="return_url">
<input type="hidden" name="return_url" t-att-value="return_url"/>
</t>
@ -148,8 +147,7 @@
</field>
<field name="description" type="html">
<p>
Provide instructions to customers so that they can pay their
orders manually.
Provide instructions to customers so that they can pay their orders manually.
</p>
</field>
</record>

View File

@ -3,3 +3,4 @@
from . import payment_acquirer
from . import res_partner
from . import account_payment
from . import chart_template

View File

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from flectra import api, fields, models, _
class WizardMultiChartsAccounts(models.TransientModel):
_inherit = 'wizard.multi.charts.accounts'
@api.multi
def _create_bank_journals_from_o2m(self, company, acc_template_ref):
res = super(WizardMultiChartsAccounts, self)._create_bank_journals_from_o2m(company, acc_template_ref)
# Try to generate the missing journals
return res + self.env['payment.acquirer']._create_missing_journal_for_acquirers(company=company)

View File

@ -8,6 +8,7 @@ from flectra import api, exceptions, fields, models, _
from flectra.tools import consteq, float_round, image_resize_images, image_resize_image, ustr
from flectra.addons.base.module import module
from flectra.exceptions import ValidationError
from flectra import api, SUPERUSER_ID
_logger = logging.getLogger(__name__)
@ -20,6 +21,11 @@ def _partner_split_name(partner_name):
return [' '.join(partner_name.split()[:-1]), ' '.join(partner_name.split()[-1:])]
def create_missing_journal_for_acquirers(cr, registry):
env = api.Environment(cr, SUPERUSER_ID, {})
env['payment.acquirer']._create_missing_journal_for_acquirers()
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.
@ -51,6 +57,9 @@ class PaymentAcquirer(models.Model):
_description = 'Payment Acquirer'
_order = 'website_published desc, sequence, name'
def _get_default_view_template_id(self):
return self.env.ref('payment.default_acquirer_button', raise_if_not_found=False)
name = fields.Char('Name', required=True, translate=True)
description = fields.Html('Description')
sequence = fields.Integer('Sequence', default=10, help="Determine the display order")
@ -196,6 +205,58 @@ class PaymentAcquirer(models.Model):
"""
return dict(authorize=[], tokenize=[], fees=[])
@api.multi
def _prepare_account_journal_vals(self):
'''Prepare the values to create the acquirer's journal.
:return: a dictionary to create a account.journal record.
'''
self.ensure_one()
account_vals = self.env['account.journal']._prepare_liquidity_account(
self.name, self.company_id, None, 'bank')
account_vals['user_type_id'] = self.env.ref('account.data_account_type_current_assets').id
account_vals['reconcile'] = True
account = self.env['account.account'].create(account_vals)
return {
'name': self.name,
'code': self.name.upper(),
'sequence': 999,
'type': 'bank',
'company_id': self.company_id.id,
'default_debit_account_id': account.id,
'default_credit_account_id': account.id,
# Show the journal on dashboard if the acquirer is published on the website.
'show_on_dashboard': self.website_published,
# Don't show payment methods in the backend.
'inbound_payment_method_ids': [],
'outbound_payment_method_ids': [],
}
@api.model
def _create_missing_journal_for_acquirers(self, company=None):
'''Create the journal for active acquirers.
We want one journal per acquirer. However, we can't create them during the 'create' of the payment.acquirer
because every acquirers are defined on the 'payment' module but is active only when installing their own module
(e.g. payment_paypal for Paypal). We can't do that in such modules because we have no guarantee the chart template
is already installed.
'''
# Search for installed acquirers modules.
# If this method is triggered by a post_init_hook, the module is 'to install'.
# If the trigger comes from the chart template wizard, the modules are already installed.
acquirer_modules = self.env['ir.module.module'].search(
[('name', 'like', 'payment_%'), ('state', 'in', ('to install', 'installed'))])
acquirer_names = [a.name.split('_')[1] for a in acquirer_modules]
# Search for acquirers having no journal
company = company or self.env.user.company_id
acquirers = self.env['payment.acquirer'].search(
[('provider', 'in', acquirer_names), ('journal_id', '=', False), ('company_id', '=', company.id)])
journals = self.env['account.journal']
for acquirer in acquirers.filtered(lambda l: not l.journal_id and l.company_id.chart_template_id):
acquirer.journal_id = self.env['account.journal'].create(acquirer._prepare_account_journal_vals())
journals += acquirer.journal_id
return journals
@api.model
def create(self, vals):
image_resize_images(vals)
@ -208,7 +269,13 @@ class PaymentAcquirer(models.Model):
@api.multi
def toggle_website_published(self):
self.write({'website_published': not self.website_published})
''' When clicking on the website publish toggle button, the website_published is reversed and
the acquirer journal is set or not in favorite on the dashboard.
'''
self.ensure_one()
self.website_published = not self.website_published
if self.journal_id:
self.journal_id.show_on_dashboard = self.website_published
return True
@api.multi
@ -617,6 +684,10 @@ class PaymentTransaction(models.Model):
@api.model
def get_next_reference(self, reference):
return self._get_next_reference(reference)
@api.model
def _get_next_reference(self, reference, acquirer=None):
ref_suffix = 1
init_ref = reference
while self.env['payment.transaction'].sudo().search_count([('reference', '=', reference)]):

View File

@ -414,8 +414,14 @@ flectra.define('payment.payment_form', function (require) {
},
displayError: function (title, message) {
var $checkedRadio = this.$('input[type="radio"]:checked'),
acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]),
acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]);
var $acquirerForm;
if (this.isNewPaymentRadio($checkedRadio[0])) {
$acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID);
}
else if (this.isFormPaymentRadio($checkedRadio[0])) {
$acquirerForm = this.$('#o_payment_form_acq_' + acquirerID);
}
if ($checkedRadio.length === 0) {
return new Dialog(null, {

View File

@ -71,7 +71,7 @@
</page>
<page string="Messages">
<group>
<field name="pre_msg"/>
<field name="pre_msg" invisible="1"/>
<field name="post_msg"/>
<field name="pending_msg"/>
<field name="done_msg"/>
@ -81,7 +81,7 @@
</page>
<page string="Configuration">
<group name="acquirer_config">
<field name="journal_id" context="{'default_type': 'bank'}" required="1"/>
<field name="journal_id" context="{'default_type': 'bank'}"/>
<field name="capture_manually" attrs="{'invisible': [('authorize_implemented', '=', False)]}"/>
<field name="save_token" widget="radio" attrs="{'invisible': ['|', ('token_implemented', '=', False), ('payment_flow', '=', 's2s')]}"/>
<field name="fees_active" attrs="{'invisible': [('fees_implemented', '=', False)]}"/>
@ -113,7 +113,7 @@
</div>
<field name="registration_view_template_id" groups="base.group_no_one"/>
<field name="payment_icon_ids" widget="many2many_tags"/>
<field name="payment_flow" widget="radio"/>
<field name="payment_flow" widget="radio" attrs="{'invisible': [('token_implemented', '=', False)]}"/>
</group>
</page>
</notebook>

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -14,4 +14,5 @@
'data/payment_acquirer_data.xml',
],
'installable': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -14,6 +14,7 @@ from werkzeug import urls
from flectra import api, fields, models, tools, _
from flectra.addons.payment.models.payment_acquirer import ValidationError
from flectra.addons.payment_adyen.controllers.main import AdyenController
from flectra.tools.pycompat import to_native
_logger = logging.getLogger(__name__)
@ -192,7 +193,7 @@ class TxAdyen(models.Model):
shasign_check = tx.acquirer_id._adyen_generate_merchant_sig_sha256('out', data)
else:
shasign_check = tx.acquirer_id._adyen_generate_merchant_sig('out', data)
if shasign_check != data.get('merchantSig'):
if to_native(shasign_check) != to_native(data.get('merchantSig')):
error_msg = _('Adyen: invalid merchantSig, received %s, computed %s') % (data.get('merchantSig'), shasign_check)
_logger.warning(error_msg)
raise ValidationError(error_msg)

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -14,4 +14,5 @@
'data/payment_acquirer_data.xml',
],
'installable': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -11,7 +11,7 @@ import time
from flectra import _, api, fields, models
from flectra.addons.payment.models.payment_acquirer import ValidationError
from flectra.addons.payment_authorize.controllers.main import AuthorizeController
from flectra.tools.float_utils import float_compare
from flectra.tools.float_utils import float_compare, float_repr
from flectra.tools.safe_eval import safe_eval
_logger = logging.getLogger(__name__)
@ -59,12 +59,21 @@ class PaymentAcquirerAuthorize(models.Model):
@api.multi
def authorize_form_generate_values(self, values):
self.ensure_one()
# State code is only supported in US, use state name by default
# See https://developer.authorize.net/api/reference/
state = values['partner_state'].name if values.get('partner_state') else ''
if values.get('partner_country') and values.get('partner_country') == self.env.ref('base.us', False):
state = values['partner_state'].code if values.get('partner_state') else ''
billing_state = values['billing_partner_state'].name if values.get('billing_partner_state') else ''
if values.get('billing_partner_country') and values.get('billing_partner_country') == self.env.ref('base.us', False):
billing_state = values['billing_partner_state'].code if values.get('billing_partner_state') else ''
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
authorize_tx_values = dict(values)
temp_authorize_tx_values = {
'x_login': self.authorize_login,
'x_trans_key': self.authorize_transaction_key,
'x_amount': str(values['amount']),
'x_amount': float_repr(values['amount'], values['currency'].decimal_places if values['currency'] else 2),
'x_show_form': 'PAYMENT_FORM',
'x_type': 'AUTH_CAPTURE' if not self.capture_manually else 'AUTH_ONLY',
'x_method': 'CC',
@ -83,7 +92,7 @@ class PaymentAcquirerAuthorize(models.Model):
'first_name': values.get('partner_first_name'),
'last_name': values.get('partner_last_name'),
'phone': values.get('partner_phone'),
'state': values.get('partner_state') and values['partner_state'].code or '',
'state': state,
'billing_address': values.get('billing_partner_address'),
'billing_city': values.get('billing_partner_city'),
'billing_country': values.get('billing_partner_country') and values.get('billing_partner_country').name or '',
@ -92,7 +101,7 @@ class PaymentAcquirerAuthorize(models.Model):
'billing_first_name': values.get('billing_partner_first_name'),
'billing_last_name': values.get('billing_partner_last_name'),
'billing_phone': values.get('billing_partner_phone'),
'billing_state': values.get('billing_partner_state') and values['billing_partner_state'].code or '',
'billing_state': billing_state,
}
temp_authorize_tx_values['returndata'] = authorize_tx_values.pop('return_url', '')
temp_authorize_tx_values['x_fp_hash'] = self._authorize_generate_hashing(temp_authorize_tx_values)

View File

@ -51,7 +51,7 @@ class AuthorizeForm(AuthorizeCommon):
form_values = {
'x_login': self.authorize.authorize_login,
'x_trans_key': self.authorize.authorize_transaction_key,
'x_amount': '320.0',
'x_amount': '56.16',
'x_show_form': 'PAYMENT_FORM',
'x_type': 'AUTH_CAPTURE',
'x_method': 'CC',
@ -87,7 +87,7 @@ class AuthorizeForm(AuthorizeCommon):
form_values['x_fp_hash'] = self._authorize_generate_hashing(form_values)
# render the button
res = self.authorize.render('SO004', 320.0, self.currency_usd.id, values=self.buyer_values)
res = self.authorize.render('SO004', 56.16, self.currency_usd.id, values=self.buyer_values)
# check form result
tree = objectify.fromstring(res)

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -14,4 +14,5 @@
'data/payment_acquirer_data.xml',
],
'installable': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -14,4 +14,5 @@
'data/payment_acquirer_data.xml',
],
'installable': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -110,7 +110,7 @@ class OgoneController(http.Controller):
def feedback(self, **kwargs):
try:
tx = request.env['payment.transaction'].sudo()._ogone_form_get_tx_from_data(kwargs)
tx._ogone_s2s_validate()
tx._ogone_s2s_validate_tree(kwargs)
except ValidationError:
return 'ko'
return 'ok'

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -14,4 +14,5 @@
'data/payment_acquirer_data.xml',
],
'installable': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -17,4 +17,5 @@
'views/payment_payumoney_templates.xml',
'data/payment_acquirer_data.xml',
],
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -1,2 +1,3 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -20,4 +20,5 @@ payments acquirer using Worldline SIPS.""",
'data/payment_acquirer_data.xml',
],
'installable': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -111,6 +111,16 @@ class AcquirerSips(models.Model):
return self.environment == 'prod' and self.sips_prod_url or self.sips_test_url
class PaymentTransactionSips(models.Model):
_inherit = 'payment.transaction'
@api.model
def _get_next_reference(self, reference, acquirer=None):
if acquirer and acquirer.provider == 'sips':
reference = re.sub(r'[^0-9a-zA-Z]+', 'x' , reference)
return super(PaymentTransactionSips, self)._get_next_reference(reference, acquirer=acquirer)
class TxSips(models.Model):
_inherit = 'payment.transaction'

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -15,4 +15,5 @@
],
'images': ['static/description/icon.png'],
'installable': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -8,6 +8,7 @@ from flectra import api, fields, models, _
from flectra.addons.payment.models.payment_acquirer import ValidationError
from flectra.exceptions import UserError
from flectra.tools.safe_eval import safe_eval
from flectra.tools.float_utils import float_round
_logger = logging.getLogger(__name__)
@ -41,12 +42,12 @@ class PaymentAcquirerStripe(models.Model):
stripe_tx_values = dict(tx_values)
temp_stripe_tx_values = {
'company': self.company_id.name,
'amount': tx_values.get('amount'),
'currency': tx_values.get('currency') and tx_values.get('currency').name or '',
'currency_id': tx_values.get('currency') and tx_values.get('currency').id or '',
'address_line1': tx_values.get('partner_address'),
'amount': tx_values['amount'], # Mandatory
'currency': tx_values['currency'].name, # Mandatory anyway
'currency_id': tx_values['currency'].id, # same here
'address_line1': tx_values.get('partner_address'), # Any info of the partner is not mandatory
'address_city': tx_values.get('partner_city'),
'address_country': tx_values.get('partner_country') and tx_values['partner_country'].name or '',
'address_country': tx_values.get('partner_country') and tx_values.get('partner_country').name or '',
'email': tx_values.get('partner_email'),
'address_zip': tx_values.get('partner_zip'),
'name': tx_values.get('partner_name'),
@ -106,7 +107,7 @@ class PaymentTransactionStripe(models.Model):
def _create_stripe_charge(self, acquirer_ref=None, tokenid=None, email=None):
api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url())
charge_params = {
'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else self.amount*100),
'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)),
'currency': self.currency_id.name,
'metadata[reference]': self.reference,
'description': self.reference,
@ -126,7 +127,7 @@ class PaymentTransactionStripe(models.Model):
@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)
result = self._create_stripe_charge(acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email)
return self._stripe_s2s_validate_tree(result)
@ -135,7 +136,7 @@ class PaymentTransactionStripe(models.Model):
refund_params = {
'charge': self.acquirer_reference,
'amount': int(self.amount*100), # by default, stripe refund the full amount (we don't really need to specify the value)
'amount': int(float_round(self.amount * 100, 2)), # by default, stripe refund the full amount (we don't really need to specify the value)
'metadata[reference]': self.reference,
}

View File

@ -62,31 +62,14 @@ class StripeTest(StripeCommon):
# ----------------------------------------
# Test: button direct rendering
# ----------------------------------------
form_values = {
'amount': 320.0,
'currency': 'EUR',
'address_line1': 'Huge Street 2/543',
'address_city': 'Sin City',
'address_country': 'Belgium',
'email': 'norbert.buyer@example.com',
'address_zip': '1000',
'name': 'Norbert Buyer',
'phone': '0032 12 34 56 78'
}
# render the button
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"
res = self.stripe.render('SO404', 320.0, self.currency_euro.id, values=self.buyer_values).decode('utf-8')
popup_script_src = 'script src="https://checkout.stripe.com/checkout.js"'
# check form result
if "https://checkout.stripe.com/checkout.js" in res[0]:
self.assertEqual(post_url, 'https://checkout.stripe.com/checkout.js', 'Stripe: wrong form POST url')
self.assertIn(popup_script_src, res, "Stripe: popup script not found in template render")
# Generated and received
if email in res[0]:
self.assertEqual(
email, form_values.get('email'),
'Stripe: wrong value for input %s: received %s instead of %s' % (email, email, form_values.get('email'))
)
self.assertIn(self.buyer_values.get('partner_email'), res, 'Stripe: email input not found in rendered template')
@unittest.skip("Stripe test disabled: We do not want to overload Stripe with runbot's requests")
def test_30_stripe_form_management(self):

View File

@ -3,3 +3,4 @@
from . import models
from . import controllers
from flectra.addons.payment.models.payment_acquirer import create_missing_journal_for_acquirers

View File

@ -15,4 +15,5 @@
],
'installable': True,
'auto_install': True,
'post_init_hook': 'create_missing_journal_for_acquirers',
}

View File

@ -15,6 +15,19 @@ class TransferPaymentAcquirer(models.Model):
provider = fields.Selection(selection_add=[('transfer', 'Wire Transfer')], default='transfer')
@api.model
def _create_missing_journal_for_acquirers(self, company=None):
# By default, the wire transfer method uses the default Bank journal.
company = company or self.env.user.company_id
acquirers = self.env['payment.acquirer'].search(
[('provider', '=', 'transfer'), ('journal_id', '=', False), ('company_id', '=', company.id)])
bank_journal = self.env['account.journal'].search(
[('type', '=', 'bank'), ('company_id', '=', company.id)], limit=1)
if bank_journal:
acquirers.write({'journal_id': bank_journal.id})
return super(TransferPaymentAcquirer, self)._create_missing_journal_for_acquirers(company=company)
def transfer_get_form_action_url(self):
return '/payment/transfer/feedback'