diff --git a/addons/account/controllers/portal.py b/addons/account/controllers/portal.py
index 0da2bd0ce5d..e66e6a68d35 100644
--- a/addons/account/controllers/portal.py
+++ b/addons/account/controllers/portal.py
@@ -3,6 +3,7 @@
from odoo import http, _
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
+from odoo.addons.payment.controllers.portal import PaymentProcessing
from odoo.exceptions import AccessError, MissingError
from odoo.http import request
@@ -87,6 +88,7 @@ class PortalAccount(CustomerPortal):
return self._show_report(model=invoice_sudo, report_type=report_type, report_ref='account.account_invoices', download=download)
values = self._invoice_get_page_view_values(invoice_sudo, access_token, **kw)
+ PaymentProcessing.remove_payment_transaction(invoice_sudo.transaction_ids)
return request.render("account.portal_invoice_page", values)
# ------------------------------------------------------------
diff --git a/addons/account_payment/controllers/payment.py b/addons/account_payment/controllers/payment.py
index 5c3f6f0ea31..c9ef4530f99 100644
--- a/addons/account_payment/controllers/payment.py
+++ b/addons/account_payment/controllers/payment.py
@@ -3,6 +3,7 @@
from odoo import http, _
from odoo.addons.portal.controllers.portal import _build_url_w_params
+from odoo.addons.payment.controllers.portal import PaymentProcessing
from odoo.http import request, route
@@ -31,16 +32,17 @@ class PaymentPortal(http.Controller):
save_token = False # we avoid to create a token for the public user
vals = {
'acquirer_id': acquirer_id,
+ 'return_url': success_url,
}
if save_token:
vals['type'] = 'form_save'
transaction = invoice_sudo._create_payment_transaction(vals)
+ PaymentProcessing.add_payment_transaction(transaction)
return transaction.render_invoice_button(
invoice_sudo,
- success_url,
submit_txt=_('Pay & Confirm'),
render_values={
'type': 'form_save' if save_token else 'form',
@@ -75,9 +77,11 @@ class PaymentPortal(http.Controller):
vals = {
'payment_token_id': token.id,
'type': 'server2server',
+ 'return_url': success_url,
}
- invoice_sudo._create_payment_transaction(vals)
+ tx = invoice_sudo._create_payment_transaction(vals)
+ PaymentProcessing.add_payment_transaction(tx)
params['success'] = 'pay_invoice'
return request.redirect(_build_url_w_params(success_url, params))
diff --git a/addons/account_payment/models/payment.py b/addons/account_payment/models/payment.py
index 5669d2b300d..7d20ec0d6b0 100644
--- a/addons/account_payment/models/payment.py
+++ b/addons/account_payment/models/payment.py
@@ -12,9 +12,8 @@ _logger = logging.getLogger(__name__)
class PaymentTransaction(models.Model):
_inherit = 'payment.transaction'
- def render_invoice_button(self, invoice, return_url, submit_txt=None, render_values=None):
+ def render_invoice_button(self, invoice, submit_txt=None, render_values=None):
values = {
- 'return_url': return_url,
'partner_id': invoice.partner_id.id,
}
if render_values:
diff --git a/addons/account_payment/views/account_portal_templates.xml b/addons/account_payment/views/account_portal_templates.xml
index ffcbb7f99c6..0c7ddc0ce88 100644
--- a/addons/account_payment/views/account_portal_templates.xml
+++ b/addons/account_payment/views/account_portal_templates.xml
@@ -45,7 +45,7 @@
×
-
+
@@ -92,11 +92,11 @@
-
-
+
You have credits card registered, you can log-in to be able to use them.
@@ -137,7 +137,7 @@
Communication:
-
+
Done, your online payment has been successfully processed. Thank you for your order.
diff --git a/addons/payment/__manifest__.py b/addons/payment/__manifest__.py
index c353f9b0ad3..97890e9b3ce 100644
--- a/addons/payment/__manifest__.py
+++ b/addons/payment/__manifest__.py
@@ -12,6 +12,7 @@
'data/account_data.xml',
'data/payment_icon_data.xml',
'data/payment_acquirer_data.xml',
+ 'data/payment_cron.xml',
'views/payment_views.xml',
'views/account_payment_views.xml',
'views/account_invoice_views.xml',
diff --git a/addons/payment/controllers/portal.py b/addons/payment/controllers/portal.py
index 904c440bcac..057ee72aa3b 100644
--- a/addons/payment/controllers/portal.py
+++ b/addons/payment/controllers/portal.py
@@ -1,10 +1,102 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import logging
+
from odoo import http, _
from odoo.http import request
+from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from datetime import datetime, timedelta
+_logger = logging.getLogger(__name__)
+
+class PaymentProcessing(http.Controller):
+
+ @staticmethod
+ def remove_payment_transaction(transactions):
+ tx_ids_list = request.session.get("__payment_tx_ids__", [])
+ if transactions:
+ for tx in transactions:
+ if tx.id in tx_ids_list:
+ tx_ids_list.remove(tx.id)
+ else:
+ return False
+ request.session["__payment_tx_ids__"] = tx_ids_list
+ return True
+
+ @staticmethod
+ def add_payment_transaction(transactions):
+ if not transactions:
+ return False
+ tx_ids_list = set(request.session.get("__payment_tx_ids__", [])) | set(transactions.ids)
+ request.session["__payment_tx_ids__"] = tx_ids_list
+ return True
+
+ @staticmethod
+ def get_payment_transaction_ids():
+ # return the ids and not the recordset, since we might need to
+ # sudo the browse to access all the record
+ # I prefer to let the controller chose when to access to payment.transaction using sudo
+ return request.session.get("__payment_tx_ids__", [])
+
+ @http.route(['/payment/process'], type="http", auth="public", website=True)
+ def payment_status_page(self, **kwargs):
+ # When the customer is redirect to this website page,
+ # we retrieve the payment transaction list from his session
+ tx_ids_list = self.get_payment_transaction_ids()
+ payment_transaction_ids = request.env['payment.transaction'].sudo().browse(tx_ids_list).exists()
+
+ render_ctx = {
+ 'payment_tx_ids': payment_transaction_ids.ids,
+ }
+ return request.render("payment.payment_process_page", render_ctx)
+
+ @http.route(['/payment/process/poll'], type="json", auth="public")
+ def payment_status_poll(self):
+ # retrieve the transactions
+ tx_ids_list = self.get_payment_transaction_ids()
+
+ payment_transaction_ids = request.env['payment.transaction'].sudo().search([
+ ('id', 'in', list(tx_ids_list)),
+ ('date', '>=', (datetime.now() - timedelta(days=1)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
+ ])
+ if not payment_transaction_ids:
+ return {
+ 'success': False,
+ 'error': 'no_tx_found',
+ }
+ # create the returned dictionnary
+ result = {
+ 'success': True,
+ 'transactions': [],
+ }
+ # populate the returned dictionnary with the transactions data
+ for tx in payment_transaction_ids:
+ message_to_display = tx.acquirer_id[tx.state + '_msg'] if tx.state in ['done', 'pending', 'cancel', 'error'] else None
+ result['transactions'].append({
+ 'reference': tx.reference,
+ 'state': tx.state,
+ 'return_url': tx.return_url,
+ 'is_processed': tx.is_processed,
+ 'state_message': tx.state_message,
+ 'message_to_display': message_to_display,
+ 'amount': tx.amount,
+ 'currency': tx.currency_id.name,
+ 'acquirer_provider': tx.acquirer_id.provider,
+ })
+
+ tx_to_process = payment_transaction_ids.filtered(lambda x: x.state == 'done' and x.is_processed is False)
+ try:
+ tx_to_process._post_process_after_done()
+ except Exception as e:
+ request.env.cr.rollback()
+ result['success'] = False
+ result['error'] = str(e)
+ _logger.error("Error while processing transaction(s) %s, exception \"%s\"", tx_to_process.ids, str(e))
+
+ return result
+
class WebsitePayment(http.Controller):
@http.route(['/my/payment_method'], type='http', auth="user", website=True)
def payment_method(self, **kwargs):
@@ -78,15 +170,13 @@ class WebsitePayment(http.Controller):
partner_id = user.partner_id.id if not user._is_public() else False
values.update({
- 'return_url': '/website_payment/confirm',
'partner_id': partner_id,
'bootstrap_formatting': True,
'error_msg': kw.get('error_msg')
})
- values['s2s_acquirers'] = [acq for acq in acquirers if acq.payment_flow == 's2s']
- values['form_acquirers'] = [acq for acq in acquirers if acq.payment_flow == 'form']
- values['pms'] = request.env['payment.token'].search([('acquirer_id', 'in', [acq.id for acq in values['s2s_acquirers']])])
+ values['acquirers'] = [acq for acq in acquirers if acq.payment_flow in ['form', 's2s']]
+ values['pms'] = request.env['payment.token'].search([('acquirer_id', 'in', acquirers.filtered(lambda x: x.payment_flow == 's2s').ids)])
return request.render('payment.pay', values)
@@ -97,6 +187,9 @@ class WebsitePayment(http.Controller):
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
order_id = kwargs.get('order_id')
+ reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
+ reference = request.env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)
+
values = {
'acquirer_id': int(acquirer_id),
'reference': reference,
@@ -112,9 +205,11 @@ class WebsitePayment(http.Controller):
reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
values['reference'] = request.env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)
tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
+ tx.return_url = '/website_payment/confirm?tx_id=%d' % tx.id
+
+ PaymentProcessing.add_payment_transaction(tx)
render_values = {
- 'return_url': '/website_payment/confirm?tx_id=%d' % tx.id,
'partner_id': partner_id,
}
@@ -139,23 +234,25 @@ class WebsitePayment(http.Controller):
'partner_id': partner_id,
'payment_token_id': pm_id,
'type': 'form_save' if token.acquirer_id.save_token != 'none' and partner_id else 'form',
+ 'return_url': return_url,
}
if order_id:
values['sale_order_ids'] = [(6, 0, [order_id])]
tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
+ PaymentProcessing.add_payment_transaction(tx)
try:
res = tx.s2s_do_transaction()
+ if tx.state == 'done':
+ tx.return_url = return_url or '/website_payment/confirm?tx_id=%d' % tx.id
+ valid_state = 'authorized' if tx.acquirer_id.capture_manually else 'done'
+ if not res or tx.state != valid_state:
+ tx.return_url = '/website_payment/pay?error_msg=%s' % _('Payment transaction failed.')
+ return request.redirect('/payment/process')
except Exception as e:
- return request.redirect('/website_payment/pay?error_msg=%s' % _('Payment transaction failed.'))
-
- valid_state = 'authorized' if tx.acquirer_id.capture_manually else 'done'
- if not res or tx.state != valid_state:
- return request.redirect('/website_payment/pay?error_msg=%s' % _('Payment transaction failed.'))
-
- return request.redirect(return_url if return_url else '/website_payment/confirm?tx_id=%d' % tx.id)
+ return request.redirect('/payment/process')
@http.route(['/website_payment/confirm'], type='http', auth='public', website=True)
def confirm(self, **kw):
diff --git a/addons/payment/data/payment_cron.xml b/addons/payment/data/payment_cron.xml
new file mode 100644
index 00000000000..c0bbe62b785
--- /dev/null
+++ b/addons/payment/data/payment_cron.xml
@@ -0,0 +1,14 @@
+
+
+
+ Post process payment transactions
+
+ code
+ model._cron_post_process_after_done()
+
+
+ 10
+ minutes
+ -1
+
+
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index b3560e236f4..3da56665b26 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -308,10 +308,7 @@ class PaymentAcquirer(models.Model):
It contains
- * form_acquirers: record set of acquirers based on a local form that
- sends customer to the acquirer website;
- * s2s_acquirers: reset set of acquirers that send customer data to
- acquirer without redirecting to any other website;
+ * acquirers: record set of both form and s2s acquirers;
* pms: record set of stored credit card data (aka payment.token)
connected to a given partner to allow customers to reuse them """
if not company:
@@ -319,14 +316,13 @@ class PaymentAcquirer(models.Model):
if not partner:
partner = self.env.user.partner_id
active_acquirers = self.sudo().search([('website_published', '=', True), ('company_id', '=', company.id)])
- form_acquirers = active_acquirers.filtered(lambda acq: acq.payment_flow == 'form' and acq.view_template_id)
- s2s_acquirers = active_acquirers.filtered(lambda acq: acq.payment_flow == 's2s' and acq.registration_view_template_id)
+ acquirers = active_acquirers.filtered(lambda acq: (acq.payment_flow == 'form' and acq.view_template_id) or
+ (acq.payment_flow == 's2s' and acq.registration_view_template_id))
return {
- 'form_acquirers': form_acquirers,
- 's2s_acquirers': s2s_acquirers,
+ 'acquirers': acquirers,
'pms': self.env['payment.token'].search([
('partner_id', '=', partner.id),
- ('acquirer_id', 'in', s2s_acquirers.ids)]),
+ ('acquirer_id', 'in', acquirers.filtered(lambda acq: acq.payment_flow == 's2s').ids)]),
}
@api.multi
@@ -571,7 +567,8 @@ class PaymentTransaction(models.Model):
('pending', 'Pending'),
('authorized', 'Authorized'),
('done', 'Done'),
- ('cancel', 'Canceled')],
+ ('cancel', 'Canceled'),
+ ('error', 'Error'),],
string='Status', copy=False, default='draft', required=True, readonly=True)
state_message = fields.Text(string='Message', readonly=True,
help='Field used to store error and/or validation messages for information')
@@ -599,6 +596,10 @@ class PaymentTransaction(models.Model):
callback_method = fields.Char('Callback Method', groups="base.group_system")
callback_hash = fields.Char('Callback Hash', groups="base.group_system")
+ # Fields used for user redirection & payment post processing
+ return_url = fields.Char('Return URL after payment')
+ is_processed = fields.Boolean('Has the payment been post processed', default=False)
+
# Fields used for payment.transaction traceability.
payment_token_id = fields.Many2one('payment.token', 'Payment Token', readonly=True,
@@ -730,8 +731,6 @@ class PaymentTransaction(models.Model):
raise ValidationError(_('Only draft/authorized transaction can be posted.'))
self.write({'state': 'done', 'date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
- self._reconcile_after_transaction_done()
- self._log_payment_transaction_received()
@api.multi
def _reconcile_after_transaction_done(self):
@@ -768,6 +767,39 @@ class PaymentTransaction(models.Model):
self.write({'state': 'cancel', 'date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
self._log_payment_transaction_received()
+ @api.multi
+ def _set_transaction_error(self, msg):
+ '''Move the transaction to the error state (Third party returning error e.g. Paypal).'''
+ if any(trans.state != 'draft' for trans in self):
+ raise ValidationError(_('Only draft transaction can be processed.'))
+
+ self.write({
+ 'state': 'error',
+ 'date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
+ 'state_message': msg,
+ })
+
+ @api.multi
+ def _post_process_after_done(self):
+ self._reconcile_after_transaction_done()
+ self._log_payment_transaction_received()
+ self.write({'is_processed': True})
+ return True
+
+ @api.multi
+ def _cron_post_process_after_done(self):
+ if not self:
+ # we retrieve all the payment tx that need to be post processed
+ self = self.search([('state', '=', 'done'),
+ ('is_processed', '=', False)
+ ])
+ for tx in self:
+ try:
+ tx._post_process_after_done()
+ self.env.cr.commit()
+ except Exception as e:
+ _logger.error("Transaction post processing failed, reason \"%s\"", e)
+
@api.model
def _compute_reference_prefix(self, values):
if values and values.get('invoice_ids'):
@@ -919,10 +951,6 @@ class PaymentTransaction(models.Model):
return True
- @api.multi
- def _post_process_after_done(self, **kwargs):
- return True
-
# --------------------------------------------------
# SERVER2SERVER RELATED METHODS
# --------------------------------------------------
diff --git a/addons/payment/static/src/js/payment_processing.js b/addons/payment/static/src/js/payment_processing.js
new file mode 100644
index 00000000000..b40c2074131
--- /dev/null
+++ b/addons/payment/static/src/js/payment_processing.js
@@ -0,0 +1,125 @@
+odoo.define('payment.processing', function (require) {
+ 'use strict';
+
+ var Widget = require('web.Widget');
+ var Ajax = require('web.ajax');
+ var Core = require('web.core');
+ var Qweb = Core.qweb;
+ var _t = Core._t;
+
+ $.blockUI.defaults.css.border = '0';
+ $.blockUI.defaults.css["background-color"] = '';
+ $.blockUI.defaults.overlayCSS["opacity"] = '0.9';
+
+ return Widget.extend({
+ /* Members */
+ _payment_tx_ids: null,
+ _pollCount: 0,
+ /* Events */
+ events: {
+
+ },
+ /* deps */
+ xmlDependencies: ['/payment/static/src/xml/payment_processing.xml'],
+ /* Widget overrides */
+ init: function (parent, payment_tx_ids) {
+ this._super.apply(this, arguments);
+ //
+ this._payment_tx_ids = payment_tx_ids;
+ },
+ start: function() {
+ this.displayLoading();
+ this.poll();
+ return this._super.apply(this, arguments);
+ },
+ /* Methods */
+ startPolling: function () {
+ var timeout = 3000;
+ //
+ if(this._pollCount >= 10 && this._pollCount < 20) {
+ timeout = 10000;
+ }
+ else if(this._pollCount >= 20) {
+ timeout = 30000;
+ }
+ //
+ setTimeout(this.poll.bind(this), timeout);
+ this._pollCount ++;
+ },
+ poll: function () {
+ var self = this;
+ Ajax.jsonRpc('/payment/process/poll', 'call', {}).then(function(data) {
+ if(data.success === true) {
+ self.processPolledData(data.transactions);
+ }
+ else {
+ switch(data.error) {
+ case "no_tx_found":
+ self.displayContent("payment.no_tx_found", {});
+ break;
+ default: // if an exception is raised
+ self.displayContent("payment.exception", {exception_msg: data.error});
+ break;
+ }
+ }
+ self.startPolling();
+ }).always(function(){
+ if ($.blockUI) {
+ $.unblockUI();
+ }
+ }).fail(function(e) {
+ self.displayContent("payment.rpc_error", {});
+ self.startPolling();
+ });
+ },
+ processPolledData: function (transactions) {
+ var render_values = {
+ 'tx_draft': [],
+ 'tx_pending': [],
+ 'tx_authorized': [],
+ 'tx_done': [],
+ 'tx_cancel': [],
+ 'tx_error': [],
+ };
+ // group the transaction according to their state
+ transactions.forEach(tx => {
+ var key = 'tx_' + tx.state;
+ if(key in render_values) {
+ render_values[key].push(tx);
+ }
+ });
+
+ function countTxInState(states) {
+ var nbTx = 0;
+ for (var prop in render_values) {
+ if (states.indexOf(prop) > -1 && render_values.hasOwnProperty(prop)) {
+ nbTx += render_values[prop].length;
+ }
+ }
+ return nbTx;
+ }
+ // if there's only one tx to manage
+ if(countTxInState(['tx_done', 'tx_error']) === 1) {
+ var tx = render_values['tx_done'][0] || render_values['tx_error'][0];
+ if (tx) {
+ window.location = tx.return_url;
+ return;
+ }
+ }
+
+ this.displayContent("payment.display_tx_list", render_values);
+ },
+ displayContent: function (xmlid, render_values) {
+ var html = Qweb.render(xmlid, render_values);
+ this.$el.find('.o_payment_processing_content').html(html);
+ },
+ displayLoading: function () {
+ var msg = _t("We are processing your payments, please wait ...");
+ $.blockUI({
+ 'message': ' ' +
+ ' ' + msg +
+ ' '
+ });
+ },
+ });
+});
diff --git a/addons/payment/static/src/xml/payment_processing.xml b/addons/payment/static/src/xml/payment_processing.xml
new file mode 100644
index 00000000000..386a03bb117
--- /dev/null
+++ b/addons/payment/static/src/xml/payment_processing.xml
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
+
Payments failed
+
+
+
+
+
+
+
+
+ An error occured during the processing of this payment.
+ Reason:
+
+
+
+
+
+
+
+
+
Waiting for payment
+
+
+
+
+
+
+
+
+
+
+
+
+ We are waiting for the payment acquirer to confirm the payment.
+
+
+
+
+
+
+
+
+
Cancelled payments
+
+
+
+
+
+
+
+
+ This transaction has been cancelled.
+ No payment has been processed.
+
+
+
+
+
+
+
+
+
+
+
We are not able to find your payment, but don't worry.
+
You should receive an email confirming your payment in a few minutes.
+
If, the payment hasn't been confirmed you can contact us.
+
+
+
+
+
+
Server error: Unable to contact the Odoo server.
+
Please wait ...
+
+
+
+
+
+
Internal server error
+
+
+
+
+
diff --git a/addons/payment/views/assets.xml b/addons/payment/views/assets.xml
index 06fc316ca39..62fe9d2455f 100644
--- a/addons/payment/views/assets.xml
+++ b/addons/payment/views/assets.xml
@@ -10,6 +10,7 @@
+
diff --git a/addons/payment/views/payment_portal_templates.xml b/addons/payment/views/payment_portal_templates.xml
index 762bc2015aa..0676107c698 100644
--- a/addons/payment/views/payment_portal_templates.xml
+++ b/addons/payment/views/payment_portal_templates.xml
@@ -19,7 +19,7 @@
Manage your Payment Methods
-
+
@@ -58,7 +58,7 @@
-
+
No payment acquirer found.
Please configure a payment acquirer.
@@ -165,7 +165,7 @@
×
-
+
diff --git a/addons/payment/views/payment_templates.xml b/addons/payment/views/payment_templates.xml
index e8fd05685dc..3b1f21d21ab 100644
--- a/addons/payment/views/payment_templates.xml
+++ b/addons/payment/views/payment_templates.xml
@@ -17,12 +17,11 @@
- 'checked_pm_id' the payment token that should be checked (for radio buttons)
- 'mode' can take two values, either 'payment' or 'manage'. 'manage' displays the add a new card and delete buttons. 'payment'
display a form that is used to pay and send the information to the form action url.
- - 's2s_acquirers' the list of the server2server acquirers
- - 'form_acquirers' the list of the payment acquirers to pay with forms
+ - 'acquirers' the list of both server2server and form payment acquirers
- 'verify_validity' if we need to verify if the payment method is valid when adding a new one
- 'prepare_tx_url' the url of the route which will handle the creation of a transaction for a form base payment (handles if the transaction is form or form_save)
-->
-
-
+
-
+
-
+
+ t-att-data-form-payment="true"
+ t-att-data-provider="acq.provider"
+ t-att-class="'d-none' if (acquirers_count==1 and pms_count==0) else ''"
+ name="pm_id" t-attf-value="form_{{acq.id}}"
+ t-att-checked="acquirers_count==1 and pms_count==0 or acquirers[0] == acq"/>
-
+
+ t-att-data-s2s-payment="true"
+ t-att-data-provider="acq.provider"
+ name="pm_id" t-attf-value="new_{{acq.id}}"
+ t-att-class="'d-none' if (acquirers_count==1 and pms_count==0) else ''"
+ t-att-checked="acquirers_count==1 and pms_count==0 or acquirers[0] == acq"/>
@@ -70,7 +69,7 @@
+ t-options='{"widget": "image", "alt-field": "name"}'/>
@@ -81,46 +80,27 @@
-
-
-
-
-
-
-
+
+
+
+
+ Save my payment data
+
-
-
-
-
-
- Save my payment data
-
+
+
+
+
@@ -157,10 +137,37 @@
Pay
-
+
Add new card
+
+
+
+
+
+
+
+
+
diff --git a/addons/payment_adyen/controllers/main.py b/addons/payment_adyen/controllers/main.py
index b6b4331e578..7de5a6dc7ef 100644
--- a/addons/payment_adyen/controllers/main.py
+++ b/addons/payment_adyen/controllers/main.py
@@ -21,11 +21,7 @@ class AdyenController(http.Controller):
_logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug
if post.get('authResult') not in ['CANCELLED']:
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', '{}'))
- return_url = custom.pop('return_url', '/')
- return werkzeug.utils.redirect(return_url)
+ return werkzeug.utils.redirect('/payment/process')
@http.route([
'/payment/adyen/notification',
diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py
index 9614327401a..dd8d96f6e90 100644
--- a/addons/payment_authorize/controllers/main.py
+++ b/addons/payment_authorize/controllers/main.py
@@ -5,7 +5,7 @@ from werkzeug import urls, utils
from odoo import http, _
from odoo.http import request
-from odoo.exceptions import ValidationError
+from odoo.exceptions import ValidationError, UserError
_logger = logging.getLogger(__name__)
@@ -20,16 +20,14 @@ class AuthorizeController(http.Controller):
], type='http', auth='public', csrf=False)
def authorize_form_feedback(self, **post):
_logger.info('Authorize: entering form_feedback with post data %s', pprint.pformat(post))
- return_url = '/'
if post:
request.env['payment.transaction'].sudo().form_feedback(post, 'authorize')
- return_url = post.pop('return_url', '/')
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
# Authorize.Net is expecting a response to the POST sent by their server.
# This response is in the form of a URL that Authorize.Net will pass on to the
# client's browser to redirect them to the desired location need javascript.
return request.render('payment_authorize.payment_authorize_redirect', {
- 'return_url': urls.url_join(base_url, return_url)
+ 'return_url': urls.url_join(base_url, "/payment/process")
})
@http.route(['/payment/authorize/s2s/create_json'], type='json', auth='public')
@@ -38,13 +36,18 @@ class AuthorizeController(http.Controller):
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
+ try:
+ return acquirer.s2s_process(kwargs).id
+ except (ValidationError, UserError) as e:
+ return {
+ 'error': e.name,
+ }
@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 = 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)
@@ -65,6 +68,10 @@ class AuthorizeController(http.Controller):
return {
'error': message
}
+ except UserError as e:
+ return {
+ 'error': e.name,
+ }
if not token:
res = {
@@ -91,4 +98,4 @@ class AuthorizeController(http.Controller):
acquirer_id = int(post.get('acquirer_id'))
acquirer = request.env['payment.acquirer'].browse(acquirer_id)
acquirer.s2s_process(post)
- return utils.redirect(post.get('return_url', '/'))
+ return utils.redirect("/payment/process")
diff --git a/addons/payment_buckaroo/controllers/main.py b/addons/payment_buckaroo/controllers/main.py
index 40e4ae539e4..7fe17c8344f 100644
--- a/addons/payment_buckaroo/controllers/main.py
+++ b/addons/payment_buckaroo/controllers/main.py
@@ -28,4 +28,4 @@ class BuckarooController(http.Controller):
request.env['payment.transaction'].sudo().form_feedback(post, 'buckaroo')
post = {key.upper(): value for key, value in post.items()}
return_url = post.get('ADD_RETURNDATA') or '/'
- return werkzeug.utils.redirect(return_url)
+ return werkzeug.utils.redirect('/payment/process')
diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py
index 409e09b8a9e..6bebff17f34 100644
--- a/addons/payment_ogone/controllers/main.py
+++ b/addons/payment_ogone/controllers/main.py
@@ -7,6 +7,7 @@ from werkzeug.urls import url_unquote_plus
from odoo import http
from odoo.http import request
from odoo.addons.payment.models.payment_acquirer import ValidationError
+from odoo.addons.payment.controllers.portal import PaymentProcessing
_logger = logging.getLogger(__name__)
@@ -27,7 +28,7 @@ class OgoneController(http.Controller):
""" Ogone contacts using GET, at least for accept """
_logger.info('Ogone: entering form_feedback with post data %s', pprint.pformat(post)) # debug
request.env['payment.transaction'].sudo().form_feedback(post, 'ogone')
- return werkzeug.utils.redirect(url_unquote_plus(post.pop('return_url', '/')))
+ return werkzeug.utils.redirect("/payment/process")
@http.route(['/payment/ogone/s2s/create_json'], type='json', auth='public', csrf=False)
def ogone_s2s_create_json(self, **kwargs):
@@ -40,11 +41,18 @@ class OgoneController(http.Controller):
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)
+ token = False
+ error = None
+
+ try:
+ token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs)
+ except Exception as e:
+ error = str(e)
if not token:
res = {
'result': False,
+ 'error': error,
}
return res
@@ -94,7 +102,9 @@ class OgoneController(http.Controller):
tx = token.validate(**params)
if tx and tx.html_3ds:
return tx.html_3ds
- return werkzeug.utils.redirect(post.get('return_url', '/') + (error and '#error=%s' % werkzeug.url_quote(error) or ''))
+ # add the payment transaction into the session to let the page /payment/process to handle it
+ PaymentProcessing.add_payment_transaction(tx)
+ return werkzeug.utils.redirect("/payment/process")
@http.route([
'/payment/ogone/validate/accept',
@@ -104,7 +114,7 @@ class OgoneController(http.Controller):
def ogone_validation_form_feedback(self, **post):
""" Feedback from 3d secure for a bank card validation """
request.env['payment.transaction'].sudo().form_feedback(post, 'ogone')
- return werkzeug.utils.redirect(werkzeug.url_unquote(post.pop('return_url', '/')))
+ return werkzeug.utils.redirect("/payment/process")
@http.route(['/payment/ogone/s2s/feedback'], auth='none', csrf=False)
def feedback(self, **kwargs):
diff --git a/addons/payment_paypal/controllers/main.py b/addons/payment_paypal/controllers/main.py
index e14b7d59ca6..f9166ecf302 100644
--- a/addons/payment_paypal/controllers/main.py
+++ b/addons/payment_paypal/controllers/main.py
@@ -20,14 +20,6 @@ class PaypalController(http.Controller):
_return_url = '/payment/paypal/dpn/'
_cancel_url = '/payment/paypal/cancel/'
- def _get_return_url(self, **post):
- """ Extract the return URL from the data coming from paypal. """
- return_url = post.pop('return_url', '')
- if not return_url:
- custom = json.loads(urls.url_unquote_plus(post.pop('custom', False) or post.pop('cm', False) or '{}'))
- return_url = custom.get('return_url', '/')
- return return_url
-
def _parse_pdt_response(self, response):
""" Parse a text response for a PDT verification.
@@ -63,20 +55,20 @@ class PaypalController(http.Controller):
Once data is validated, process it. """
res = False
- new_post = dict(post, cmd='_notify-validate', charset='UTF-8')
+ post['cmd'] = '_notify-validate'
reference = post.get('item_number')
tx = None
if reference:
tx = request.env['payment.transaction'].search([('reference', '=', reference)])
paypal_urls = request.env['payment.acquirer']._get_paypal_urls(tx and tx.acquirer_id.environment or 'prod')
- pdt_request = bool(new_post.get('amt')) # check for spefific pdt param
+ pdt_request = bool(post.get('amt')) # check for spefific pdt param
if pdt_request:
# this means we are in PDT instead of DPN like before
# fetch the PDT token
- new_post['at'] = tx and tx.acquirer_id.paypal_pdt_token or ''
- new_post['cmd'] = '_notify-synch' # command is different in PDT than IPN/DPN
+ post['at'] = tx and tx.acquirer_id.paypal_pdt_token or ''
+ post['cmd'] = '_notify-synch' # command is different in PDT than IPN/DPN
validate_url = paypal_urls['paypal_form_url']
- urequest = requests.post(validate_url, new_post)
+ urequest = requests.post(validate_url, post)
urequest.raise_for_status()
resp = urequest.text
if pdt_request:
@@ -84,10 +76,14 @@ class PaypalController(http.Controller):
if resp in ['VERIFIED', 'SUCCESS']:
_logger.info('Paypal: validated data')
res = request.env['payment.transaction'].sudo().form_feedback(post, 'paypal')
+ if not res:
+ tx.sudo()._set_transaction_error('Validation error occured. Please contact your administrator.')
elif resp in ['INVALID', 'FAIL']:
_logger.warning('Paypal: answered INVALID/FAIL on data verification')
+ tx.sudo()._set_transaction_error('Invalid response from Paypal. Please contact your administrator.')
else:
_logger.warning('Paypal: unrecognized paypal answer, received %s instead of VERIFIED/SUCCESS or INVALID/FAIL (validation: %s)' % (resp, 'PDT' if pdt_request else 'IPN/DPN'))
+ tx.sudo()._set_transaction_error('Unrecognized error from Paypal. Please contact your administrator.')
return res
@http.route('/payment/paypal/ipn/', type='http', auth='none', methods=['POST'], csrf=False)
@@ -104,13 +100,14 @@ class PaypalController(http.Controller):
def paypal_dpn(self, **post):
""" Paypal DPN """
_logger.info('Beginning Paypal DPN form_feedback with post data %s', pprint.pformat(post)) # debug
- return_url = self._get_return_url(**post)
- self.paypal_validate_data(**post)
- return werkzeug.utils.redirect(return_url)
+ try:
+ res = self.paypal_validate_data(**post)
+ except ValidationError:
+ _logger.exception('Unable to validate the Paypal payment')
+ return werkzeug.utils.redirect('/payment/process')
@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 """
_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)
+ return werkzeug.utils.redirect('/payment/process')
diff --git a/addons/payment_paypal/models/payment.py b/addons/payment_paypal/models/payment.py
index e967456f28c..fb12cabdc50 100644
--- a/addons/payment_paypal/models/payment.py
+++ b/addons/payment_paypal/models/payment.py
@@ -25,7 +25,7 @@ class AcquirerPaypal(models.Model):
'Paypal Merchant ID', groups='base.group_user',
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', groups='base.group_user')
- paypal_pdt_token = fields.Char(string='Paypal PDT Token', required_if_provider='paypal', help='Payment Data Transfer allows you to receive notification of successful payments as they are made.', groups='base.group_user')
+ paypal_pdt_token = fields.Char(string='Paypal PDT Token', help='Payment Data Transfer allows you to receive notification of successful payments as they are made.', groups='base.group_user')
# Server 2 server
paypal_api_enabled = fields.Boolean('Use Rest API', default=False)
paypal_api_username = fields.Char('Rest API Username', groups='base.group_user')
diff --git a/addons/payment_payumoney/controllers/main.py b/addons/payment_payumoney/controllers/main.py
index 78bc73dfe2d..a565afddeee 100644
--- a/addons/payment_payumoney/controllers/main.py
+++ b/addons/payment_payumoney/controllers/main.py
@@ -17,8 +17,6 @@ class PayuMoneyController(http.Controller):
""" PayUmoney."""
_logger.info(
'PayUmoney: entering form_feedback with post data %s', pprint.pformat(post))
- return_url = '/'
if post:
request.env['payment.transaction'].sudo().form_feedback(post, 'payumoney')
- return_url = post.get('udf1')
- return werkzeug.utils.redirect(return_url)
+ return werkzeug.utils.redirect('/payment/process')
diff --git a/addons/payment_sips/controllers/main.py b/addons/payment_sips/controllers/main.py
index 3af8e2c7b3e..de3e90810ee 100644
--- a/addons/payment_sips/controllers/main.py
+++ b/addons/payment_sips/controllers/main.py
@@ -16,16 +16,6 @@ class SipsController(http.Controller):
_notify_url = '/payment/sips/ipn/'
_return_url = '/payment/sips/dpn/'
- def _get_return_url(self, **post):
- """ Extract the return URL from the data coming from sips. """
- return_url = post.pop('return_url', '')
- if not return_url:
- 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):
sips = request.env['payment.acquirer'].search([('provider', '=', 'sips')], limit=1)
security = sips.sudo()._sips_generate_shasign(post)
@@ -47,6 +37,8 @@ class SipsController(http.Controller):
'/payment/sips/dpn'], type='http', auth="none", methods=['POST'], csrf=False)
def sips_dpn(self, **post):
""" Sips DPN """
- return_url = self._get_return_url(**post)
- self.sips_validate_data(**post)
- return werkzeug.utils.redirect(return_url)
+ try:
+ self.sips_validate_data(**post)
+ except:
+ pass
+ return werkzeug.utils.redirect('/payment/process')
diff --git a/addons/payment_stripe/controllers/main.py b/addons/payment_stripe/controllers/main.py
index f446375d429..a92e3cb2752 100644
--- a/addons/payment_stripe/controllers/main.py
+++ b/addons/payment_stripe/controllers/main.py
@@ -5,6 +5,7 @@ import werkzeug
from odoo import http
from odoo.http import request
+from odoo.addons.payment.controllers.portal import PaymentProcessing
_logger = logging.getLogger(__name__)
@@ -93,4 +94,6 @@ class StripeController(http.Controller):
_logger.info('Stripe: entering form_feedback with post data %s', pprint.pformat(response))
if response:
request.env['payment.transaction'].sudo().with_context(lang=None).form_feedback(response, 'stripe')
- return post.pop('return_url', '/')
+ # add the payment transaction into the session to let the page /payment/process to handle it
+ PaymentProcessing.add_payment_transaction(tx)
+ return "/payment/process"
diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py
index ee2b6db26d5..78920e93580 100644
--- a/addons/payment_stripe/models/payment.py
+++ b/addons/payment_stripe/models/payment.py
@@ -54,7 +54,6 @@ class PaymentAcquirerStripe(models.Model):
'phone': tx_values.get('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
diff --git a/addons/payment_transfer/controllers/main.py b/addons/payment_transfer/controllers/main.py
index 6411414c597..29a1d20d8e1 100644
--- a/addons/payment_transfer/controllers/main.py
+++ b/addons/payment_transfer/controllers/main.py
@@ -18,4 +18,4 @@ class OgoneController(http.Controller):
def transfer_form_feedback(self, **post):
_logger.info('Beginning form_feedback with post data %s', pprint.pformat(post)) # debug
request.env['payment.transaction'].sudo().form_feedback(post, 'transfer')
- return werkzeug.utils.redirect(post.pop('return_url', '/'))
+ return werkzeug.utils.redirect('/payment/process')
diff --git a/addons/sale/controllers/portal.py b/addons/sale/controllers/portal.py
index 6b1fd33f87f..1b38e714e40 100644
--- a/addons/sale/controllers/portal.py
+++ b/addons/sale/controllers/portal.py
@@ -175,11 +175,11 @@ class CustomerPortal(CustomerPortal):
])
acquirers = request.env['payment.acquirer'].sudo().search(domain)
- values['form_acquirers'] = [acq for acq in acquirers if acq.payment_flow == 'form' and acq.view_template_id]
- values['s2s_acquirers'] = [acq for acq in acquirers if acq.payment_flow == 's2s' and acq.registration_view_template_id]
+ values['acquirers'] = acquirers.filtered(lambda acq: (acq.payment_flow == 'form' and acq.view_template_id) or
+ (acq.payment_flow == 's2s' and acq.registration_view_template_id))
values['pms'] = request.env['payment.token'].search(
[('partner_id', '=', order_sudo.partner_id.id),
- ('acquirer_id', 'in', [acq.id for acq in values['s2s_acquirers']])])
+ ('acquirer_id', 'in', acquirers.filtered(lambda acq: acq.payment_flow == 's2s').ids)])
if order_sudo.state in ('draft', 'sent', 'cancel'):
history = request.session.get('my_quotations_history', [])
diff --git a/addons/sale/models/payment.py b/addons/sale/models/payment.py
index 950aa41c1f9..d6af752c531 100644
--- a/addons/sale/models/payment.py
+++ b/addons/sale/models/payment.py
@@ -108,7 +108,7 @@ class PaymentTransaction(models.Model):
@api.multi
def _invoice_sale_orders(self):
- if self.env['ir.config_parameter'].sudo().get_param('website_sale.automatic_invoice'):
+ if self.env['ir.config_parameter'].sudo().get_param('sale.automatic_invoice'):
ctx_company = {'company_id': self.acquirer_id.company_id.id,
'force_company': self.acquirer_id.company_id.id}
for trans in self.filtered(lambda t: t.sale_order_ids):
@@ -146,9 +146,8 @@ class PaymentTransaction(models.Model):
# Tools for payment
# --------------------------------------------------
- def render_sale_button(self, order, return_url, submit_txt=None, render_values=None):
+ def render_sale_button(self, order, submit_txt=None, render_values=None):
values = {
- 'return_url': return_url,
'partner_id': order.partner_shipping_id.id or order.partner_invoice_id.id,
'billing_partner_id': order.partner_invoice_id.id,
}
diff --git a/addons/sale/views/sale_portal_templates.xml b/addons/sale/views/sale_portal_templates.xml
index bf382905b3f..5293c7c6f79 100644
--- a/addons/sale/views/sale_portal_templates.xml
+++ b/addons/sale/views/sale_portal_templates.xml
@@ -243,7 +243,7 @@
With payment terms:
-
+
Pay with
diff --git a/addons/website_event_sale/static/src/js/website.tour.event_sale.js b/addons/website_event_sale/static/src/js/website.tour.event_sale.js
index 3aec6f28638..c6dafbb019e 100644
--- a/addons/website_event_sale/static/src/js/website.tour.event_sale.js
+++ b/addons/website_event_sale/static/src/js/website.tour.event_sale.js
@@ -85,9 +85,14 @@ tour.register('event_buy_tickets', {
extra_trigger: '#payment_method label:contains("Wire Transfer") input:checked,#payment_method:not(:has("input:radio:visible"))',
trigger: 'button[id="o_payment_form_pay"]:visible',
},
+ {
+ content: "Payment processing",
+ trigger: 'a[class="list-group-item"]',
+ timeout: 5000,
+ },
{
content: "Last step",
- trigger: '.oe_website_sale:contains("Thank you for your order")',
+ trigger: '.oe_website_sale:contains("Pending... The order will be validated after the payment.")',
timeout: 30000,
}
]
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index ef709f02a5d..10027f17655 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -8,6 +8,7 @@ from odoo import fields, http, tools, _
from odoo.http import request
from odoo.addons.base.models.ir_qweb_fields import nl2br
from odoo.addons.http_routing.models.ir_http import slug
+from odoo.addons.payment.controllers.portal import PaymentProcessing
from odoo.addons.website.controllers.main import QueryURL
from odoo.exceptions import ValidationError
from odoo.addons.website.controllers.main import Website
@@ -798,8 +799,8 @@ class WebsiteSale(http.Controller):
acquirers = request.env['payment.acquirer'].search(domain)
values['access_token'] = order.access_token
- values['form_acquirers'] = [acq for acq in acquirers if acq.payment_flow == 'form' and acq.view_template_id]
- values['s2s_acquirers'] = [acq for acq in acquirers if acq.payment_flow == 's2s' and acq.registration_view_template_id]
+ values['acquirers'] = [acq for acq in acquirers if (acq.payment_flow == 'form' and acq.view_template_id) or
+ (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', acquirers.ids)])
@@ -870,7 +871,8 @@ class WebsiteSale(http.Controller):
assert order.partner_id.id != request.website.partner_id.id
# Create transaction
- vals = {'acquirer_id': acquirer_id}
+ vals = {'acquirer_id': acquirer_id,
+ 'return_url': '/shop/payment/validate'}
if save_token:
vals['type'] = 'form_save'
@@ -879,7 +881,15 @@ class WebsiteSale(http.Controller):
transaction = order._create_payment_transaction(vals)
- return transaction.render_sale_button(order, '/shop/payment/validate')
+ # store the new transaction into the transaction list and if there's an old one, we remove it
+ # until the day the ecommerce supports multiple orders at the same time
+ last_tx_id = request.session.get('__website_sale_last_tx_id')
+ last_tx = request.env['payment.transaction'].browse(last_tx_id).sudo().exists()
+ if last_tx:
+ PaymentProcessing.remove_payment_transaction(last_tx)
+ PaymentProcessing.add_payment_transaction(transaction)
+ request.session['__website_sale_last_tx_id'] = transaction.id
+ return transaction.render_sale_button(order)
@http.route('/shop/payment/token', type='http', auth='public', website=True)
def payment_token(self, pm_id=None, **kwargs):
@@ -904,10 +914,11 @@ class WebsiteSale(http.Controller):
return request.redirect('/shop/?error=token_not_found')
# Create transaction
- vals = {'payment_token_id': pm_id}
+ vals = {'payment_token_id': pm_id, 'return_url': '/shop/payment/validate'}
- order._create_payment_transaction(vals)
- return request.redirect('/shop/payment/validate')
+ tx = order._create_payment_transaction(vals)
+ PaymentProcessing.add_payment_transaction(tx)
+ return request.redirect('/payment/process')
@http.route('/shop/payment/get_status/', type='json', auth="public", website=True)
def payment_get_status(self, sale_order_id, **post):
@@ -950,6 +961,7 @@ class WebsiteSale(http.Controller):
if tx and tx.state == 'draft':
return request.redirect('/shop')
+ PaymentProcessing.remove_payment_transaction(tx)
return request.redirect('/shop/confirmation')
diff --git a/addons/website_sale/static/src/js/website_sale_tour_buy.js b/addons/website_sale/static/src/js/website_sale_tour_buy.js
index 0872f5ae347..c2ffef9b81e 100644
--- a/addons/website_sale/static/src/js/website_sale_tour_buy.js
+++ b/addons/website_sale/static/src/js/website_sale_tour_buy.js
@@ -74,9 +74,14 @@ tour.register('shop_buy_product', {
extra_trigger: '#payment_method label:contains("Wire Transfer") input:checked,#payment_method:not(:has("input:radio:visible"))',
trigger: 'button[id="o_payment_form_pay"]:visible:not(:disabled)',
},
+ {
+ content: "Payment processing",
+ trigger: 'a[class="list-group-item"]',
+ timeout: 5000,
+ },
{
content: "finish",
- trigger: '.oe_website_sale:contains("Thank you for your order")',
+ trigger: '.oe_website_sale:contains("Pending... The order will be validated after the payment.")',
timeout: 30000,
}
]
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 15e4800e028..eab9ebc7618 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -1336,8 +1336,8 @@
-
-
Pay with
+
+
Pay with
Pay Now
@@ -1356,7 +1356,7 @@
-