[MERGE] fix payment flow with acquirers in e-commerce, invoices and quotations

As some flows were broken, this set of fixs improve the different routes for all acquirers
See commit messages for more information

Thanks to @jpr-odoo for his first implementation and to @tde and @fgi
This commit is contained in:
fda-odoo
2018-01-25 13:12:25 +01:00
31 changed files with 385 additions and 134 deletions
@@ -253,8 +253,8 @@
</template>
<template id="portal_invoice_error" name="Invoice error/warning display">
<div class="row">
<div t-att-class="'col-md-6 col-md-offset-3 text-center alert alert-dismissable %s' % ('alert-danger' if error else 'alert-warning')">
<div class="row mr16">
<div t-attf-class="'col-md-12 mr16 ml16 alert alert-dismissable' #{'alert-danger' if error else 'alert-warning'}">
<a href="#" class="close" data-dismiss="alert" aria-label="close" title="close">×</a>
<t t-if="error == 'generic'" name="generic">
There was an error processing this page.
@@ -264,8 +264,8 @@
</template>
<template id="portal_invoice_success" name="Invoice success display">
<div class="row">
<div class="col-md-6 col-md-offset-3 text-center alert alert-dismissable alert-success">
<div class="row mr16">
<div class="col-md-12 mr16 ml16 alert alert-dismissable alert-success">
<a href="#" class="close" data-dismiss="alert" aria-label="close" title="close">×</a>
</div>
</div>
@@ -94,9 +94,9 @@ class PaymentPortal(http.Controller):
# proceed to the payment
res = tx.confirm_invoice_token()
if res is not True:
params['error'] = res
return request.redirect(_build_url_w_params(error_url, params))
params['success'] = 'pay_invoice'
if tx.state != 'authorized' or not tx.acquirer_id.capture_manually:
if res is not True:
params['error'] = res
return request.redirect(_build_url_w_params(error_url, params))
params['success'] = 'pay_invoice'
return request.redirect(_build_url_w_params(success_url, params))
@@ -12,6 +12,36 @@
</xpath>
</template>
<!-- duplicata of the template payment.payment_confirmation_status.
The duplication avoid to break an existing installation in stable version-->
<template id="payment_confirmation_status">
<div t-if="payment_tx_id and payment_tx_id.state == 'done'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.done_msg' t-raw="payment_tx_id.acquirer_id.done_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'pending'" class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.pending_msg' t-raw="payment_tx_id.acquirer_id.pending_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'cancel'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.cancel_msg' t-raw="payment_tx_id.acquirer_id.cancel_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'error'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.error_msg' t-raw="payment_tx_id.acquirer_id.error_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'authorized'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your payment has been authorized.
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
</template>
<template id="portal_invoice_page_inherit_payment" name="Payment on My Invoices" inherit_id="account.portal_invoice_page">
<xpath expr="//div[@id='o_portal_account_actions']/a" position="before">
<t t-set="tx_ids" t-value="invoice.payment_tx_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))"/>
@@ -28,6 +58,13 @@
<i class="fa fa-check-circle"/> Paid
</a>
</xpath>
<xpath expr="//t[@t-call='account.portal_invoice_success']" position="after">
<div t-if="invoice.payment_tx_ids and invoice.amount_total and not success and not error" class="o_account_payment_tx_status" t-att-data-invoice-id="invoice.id">
<t t-call="account_payment.payment_confirmation_status">
<t t-set="payment_tx_id" t-value="invoice.payment_tx_id"/>
</t>
</div>
</xpath>
<xpath expr="//div[hasclass('panel-body')]" position="after">
<div class="panel-body" t-if="not tx_ids and invoice.state == 'open' and invoice.amount_total" id="portal_pay">
<div t-if="pms or s2s_acquirers or form_acquirers" id="payment_method" class="col-md-offset-3 col-md-6">
@@ -35,8 +72,8 @@
<t t-call="payment.payment_tokens_list">
<t t-set="mode" t-value="'payment'"/>
<t t-set="partner_id" t-value="invoice.partner_id.id"/>
<t t-set="success_url" t-value="'/my/invoices/%s?%s' % (invoice.id, keep_query())"/>
<t t-set="error_url" t-value="'/my/invoices/%s?%s' % (invoice.id, keep_query())"/>
<t t-set="success_url" t-value="'/my/invoices/%s%s' % (invoice.id, ('?access_token=%s' % (access_token)) if access_token else '')"/>
<t t-set="error_url" t-value="'/my/invoices/%s%s' % (invoice.id, ('?access_token=%s' % (access_token)) if access_token else '')"/>
<t t-set="access_token" t-value="access_token or ''"/>
<t t-set="callback_method" t-value="''"/>
<t t-set="form_action" t-value="'/invoice/pay/' + str(invoice.id) + '/s2s_token_tx/'"/>
@@ -119,7 +156,8 @@
inherit_id="account.portal_invoice_success">
<xpath expr="//a[hasclass('close')]" position="after">
<t t-if="success == 'pay_invoice'">
Invoice successfully paid.
<span t-if='invoice.payment_acquirer_id.done_msg' t-raw="invoice.payment_acquirer_id.done_msg"/>
<span t-if='invoice.payment_acquirer_id.post_msg' t-raw="invoice.payment_acquirer_id.post_msg"/>
</t>
</xpath>
</template>
+3 -3
View File
@@ -151,9 +151,9 @@ class PaymentAcquirer(models.Model):
"Use this field anywhere a small image is required.")
payment_icon_ids = fields.Many2many('payment.icon', string='Supported Payment Icons')
payment_flow = fields.Selection(selection=[('s2s','The customer encode his payment details on the website.'),
('form', 'The customer is redirected to the website of the acquirer.')],
default='form', required=True, string='Payment flow',
payment_flow = fields.Selection(selection=[('form', 'Redirection to the acquirer website'),
('s2s','Payment from Odoo')],
default='form', required=True, string='Payment Flow',
help="""Note: Subscriptions does not take this field in account, it uses server to server by default.""")
def _search_is_tokenized(self, operator, value):
@@ -14,6 +14,15 @@ div.card_placeholder {
transition: 0.4s cubic-bezier(0.455, 0.03, 0.515, 0.955);
pointer-events: none; }
/* if s2s form not in bootstrap_formatting */
div.o_card_brand_detail {
position: relative;
}
div.o_card_brand_detail div.card_placeholder {
right: 5px;
}
div.amex {
background-image: url("/website_payment/static/src/img/amex.png");
background-repeat: no-repeat; }
+96 -60
View File
@@ -57,29 +57,34 @@ odoo.define('payment.payment_form', function (require) {
console.warn('payment_form: unset partner_id when adding new token; things could go wrong');
}
var form_data = this.getFormData(inputs_form);
var empty_inputs = false;
var wrong_input = false;
inputs_form.toArray().forEach(function (element) {
if (element.dataset.isRequired) {
if (element.value.length === 0) {
//skip the check of non visible inputs
if ($(element).attr('type') == 'hidden') {
return true;
}
$(element).closest('div.form-group').removeClass('has-error');
$(element).siblings( ".o_invalid_field" ).remove();
//force check of forms validity (useful for Firefox that refill forms automatically on f5)
$(element).trigger("focusout");
if (element.dataset.isRequired && element.value.length === 0) {
$(element).closest('div.form-group').addClass('has-error');
empty_inputs = true;
}
else {
$(element).closest('div.form-group').removeClass('has-error');
}
$(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field">' + _.str.escapeHTML("The value is invalid.") + '</div>');
wrong_input = true;
}
else if ($(element).closest('div.form-group').hasClass('has-error')) {
wrong_input = true;
$(element).closest('div.form-group').append('<div style="color: red" class="o_invalid_field">' + _.str.escapeHTML("The value is invalid.") + '</div>');
}
});
if (empty_inputs) {
this.displayError(
_t('Missing values'),
_t('<p>Please fill all the inputs required.</p>')
);
if (wrong_input) {
return;
}
$(button).attr('disabled', true);
$(button).children('.fa-plus-circle').removeClass('fa-plus-circle')
$(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i>&nbsp;</span>');
var verify_validity = this.$el.find('input[name="verify_validity"]');
@@ -104,24 +109,30 @@ odoo.define('payment.payment_form', function (require) {
}
// if the server has returned false, we display an error
else {
self.displayError(
_t('Server Error'),
_t("<p>We are not able to add your payment method at the moment.</p>"));
if (data.error) {
self.displayError(
'',
data.error);
} else { // if the server doesn't provide an error message
self.displayError(
_t('Server Error'),
_t('e.g. Your credit card details are wrong. Please verify.'));
}
}
// here we remove the 'processing' icon from the 'add a new payment' button
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).find('span.o_loader').remove();
}).fail(function (message, data) {
// if the rpc fails, pretty obvious
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).find('span.o_loader').remove();
self.displayError(
_t('Server Error'),
_t("<p>We are not able to add your payment method at the moment.</p>") +
"<p>" +
(core.debug ? (data.data.message.replace(/\n/g, "<br />")): '') +
"</p>"
_t("We are not able to add your payment method at the moment.") +
data.data.message
);
});
}
@@ -131,7 +142,7 @@ odoo.define('payment.payment_form', function (require) {
// if there's a prepare tx url set
if ($tx_url.length === 1) {
// if the user wants to save his credit card info
var form_save_token = $('input[name="o_payment_form_save_token"]').prop('checked');
var form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked');
// then we call the route to prepare the transaction
ajax.jsonRpc($tx_url[0].value, 'call', {
'acquirer_id': parseInt(acquirer_id),
@@ -159,16 +170,14 @@ odoo.define('payment.payment_form', function (require) {
else {
self.displayError(
_t('Server Error'),
_t("<p>We are not able to redirect you to the payment form.</p>")
_t("We are not able to redirect you to the payment form.")
);
}
}).fail(function (message, data) {
self.displayError(
_t('Server Error'),
_t("<p>We are not able to redirect you to the payment form.</p>") +
"<p>" +
(core.debug ? (data.data.message.replace(/\n/g, "<br />")): '') +
"</p>"
_t("We are not able to redirect you to the payment form.<") +
data.data.message
);
});
}
@@ -176,7 +185,7 @@ odoo.define('payment.payment_form', function (require) {
// we append the form to the body and send it.
this.displayError(
_t("Cannot set-up the payment"),
_t("<p>We're unable to process your payment.</p>")
_t("We're unable to process your payment.")
);
}
}
@@ -187,7 +196,7 @@ odoo.define('payment.payment_form', function (require) {
else {
this.displayError(
_t('No payment method selected'),
_t('<p>Please select a payment method.</p>')
_t('Please select a payment method.')
);
}
},
@@ -209,29 +218,36 @@ odoo.define('payment.payment_form', function (require) {
var inputs_form = $('input', acquirer_form);
var form_data = this.getFormData(inputs_form);
var ds = $('input[name="data_set"]', acquirer_form)[0];
var empty_inputs = false;
var wrong_input = false;
inputs_form.toArray().forEach(function (element) {
if (element.dataset.isRequired) {
if (element.value.length === 0) {
//skip the check of non visible inputs
if ($(element).attr('type') == 'hidden') {
return true;
}
$(element).closest('div.form-group').removeClass('has-error');
$(element).siblings( ".o_invalid_field" ).remove();
//force check of forms validity (useful for Firefox that refill forms automatically on f5)
$(element).trigger("focusout");
if (element.dataset.isRequired && element.value.length === 0) {
$(element).closest('div.form-group').addClass('has-error');
empty_inputs = true;
}
else {
$(element).closest('div.form-group').removeClass('has-error');
}
var message = '<div style="color: red" class="o_invalid_field">' + _.str.escapeHTML("The value is invalid.") + '</div>';
$(element).closest('div.form-group').append(message);
wrong_input = true;
}
else if ($(element).closest('div.form-group').hasClass('has-error')) {
wrong_input = true;
var message = '<div style="color: red" class="o_invalid_field">' + _.str.escapeHTML("The value is invalid.") + '</div>';
$(element).closest('div.form-group').append(message);
}
});
if (empty_inputs) {
this.displayError(
_t('Missing values'),
_t('<p>Please fill all the inputs required.</p>')
);
if (wrong_input) {
return;
}
// We add a 'processing' icon into the 'add a new payment' button
$(button).attr('disabled', true);
$(button).children('.fa-plus-circle').removeClass('fa-plus-circle')
$(button).prepend('<span class="o_loader"><i class="fa fa-refresh fa-spin"></i>&nbsp;</span>');
// we force the check when adding a card trough here
@@ -259,32 +275,37 @@ odoo.define('payment.payment_form', function (require) {
}
// if the server has returned false, we display an error
else {
self.displayError(
_t('Server Error'),
_t("<p>We are not able to add your payment method at the moment.</p>")
);
if (data.error) {
self.displayError(
'',
data.error);
} else { // if the server doesn't provide an error message
self.displayError(
_t('Server Error'),
_t('e.g. Your credit card details are wrong. Please verify.'));
}
}
// here we remove the 'processing' icon from the 'add a new payment' button
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).find('span.o_loader').remove();
}).fail(function (message, data) {
// if the rpc fails, pretty obvious
$(button).attr('disabled', false);
$(button).children('.fa').addClass('fa-plus-circle')
$(button).find('span.o_loader').remove();
self.displayError(
_t('Server error'),
_t("<p>We are not able to add your payment method at the moment.</p>") +
"<p>" +
(core.debug ? (data.data.message.replace(/\n/g, "<br />")): '') +
"</p>"
_t("We are not able to add your payment method at the moment.</p>") +
data.data.message
);
});
}
else {
this.displayError(
_t('No payment method selected'),
_t('<p>Please select the option to add a new payment method.</p>')
_t('Please select the option to add a new payment method.')
);
}
},
@@ -308,7 +329,7 @@ odoo.define('payment.payment_form', function (require) {
}, function () {
self.displayError(
_t('Server Error'),
_t("<p>We are not able to delete your payment method at the moment.</p>")
_t("We are not able to delete your payment method at the moment.")
);
});
};
@@ -322,7 +343,7 @@ odoo.define('payment.payment_form', function (require) {
// if there's records linked to this payment method
var content = '';
result[pm_id].forEach(function (sub) {
content += '<p><a href="' + sub.url + '" title="' + _.str.escapeHTML(sub.description) + '">' + _.str.escapeHTML(sub.name) + '</a><p/>';
content += '<p><a href="' + sub.url + '" title="' + sub.description + '">' + sub.name + '</a><p/>';
});
content = $('<div>').html(_t('<p>This card is currently linked to the following records:<p/>') + content);
@@ -343,7 +364,7 @@ odoo.define('payment.payment_form', function (require) {
}, function (type, err) {
self.displayError(
_t('Server Error'),
_t("<p>We are not able to delete your payment method at the moment.</p>") + (core.debug ? err.data.message : '')
_t("We are not able to delete your payment method at the moment.") + err.data.message
);
});
},
@@ -391,12 +412,27 @@ odoo.define('payment.payment_form', function (require) {
return $(element).data('acquirer-id');
},
displayError: function (title, message) {
return new Dialog(null, {
title: _t('Error: ') + title,
size: 'medium',
$content: message || "",
buttons: [
{text: _t('Ok'), close: true}]}).open();
var $checkedRadio = this.$('input[type="radio"]:checked'),
acquirerID = this.getAcquirerIdFromRadio($checkedRadio[0]),
$acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID);
if ($checkedRadio.length === 0) {
return new Dialog(null, {
title: _t('Error: ') + _.str.escapeHTML(title),
size: 'medium',
$content: "<p>" + (_.str.escapeHTML(message) || "") + "</p>" ,
buttons: [
{text: _t('Ok'), close: true}]}).open();
} else {
// removed if exist error message
this.$('#payment_error').remove();
var messageResult = '<div class="alert alert-danger mb4" id="payment_error">';
if (title != '') {
messageResult = messageResult + '<b>' + _.str.escapeHTML(title) + ':</b></br>';
}
messageResult = messageResult + _.str.escapeHTML(message) + '</div>';
$acquirerForm.append(messageResult);
}
},
getFormData: function ($form) {
var unindexed_array = $form.serializeArray();
@@ -17,6 +17,7 @@ $(document).ready(function () {
if (valid_value) {
$(this).parent('.form-group').addClass('has-success');
$(this).parent('.form-group').removeClass('has-error');
$(this).siblings('.o_invalid_field').remove();
}
else {
$(this).parent('.form-group').addClass('has-error');
@@ -31,6 +32,7 @@ $(document).ready(function () {
if (valid_value) {
$(this).parent('.form-group').addClass('has-success');
$(this).parent('.form-group').removeClass('has-error');
$(this).siblings('.o_invalid_field').remove();
}
else {
$(this).parent('.form-group').addClass('has-error');
@@ -46,6 +48,7 @@ $(document).ready(function () {
if (valid_value) {
$(this).parent('.form-group').addClass('has-success');
$(this).parent('.form-group').removeClass('has-error');
$(this).siblings('.o_invalid_field').remove();
}
else {
$(this).parent('.form-group').addClass('has-error');
@@ -1,6 +1,6 @@
.o_payment_form {
label > input[type="radio"], input[type="checkbox"]{
vertical-align: bottom;
vertical-align: top;
margin-right: 5px;
}
@@ -14,6 +14,7 @@
label {
font-weight: normal;
margin-top: 5px;
}
.panel-body:first-child {
@@ -35,16 +36,18 @@
}
.payment_icon_list {
position: relative;
li {
padding-left: 5px !important;
padding-right: 0px !important;
}
.more_option {
.o-position-absolute(@right: 0px, @bottom: -10px);
font-size:10px;
}
margin-top: 0px !important;
margin-bottom: -5px !important;
}
}
}
@@ -34,7 +34,7 @@
<!-- TDE FIXME: was customize_show=True -->
<template id="pay_meth_link" inherit_id="portal.portal_layout">
<xpath expr="//div[hasclass('o_portal_my_details')]" position="inside">
<t t-if="request.env['payment.acquirer'].search([('website_published', '=', True), ('registration_view_template_id', '!=', False)])">
<t t-if="request.env['payment.acquirer'].search([('website_published', '=', True), ('registration_view_template_id', '!=', False), ('payment_flow', '=', 's2s')])">
<div class='manage_payment_method mt16'><a href="/my/payment_method">Manage your payment methods</a></div>
</t>
</xpath>
@@ -122,5 +122,31 @@
</t>
</template>
<template id="payment_confirmation_status">
<div t-if="payment_tx_id and payment_tx_id.state == 'done'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.done_msg' t-raw="payment_tx_id.acquirer_id.done_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'pending'" class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.pending_msg' t-raw="payment_tx_id.acquirer_id.pending_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'cancel'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.cancel_msg' t-raw="payment_tx_id.acquirer_id.cancel_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'error'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.error_msg' t-raw="payment_tx_id.acquirer_id.error_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'authorized'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your payment has been authorized.
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
</template>
</data>
</odoo>
+7 -6
View File
@@ -41,6 +41,7 @@
<div class="panel panel-default">
<t t-set="acquirers_count" t-value="(len(s2s_acquirers) if s2s_acquirers else 0) + (len(form_acquirers) if form_acquirers else 0)"/>
<t t-set="pms_count" t-value="len(pms) if pms else 0"/>
<t t-set="MAX_BRAND_LINE" t-value="3"/>
<t t-foreach="s2s_acquirers" t-as="acq">
<div class="panel-body">
<label>
@@ -65,13 +66,13 @@
<ul class="pull-right list-inline payment_icon_list">
<t t-set="i" t-value="0"/>
<t t-foreach="acq.payment_icon_ids" t-as="pm_icon">
<li t-att-class="'' if (i &lt; 3) else 'hidden'">
<li t-att-class="'' if (i &lt; MAX_BRAND_LINE) else 'hidden'">
<span t-field="pm_icon.image_payment_form"
t-options='{"widget": "image", "alt-field": "name"}' />
</li>
<li t-if="i==3" style="display:block;">
<li t-if="i==MAX_BRAND_LINE" style="display:block;">
<span class="pull-right more_option text-info">
<a href="#" class="o_payment_form_pay_icon_more" data-toggle="tooltip" t-att-title="', '.join([opt.name for opt in acq.payment_icon_ids[3:]])">and more</a>
<a href="#" class="o_payment_form_pay_icon_more" data-toggle="tooltip" t-att-title="', '.join([opt.name for opt in acq.payment_icon_ids[MAX_BRAND_LINE:]])">and more</a>
</span>
</li>
<t t-set="i" t-value="i+1"/>
@@ -102,13 +103,13 @@
<ul class="pull-right list-inline payment_icon_list">
<t t-set="i" t-value="0"/>
<t t-foreach="acq.payment_icon_ids" t-as="pm_icon">
<li t-att-class=" '' if (i &lt; 3) else 'hidden'">
<li t-att-class=" '' if (i &lt; MAX_BRAND_LINE) else 'hidden'">
<span t-field="pm_icon.image_payment_form"
t-options='{"widget": "image", "alt-field": "name"}' />
</li>
<li t-if="i==3" style="display:block;">
<li t-if="i==MAX_BRAND_LINE" style="display:block;">
<span class="pull-right more_option text-info">
<a href="#" class="o_payment_form_pay_icon_more" data-toggle="tooltip" t-att-title="', '.join([opt.name for opt in acq.payment_icon_ids[3:]])">and more</a>
<a href="#" class="o_payment_form_pay_icon_more" data-toggle="tooltip" t-att-title="', '.join([opt.name for opt in acq.payment_icon_ids[MAX_BRAND_LINE:]])">and more</a>
</span>
</li>
<t t-set="i" t-value="i+1"/>
+1 -1
View File
@@ -83,7 +83,7 @@
<group name="acquirer_config">
<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)]}"/>
<field name="save_token" widget="radio" attrs="{'invisible': ['|', ('token_implemented', '=', False), ('payment_flow', '=', 's2s')]}"/>
<field name="fees_active" attrs="{'invisible': [('fees_implemented', '=', False)]}"/>
<field name="fees_dom_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/>
<field name="fees_dom_var" attrs="{'invisible': [('fees_active', '=', False)]}"/>
+27 -2
View File
@@ -3,8 +3,9 @@ import pprint
import logging
from werkzeug import urls, utils
from odoo import http
from odoo import http, _
from odoo.http import request
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
@@ -35,11 +36,35 @@ class AuthorizeController(http.Controller):
def authorize_s2s_create_json(self, **kwargs):
acquirer_id = int(kwargs.get('acquirer_id'))
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
if not kwargs.get('partner_id'):
kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id)
return acquirer.s2s_process(kwargs).id
@http.route(['/payment/authorize/s2s/create_json_3ds'], type='json', auth='public', csrf=False)
def authorize_s2s_create_json_3ds(self, verify_validity=False, **kwargs):
token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs)
token = False
acquirer = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id')))
try:
if not kwargs.get('partner_id'):
kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id)
token = acquirer.s2s_process(kwargs)
except ValidationError as e:
message = e.args[0]
if isinstance(message, dict) and 'missing_fields' in message:
msg = _("The transaction cannot be processed because some contact details are missing or invalid: ")
message = msg + ', '.join(message['missing_fields']) + '. '
if request.env.user._is_public():
message += _("Please sign in to complete your profile.")
# update message if portal mode = b2b
if request.env['ir.config_parameter'].sudo().get_param('auth_signup.allow_uninvited', 'False').lower() == 'false':
message += _("If you don't have any account, please ask your salesperson to update your profile. ")
else:
message += _("Please complete your profile. ")
return {
'error': message
}
if not token:
res = {
@@ -139,6 +139,11 @@ class AuthorizeAPI():
etree.SubElement(payment_profile, "customerType").text = 'business' if partner.is_company else 'individual'
billTo = etree.SubElement(payment_profile, "billTo")
etree.SubElement(billTo, "address").text = (partner.street or '' + (partner.street2 if partner.street2 else '')) or None
missing_fields = [partner._fields[field].string for field in ['city', 'country_id', 'zip'] if not partner[field]]
if missing_fields:
raise ValidationError({'missing_fields': missing_fields})
etree.SubElement(billTo, "city").text = partner.city
etree.SubElement(billTo, "state").text = partner.state_id.name or None
etree.SubElement(billTo, "zip").text = partner.zip
@@ -51,7 +51,7 @@
<template id="authorize_s2s_form">
<input type="hidden" name="data_set" data-create-route="/payment/authorize/s2s/create_json_3ds"/>
<div class="mt8">
<div t-attf-class="mt8 #{'' if bootstrap_formatting else 'o_card_brand_detail'}">
<div t-att-class="'form-group col-md-12' if bootstrap_formatting else 'form-group'">
<input type="tel" name="cc_number" id="cc_number" class="form-control" placeholder="Card number" data-is-required="true"/>
<div class="card_placeholder"></div>
+4
View File
@@ -30,11 +30,15 @@ class OgoneController(http.Controller):
@http.route(['/payment/ogone/s2s/create_json'], type='json', auth='public', csrf=False)
def ogone_s2s_create_json(self, **kwargs):
if not kwargs.get('partner_id'):
kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id)
new_id = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs)
return new_id.id
@http.route(['/payment/ogone/s2s/create_json_3ds'], type='json', auth='public', csrf=False)
def ogone_s2s_create_json_3ds(self, verify_validity=False, **kwargs):
if not kwargs.get('partner_id'):
kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id)
token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs)
if not token:
@@ -40,7 +40,7 @@
<input type="hidden" name="acquirer_id" t-att-value="id"/>
<input t-if="return_url" type="hidden" name="return_url" t-att-value="return_url"/>
<input t-if="partner_id" type="hidden" name="partner_id" t-att-value="partner_id"/>
<div class="mt8">
<div t-attf-class="mt8 #{'' if bootstrap_formatting else 'o_card_brand_detail'}">
<div t-att-class="'form-group col-md-12' if bootstrap_formatting else 'form-group'">
<input type="tel" name="cc_number" id="cc_number" class="form-control" placeholder="Card number" data-is-required="true"/>
<div class="card_placeholder"></div>
@@ -15,6 +15,8 @@ class StripeController(http.Controller):
def stripe_s2s_create_json(self, **kwargs):
acquirer_id = int(kwargs.get('acquirer_id'))
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
if not kwargs.get('partner_id'):
kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id)
return acquirer.s2s_process(kwargs).id
@http.route(['/payment/stripe/s2s/create'], type='http', auth='public')
@@ -36,6 +38,8 @@ class StripeController(http.Controller):
@http.route(['/payment/stripe/s2s/create_json_3ds'], type='json', auth='public', csrf=False)
def stripe_s2s_create_json_3ds(self, verify_validity=False, **kwargs):
if not kwargs.get('partner_id'):
kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id)
token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs)
if not token:
@@ -120,6 +120,14 @@ odoo.define('payment_stripe.stripe', function(require) {
}).then(function (data) {
try { provider_form[0].innerHTML = data; } catch (e) {};
});
} else if (window.location.href.includes("/my/orders/")) {
var create_tx = ajax.jsonRpc('/pay/sale/' + acquirer_id + '/form_tx/', 'call', {
so_id: so_id,
access_token: access_token,
acquirer_id: acquirer_id
}).then(function (data) {
try { provider_form.innerHTML = data; } catch (e) {};
});
}
else {
var create_tx = ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {
@@ -26,7 +26,7 @@
<template id="stripe_s2s_form">
<input type="hidden" name="data_set" data-create-route="/payment/stripe/s2s/create_json_3ds"/>
<div class="mt8">
<div t-attf-class="mt8 #{'' if bootstrap_formatting else 'o_card_brand_detail'}">
<div t-att-class="'form-group col-md-12' if bootstrap_formatting else 'form-group'">
<input type="tel" name="cc_number" id="cc_number" class="form-control" placeholder="Card number" data-is-required="true"/>
<div class="card_placeholder"></div>
@@ -15,8 +15,8 @@
</div>
<div id="o_portal_signature" class="panel-body" style="padding: 0"/>
</div>
<div class="mt16 pull-right">
<button type="submit" class="btn btn-primary o_portal_sign_submit"><t t-esc="widget.options.signLabel or 'Accept &amp; Sign'"/></button>
<div class="mt16 clearfix">
<button type="submit" class="btn btn-primary o_portal_sign_submit pull-right"><t t-esc="widget.options.signLabel or 'Accept &amp; Sign'"/></button>
</div>
</form>
</t>
+4 -4
View File
@@ -335,8 +335,8 @@
</template>
<template id="portal_order_error" name="Order error/warning display">
<div class="row">
<div t-att-class="'col-md-6 col-md-offset-3 text-center alert alert-dismissable %s' % ('alert-danger' if error else 'alert-warning')">
<div class="row mr16">
<div t-attf-class="'col-md-12 mr16 ml16 alert alert-dismissable' #{'alert-danger' if error else 'alert-warning'}">
<a href="#" class="close" data-dismiss="alert" aria-label="close" title="close">×</a>
<t t-if="error == 'generic'" name="generic">
There was an error processing this page.
@@ -346,8 +346,8 @@
</template>
<template id="portal_order_success" name="Order success display">
<div class="row">
<div class="col-md-6 col-md-offset-3 text-center alert alert-dismissable alert-success">
<div class="row mr16">
<div class="col-md-12 mr16 ml16 alert alert-dismissable alert-success">
<a href="#" class="close" data-dismiss="alert" aria-label="close" title="close">×</a>
</div>
</div>
+5 -5
View File
@@ -94,9 +94,9 @@ class PaymentPortal(http.Controller):
# proceed to the payment
res = tx.confirm_sale_token()
if res is not True:
params['error'] = res
return request.redirect(_build_url_w_params(error_url, params))
params['success'] = 'pay_sale'
if tx.state != 'authorized' or not tx.acquirer_id.capture_manually:
if res is not True:
params['error'] = res
return request.redirect(_build_url_w_params(error_url, params))
params['success'] = 'pay_sale'
return request.redirect(_build_url_w_params(success_url, params))
+2 -4
View File
@@ -64,12 +64,10 @@ class PaymentTransaction(models.Model):
_logger.info('<%s> transaction authorized, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
if self.sale_order_id.state in ('draft', 'sent'):
self.sale_order_id.with_context(send_email=True).action_confirm()
if self.state == 'done':
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s) and generating invoice', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
elif self.state == 'done':
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
if self.sale_order_id.state in ('draft', 'sent'):
self.sale_order_id.with_context(send_email=True).action_confirm()
self._generate_and_pay_invoice()
elif self.state not in ['cancel', 'error'] and self.sale_order_id.state == 'draft':
_logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
self.sale_order_id.force_quotation_send()
@@ -1,4 +1,34 @@
<odoo>
<!-- duplicata of the template payment.payment_confirmation_status.
The duplication avoid to break an existing installation in stable version-->
<template id="payment_confirmation_status">
<div t-if="payment_tx_id and payment_tx_id.state == 'done'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.done_msg' t-raw="payment_tx_id.acquirer_id.done_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'pending'" class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.pending_msg' t-raw="payment_tx_id.acquirer_id.pending_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'cancel'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.cancel_msg' t-raw="payment_tx_id.acquirer_id.cancel_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'error'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.error_msg' t-raw="payment_tx_id.acquirer_id.error_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'authorized'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your payment has been authorized.
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
</template>
<template id="portal_my_orders_payment" name="Payment on my orders" inherit_id="sale.portal_order_page">
<xpath expr="//t[@name='portal_confirmation_sign']" position="after">
<t t-if="not invoices and order.state in ('sent', 'sale') and portal_confirmation == 'pay' and order.payment_tx_id.state != 'done'" name="portal_confirmation_pay">
@@ -12,8 +42,14 @@
</a>
</t>
</xpath>
<xpath expr="//t[@t-call='sale.portal_order_success']" position="after">
<div t-if="order.payment_tx_ids and not invoices and order.state in ('sent', 'sale') and portal_confirmation == 'pay' and not success and not error" class="o_sale_payment_tx_status" t-att-data-order-id="order.id">
<t t-set="payment_tx_id" t-value="order.payment_tx_id"/>
<t t-call="sale_payment.payment_confirmation_status"/>
</div>
</xpath>
<xpath expr="//div[@id='portal_sign']" position="after">
<div class="row" t-if="not invoices and order.state in ('sent', 'sale') and portal_confirmation == 'pay'" id="portal_pay">
<div class="row" t-if="not invoices and order.state in ('sent') and portal_confirmation == 'pay'" id="portal_pay">
<div class="col-md-offset-3 col-md-6">
<div t-if="pms or s2s_acquirers or form_acquirers" id="payment_method" class="text-left col-md-13">
<h4 class="mb24">Pay with</h4>
@@ -21,8 +57,8 @@
<t t-set="mode" t-value="'payment'"/>
<t t-set="partner_id" t-value="order.partner_id.id"/>
<t t-set="access_token" t-value="access_token"/>
<t t-set="success_url" t-value="'/my/orders/%s?%s' % (order.id, keep_query())"/>
<t t-set="error_url" t-value="'/my/orders/%s?%s' % (order.id, keep_query())"/>
<t t-set="success_url" t-value="'/my/orders/%s%s' % (order.id, ('?access_token=%s' % (access_token)) if access_token else '')"/>
<t t-set="error_url" t-value="'/my/orders/%s%s' % (order.id, ('?access_token=%s' % (access_token)) if access_token else '')"/>
<t t-set="callback_method" t-value="''"/>
<t t-set="submit_txt" t-value="'Pay Now'"/>
<t t-set="icon_class" t-value="'fa-lock'"/>
@@ -101,7 +137,8 @@
inherit_id="sale.portal_order_success">
<xpath expr="//a[hasclass('close')]" position="after">
<t t-if="success == 'pay_sale'">
Order successfully paid.
<span t-if='order.payment_acquirer_id.done_msg' t-raw="order.payment_acquirer_id.done_msg"/>
<span t-if='order.payment_acquirer_id.post_msg' t-raw="order.payment_acquirer_id.post_msg"/>
</t>
</xpath>
</template>
+1
View File
@@ -79,6 +79,7 @@ class sale_quote(http.Controller):
'tx_id': Transaction.id if Transaction else False,
'tx_state': Transaction.state if Transaction else False,
'tx_post_msg': Transaction.acquirer_id.post_msg if Transaction else False,
'payment_tx': Transaction,
'need_payment': order_sudo.invoice_status == 'to invoice' and Transaction.state in ['draft', 'cancel', 'error'],
'token': token,
'return_url': '/shop/payment/validate',
+4 -1
View File
@@ -51,6 +51,9 @@ class SaleOrder(models.Model):
template = self.env.ref('website_quote.website_quote_template_default', raise_if_not_found=False)
return template and template.active and template or False
def _get_default_online_payment(self):
return 1 if self.env['ir.config_parameter'].sudo().get_param('sale.sale_portal_confirmation_options', default='none') == 'pay' else 0
template_id = fields.Many2one(
'sale.quote.template', 'Quotation Template',
readonly=True,
@@ -66,7 +69,7 @@ class SaleOrder(models.Model):
quote_viewed = fields.Boolean('Quotation Viewed')
require_payment = fields.Selection([
(0, 'Online Signature'),
(1, 'Online Payment')], default=0, string='Confirmation Mode',
(1, 'Online Payment')], default=_get_default_online_payment, string='Confirmation Mode',
help="Choose how you want to confirm an order to launch the delivery process. You can either "
"request a digital signature or an upfront payment. With a digital signature, you can "
"request the payment when issuing the invoice.")
@@ -163,6 +163,34 @@
</xpath>
</template>
<!-- duplicata of the template payment.payment_confirmation_status.
The duplication avoid to break an existing installation in stable version-->
<template id="payment_confirmation_status">
<div t-if="payment_tx_id and payment_tx_id.state == 'done'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.done_msg' t-raw="payment_tx_id.acquirer_id.done_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'pending'" class="alert alert-warning alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.pending_msg' t-raw="payment_tx_id.acquirer_id.pending_msg"/>
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'cancel'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.cancel_msg' t-raw="payment_tx_id.acquirer_id.cancel_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'error'" class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
<span t-if='payment_tx_id.acquirer_id.error_msg' t-raw="payment_tx_id.acquirer_id.error_msg"/>
</div>
<div t-if="payment_tx_id and payment_tx_id.state == 'authorized'" class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your payment has been authorized.
<span t-if='payment_tx_id.acquirer_id.post_msg' t-raw="payment_tx_id.acquirer_id.post_msg"/>
</div>
</template>
<!-- Complete page of the quotation -->
<template id="so_quotation" name="Product Quotation">
<t t-call="portal.portal_layout">
@@ -257,29 +285,18 @@
and do not hesitate to <a href="#discussion">contact us</a> for
any question.
</div>
<div class="alert alert-warning alert-dismissable" t-if="tx_id and tx_state == 'done'">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your payment has been received, thank you for your trust.
<span t-if='tx_post_msg' t-raw="tx_post_msg"/>
</div>
<div class="alert alert-warning alert-dismissable" t-if="tx_id and tx_state == 'pending'">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your transaction is waiting confirmation.
<span t-if='tx_post_msg' t-raw="tx_post_msg"/>
</div>
<div class="alert alert-success alert-dismissable" t-if="tx_id and tx_state == 'authorized'">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
Your payment has been authorized.
<span t-if='tx_post_msg' t-raw="tx_post_msg"/>
</div>
<t t-if="payment_tx">
<t t-set="payment_tx_id" t-value="payment_tx"/>
<t t-call="website_quote.payment_confirmation_status"/>
</t>
<div class="alert alert-warning alert-dismissable" t-if="message==4 and quotation.state != 'sent'">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&amp;times;</button>
This order
<t t-if="quotation.state=='draft'">has not yet been sent</t>
<t t-if="quotation.state=='cancel'">has already been cancelled</t>
<t t-if="quotation.state not in ('cancel','draft')">has already been validated</t>
! You can <a href="#discussion">contact us</a> for
any question.
<t t-if="quotation.state=='draft'">has not yet been sent!</t>
<t t-if="quotation.state=='cancel'">has already been cancelled!</t>
<t t-if="quotation.state not in ('cancel','draft')">has already been validated!</t>
You can <a href="#discussion">contact us</a> for
any questions.
</div>
<!-- modal relative to the actions Accept/Reject/Cancel -->
@@ -332,6 +349,7 @@
<t t-set="icon_class" t-value="'fa-lock'"/>
<t t-set="form_action" t-value="'/quote/' + str(quotation.id) + '/transaction/token'"/>
<t t-set="prepare_tx_url" t-value="'/quote/' + str(quotation.id) + '/transaction/'"/>
<t t-set="access_token" t-value="quotation.access_token"/>
</t>
</div>
</div>
+6 -4
View File
@@ -749,7 +749,7 @@ class WebsiteSale(http.Controller):
values['s2s_acquirers'] = [acq for acq in acquirers if acq.payment_flow == 's2s' and acq.registration_view_template_id]
values['tokens'] = request.env['payment.token'].search(
[('partner_id', '=', order.partner_id.id),
('acquirer_id', 'in', [acq.id for acq in values['s2s_acquirers']])])
('acquirer_id', 'in', acquirers.ids)])
for acq in values['form_acquirers']:
acq.form = acq.with_context(submit_class='btn btn-primary', submit_txt=_('Pay Now')).sudo().render(
@@ -858,9 +858,11 @@ class WebsiteSale(http.Controller):
# we proceed the s2s payment
res = tx.confirm_sale_token()
# we then redirect to the page that validates the payment by giving it error if there's one
if res is not True:
return request.redirect('/shop/payment/validate?success=False&error=%s' % res)
return request.redirect('/shop/payment/validate?success=True')
if tx.state != 'authorized' or not tx.acquirer_id.capture_manually:
if res is not True:
return request.redirect('/shop/payment/validate?success=False&error=%s' % res)
return request.redirect('/shop/payment/validate?success=True')
return request.redirect('/shop/payment/validate')
@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):
+1
View File
@@ -7,6 +7,7 @@ from . import mail_compose_message
from . import product
from . import res_country
from . import res_partner
from . import payment
from . import sale_order
from . import ir_model_fields
from . import website
+25
View File
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import models
_logger = logging.getLogger(__name__)
class PaymentTransaction(models.Model):
_inherit = 'payment.transaction'
# --------------------------------------------------
# Sale management
# --------------------------------------------------
def _confirm_so(self):
"""Generate the invoice automatically when option is enabled from ecommerce setting"""
result = super(PaymentTransaction, self)._confirm_so()
IrConfigParameter = self.env['ir.config_parameter'].sudo()
if self.state == 'done' and IrConfigParameter.get_param('website_sale.automatic_invoice', default=False):
_logger.info('<%s> transaction completed, generating invoice for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
self._generate_and_pay_invoice()
return result
+4
View File
@@ -1537,6 +1537,7 @@
<div t-att-class="'panel %s' % (
(order.payment_tx_id.state == 'pending' and 'panel-info') or
(order.payment_tx_id.state == 'done' and 'panel-success') or
(order.payment_tx_id.state == 'authorized' and 'panel-success') or
'panel-danger')">
<a groups="base.group_system" class="btn pull-right" target="_blank"
t-att-href="'/web#model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % ('payment.acquirer', order.payment_acquirer_id.id, 'payment.action_payment_acquirer')">
@@ -1555,6 +1556,9 @@
<t t-if="order.payment_tx_id.state == 'error'">
<t t-raw="order.payment_acquirer_id.error_msg"/>
</t>
<t t-if="order.payment_tx_id.state == 'authorized'">
Your payment has been authorized.
</t>
</div>
<div class="panel-body" t-if="order.payment_acquirer_id.post_msg">
<t t-raw="order.payment_acquirer_id.post_msg" />