diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 80c84094c30..623676c7a9c 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -786,7 +786,7 @@ class PaymentToken(models.Model): return False reference = "VALIDATION-%s-%s" % (self.id, datetime.datetime.now().strftime('%y%m%d_%H%M%S')) - tx = self.env['payment.transaction'].create({ + tx = self.env['payment.transaction'].sudo().create({ 'amount': amount, 'acquirer_id': self.acquirer_id.id, 'type': 'validation', @@ -816,6 +816,17 @@ class PaymentToken(models.Model): token.short_name = token.name.replace('XXXXXXXXXXXX', '***') @api.multi - def _get_linked_records(self): - """ Returns the list of records currently using the records in self as payment token. """ - return {} \ No newline at end of file + def get_linked_records(self): + """ This method returns a dict containing all the records linked to the payment.token (e.g Subscriptions), + the key is the id of the payment.token and the value is an array that must follow the scheme below. + + { + token_id: [ + 'description': The model description (e.g 'Sale Subscription'), + 'id': The id of the record, + 'name': The name of the record, + 'url': The url to access to this record. + ] + } + """ + return {r.id:[] for r in self} diff --git a/addons/payment/static/src/js/payment_form.js b/addons/payment/static/src/js/payment_form.js new file mode 100644 index 00000000000..ae2d44c1cb7 --- /dev/null +++ b/addons/payment/static/src/js/payment_form.js @@ -0,0 +1,261 @@ +odoo.define('payment.payment_form', function (require){ + "use strict"; + + var ajax = require('web.ajax'); + var core = require('web.core'); + var Dialog = require("web.Dialog"); + var Widget = require("web.Widget"); + var rpc = require("web.rpc"); + var _t = core._t; + + var PaymentForm = Widget.extend({ + + events: { + 'click #o_payment_form_pay': 'payEvent', + 'click #o_payment_form_add_pm': 'addPmEvent', + 'click button[name="delete_pm"]': 'deletePmEvent', + 'click input[type="radio"]': 'radioClickEvent' + }, + + payEvent: function(ev) { + ev.stopPropagation(); + ev.preventDefault(); + var form = this.el; + var checked_radio = this.$('input[type="radio"]:checked'); + var self = this; + var button = ev.target; + + // first we check that the user has selected a payment method + if(checked_radio.length == 1) { + checked_radio = checked_radio[0]; + // if the user is adding a new payment + if(this.isNewPaymentRadio(checked_radio)) { + var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); + var acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id); + // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array + var form_data = this.getFormData($('input', acquirer_form)); + var ds = $('input[name="data_set"]', acquirer_form)[0]; + $(button).attr('disabled', true); + $(button).prepend(' '); + + var verify_validity = this.$el.find('input[name="verify_validity"]'); + + if(verify_validity.length>0) + form_data.verify_validity = verify_validity[0].value === "1"; + + // do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route' + ajax.jsonRpc(ds.dataset.createRoute, 'call', form_data).then(function(data) { + // if the server has returned true + if(data.result) { + // and it need a 3DS authentication + if(data['3d_secure'] !== false) { + // then we display the 3DS page to the user + $("body").html(data['3d_secure']); + } + else { + checked_radio.value = data.id; // set the radio value to the new card id + form.submit(); + } + } + // if the server has returned false, we display an error + else { + self.error(_t('Error'), + _t("

We are not able to add your payment method at the moment.

")); + } + // here we remove the 'processing' icon from the 'add a new payment' button + $(button).attr('disabled', false); + $(button).find('span.o_loader').remove(); + }).fail(function(message, data) { + // if the rpc fails, pretty obvious + $(button).attr('disabled', false); + $(button).find('span.o_loader').remove(); + + self.error(_t('Error'), + _t("

We are not able to add your payment method at the moment.

") + (core.debug ? data.data.message : '')); + }); + } + // if the user is using an old payment + else { + // then we just submit the form + form.submit(); + } + } + else { + this.error(_t('Error'), + _t('

Please select a payment method

')); + } + }, + // event handler when clicking on the button to add a new payment method + addPmEvent: function(ev) { + ev.stopPropagation(); + ev.preventDefault(); + var form = this.el; + var checked_radio = this.$('input[type="radio"]:checked'); + var self = this; + var button = ev.target; + + // we check if the user has selected a 'add a new payment' option + if(checked_radio.length == 1 && this.isNewPaymentRadio(checked_radio[0])) { + // then we add a 'processing' icon into the 'add a new payment' button + $(button).attr('disabled', true); + $(button).prepend(' '); + // we retrieve which acquirer is used + checked_radio = checked_radio[0]; + var acquirer_id = this.getAcquirerIdFromRadio(checked_radio); + var acquirer_form = this.$('#o_payment_add_token_acq_' + acquirer_id); + // we retrieve all the input inside the acquirer form and 'serialize' them to an indexed array + var form_data = this.getFormData($('input', acquirer_form)); + var ds = $('input[name="data_set"]', acquirer_form)[0]; + + // we force the check when adding a card trough here + form_data.verify_validity = true; + + // do the call to the route stored in the 'data_set' input of the acquirer form, the data must be called 'create-route' + ajax.jsonRpc(ds.dataset.createRoute, 'call', form_data).then(function(data) { + // if the server has returned true + if(data.result) { + // and it need a 3DS authentication + if(data['3d_secure'] !== false) { + // then we display the 3DS page to the user + $("body").html(data['3d_secure']); + } + // if it doesn't require 3DS + else { + // we just go to the return_url or reload the page + if(form_data.return_url) { + window.location = form_data.return_url; + } + else { + window.location.reload(); + } + } + } + // if the server has returned false, we display an error + else { + self.error(_t('Error'), + _t("

We are not able to add your payment method at the moment.

")); + } + // here we remove the 'processing' icon from the 'add a new payment' button + $(button).attr('disabled', false); + $(button).find('span.o_loader').remove(); + }).fail(function(message, data) { + // if the rpc fails, pretty obvious + $(button).attr('disabled', false); + $(button).find('span.o_loader').remove(); + + self.error(_t('Error'), + _t("

We are not able to add your payment method at the moment.

") + (core.debug ? data.data.message : '')); + }); + } + else { + this.error(_t('Error'), + _t('

Please select the option to add a new payment method

')); + } + }, + // event handler when clicking on a button to delete a payment method + deletePmEvent: function (ev) { + ev.stopPropagation(); + ev.preventDefault(); + var self = this; + var pm_id = parseInt(ev.target.value); + + var Delete = function() { + rpc.query({ + model: 'payment.token', + method: 'unlink', + args: [pm_id], + }) + .then(function(result){ + if(result === true) { + ev.target.closest('div').remove(); + } + },function(type,err){ + self.error(_t('Error'), + _t("

We are not able to delete your payment method at the moment.

")); + }); + }; + + rpc.query({ + model: 'payment.token', + method: 'get_linked_records', + args: [pm_id], + }).then(function(result){ + if(result[pm_id].length > 0) { + // if there's records linked to this payment method + var content = ''; + result[pm_id].forEach(function(sub) { + content += '

' + _.str.escapeHTML(sub.name) + '

'; + }); + + content = $('

').html(_t('

This card is currently linked to the following records:

') + content); + // Then we display the list of the records and ask the user if he really want to remove the ppayment method. + new Dialog(self, { + title: _t('Warning!'), + size: 'medium', + $content: content, + buttons: [ + {text: _t('Confirm Deletion'), classes: 'btn-primary', close: true, click: Delete}, + {text: _t('Cancel'), close: true}]}).open(); + } + else { + // if there's no records linked to this payment method, then we delete it + Delete(); + } + }, function(type, err){ + self.error(_t('Error'), + _t("

We are not able to delete your payment method at the moment.

") + (core.debug ? err.data.message : '')); + }); + }, + // event handler when clicking on a radio button + radioClickEvent: function(ev) { + this.updateNewPaymentDisplayStatus(); + }, + updateNewPaymentDisplayStatus: function() + { + var checked_radio = this.$('input[type="radio"]:checked'); + // we hide all the acquirers form + this.$('[id*="o_payment_add_token_acq_"]').addClass('hidden'); + // if we clicked on an add new payment radio + if(checked_radio.length == 1 && this.isNewPaymentRadio(checked_radio[0])) + { + // then we retrieve the acquirer name + var acquirer_id = this.getAcquirerIdFromRadio(checked_radio[0]); + // and display its form + this.$('#o_payment_add_token_acq_' + acquirer_id).removeClass('hidden'); + } + }, + isNewPaymentRadio: function(element) { + return $(element).data('acquirer-id') !== undefined; + }, + getAcquirerIdFromRadio: function(element) { + return $(element).data('acquirer-id'); + }, + error: function(title, message, url) { + return new Dialog(null, { + title: title || "", + $content: $(core.qweb.render('website.error_dialog', { + message: message || "", + })), + }).open(); + }, + getFormData: function($form){ + var unindexed_array = $form.serializeArray(); + var indexed_array = {}; + + $.map(unindexed_array, function(n, i){ + indexed_array[n.name] = n.value; + }); + return indexed_array; + }, + start: function() { + this.updateNewPaymentDisplayStatus(); + }, + }); + + + + $(document).ready(function (){ + var form = new PaymentForm(); + form.attachTo($('.o_payment_form')); + }); +}); diff --git a/addons/payment/static/src/less/payment_form.less b/addons/payment/static/src/less/payment_form.less new file mode 100644 index 00000000000..ce6b2aeab6e --- /dev/null +++ b/addons/payment/static/src/less/payment_form.less @@ -0,0 +1,32 @@ +.o_payment_form { + label > input[type="radio"]{ + vertical-align: bottom; + margin-right: 5px; + } + + label { + font-size: 14px; + font-weight: normal !important; + font-family: Helvetica Neue, sans-serif; + line-height: 1.3em; + color: #4d4d4d; + } + + .panel-body:first-child { + border-top: 0px; + } + + .panel { + border-radius: 10px; + } + + .panel-footer:last-child { + border-bottom-right-radius: 10px !important; + border-bottom-left-radius: 10px !important; + } + + .panel-body { + border-top: 1px solid #ddd; + padding: 1.14em !important; + } +} \ No newline at end of file diff --git a/addons/payment/views/payment_assets.xml b/addons/payment/views/payment_assets.xml index 47d4717ef97..76f32ae1ed1 100644 --- a/addons/payment/views/payment_assets.xml +++ b/addons/payment/views/payment_assets.xml @@ -3,6 +3,8 @@ \ No newline at end of file diff --git a/addons/payment/views/payment_templates.xml b/addons/payment/views/payment_templates.xml index abf18e0cf25..ae9a1501632 100644 --- a/addons/payment/views/payment_templates.xml +++ b/addons/payment/views/payment_templates.xml @@ -9,59 +9,71 @@ diff --git a/addons/payment_authorize/controllers/main.py b/addons/payment_authorize/controllers/main.py index b42efe817bb..6b0aae9bf6a 100644 --- a/addons/payment_authorize/controllers/main.py +++ b/addons/payment_authorize/controllers/main.py @@ -35,7 +35,31 @@ 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) - return acquirer.s2s_process(kwargs) + 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) + + if not token: + res = { + 'result': False, + } + return res + + res = { + 'result': True, + 'id': token.id, + 'short_name': token.short_name, + '3d_secure': False, + 'verified': False, + } + + if verify_validity != False: + token.validate() + res['verified'] = token.verified + + return res @http.route(['/payment/authorize/s2s/create'], type='http', auth='public') def authorize_s2s_create(self, **post): diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py index d8191dfc34c..fcfab1e3ac2 100644 --- a/addons/payment_authorize/models/authorize_request.py +++ b/addons/payment_authorize/models/authorize_request.py @@ -163,6 +163,33 @@ class AuthorizeAPI(): res['name'] = response_profile.find('paymentProfile/payment/creditCard/cardNumber').text return res + def credit(self, token, amount, transaction_id): + """ Refund a payment for the given amount. + + :param record token: the payment.token record that must be refunded. + :param str amount: transaction amount + :param str transaction_id: the reference of the transacation that is going to be refunded. + + :return: a dict containing the response code, transaction id and transaction type + :rtype: dict + """ + root = self._base_tree('createTransactionRequest') + tx = etree.SubElement(root, "transactionRequest") + etree.SubElement(tx, "transactionType").text = "refundTransaction" + etree.SubElement(tx, "amount").text = str(amount) + payment = etree.SubElement(tx, "payment") + credit_card = etree.SubElement(payment, "creditCard") + idx = token.name.find(' - ') + etree.SubElement(credit_card, "cardNumber").text = token.name[idx-4:idx] # shitty hack, but that's the only way to get the 4 last digits + etree.SubElement(credit_card, "expirationDate").text = "XXXX" + etree.SubElement(tx, "refTransId").text = transaction_id + response = self._authorize_request(root) + res = dict() + res['x_response_code'] = response.find('transactionResponse/responseCode').text + res['x_trans_id'] = transaction_id + res['x_type'] = 'refund' + return res + # Transaction management def auth_and_capture(self, token, amount, reference): """Authorize and capture a payment for the given amount. diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index 184f07cdc58..bfc5bfd569a 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -116,7 +116,7 @@ class PaymentAcquirerAuthorize(models.Model): 'partner_id': int(data.get('partner_id')) } PaymentMethod = self.env['payment.token'].sudo().create(values) - return PaymentMethod.id + return PaymentMethod @api.multi def authorize_s2s_form_validate(self, data): @@ -191,7 +191,7 @@ class TxAuthorize(models.Model): @api.multi def _authorize_form_validate(self, data): - if self.state == 'done': + if self.state in ['done', 'refunded']: _logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % self.reference) return True status_code = int(data.get('x_response_code', '0')) @@ -219,6 +219,9 @@ class TxAuthorize(models.Model): 'partner_id': self.partner_id.id, }) self.payment_token_id = token_id + + if self.payment_token_id: + self.payment_token_id.verified = True return True elif status_code == self._authorize_pending_tx_status: self.write({ @@ -252,6 +255,17 @@ class TxAuthorize(models.Model): res = transaction.authorize(self.payment_token_id, self.amount, self.reference) return self._authorize_s2s_validate_tree(res) + @api.multi + def authorize_s2s_do_refund(self): + self.ensure_one() + transaction = AuthorizeAPI(self.acquirer_id) + self.state = 'refunding' + if self.type == 'validation': + res = transaction.void(self.acquirer_reference) + else: + res = transaction.credit(self.payment_token_id, self.amount, self.acquirer_reference) + return self._authorize_s2s_validate_tree(res) + @api.multi def authorize_s2s_capture_transaction(self): self.ensure_one() @@ -272,8 +286,7 @@ class TxAuthorize(models.Model): @api.multi def _authorize_s2s_validate(self, tree): - self.ensure_one() - if self.state == 'done': + if self.state in ['done', 'refunded']: _logger.warning('Authorize: trying to validate an already validated tx (ref %s)' % self.reference) return True status_code = int(tree.get('x_response_code', '0')) @@ -287,6 +300,10 @@ class TxAuthorize(models.Model): }) if init_state != 'authorized': self.execute_callback() + + if self.payment_token_id: + self.payment_token_id.verified = True + if tree.get('x_type').lower() == 'auth_only': self.write({ 'state': 'authorized', @@ -294,13 +311,19 @@ class TxAuthorize(models.Model): }) self.execute_callback() if tree.get('x_type').lower() == 'void': - self.write({ - 'state': 'cancel', - }) + if self.type == 'validation' and self.state == 'refunding': + self.write({ + 'state': 'refunded', + }) + else: + self.write({ + 'state': 'cancel', + }) return True elif status_code == self._authorize_pending_tx_status: + new_state = 'refunding' if self.state == 'refunding' else 'pending' self.write({ - 'state': 'pending', + 'state': new_state, 'acquirer_reference': tree.get('x_trans_id'), }) return True diff --git a/addons/payment_authorize/views/payment_authorize_templates.xml b/addons/payment_authorize/views/payment_authorize_templates.xml index abbbc80b93a..c4b91605a00 100644 --- a/addons/payment_authorize/views/payment_authorize_templates.xml +++ b/addons/payment_authorize/views/payment_authorize_templates.xml @@ -54,38 +54,28 @@ +
+ +
+
+ +
+
+ +
+
+ + + + + diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py index 503f1de1419..9c81761a70e 100644 --- a/addons/payment_ogone/controllers/main.py +++ b/addons/payment_ogone/controllers/main.py @@ -33,6 +33,40 @@ class OgoneController(http.Controller): 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): + token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs) + + if not token: + res = { + 'result': False, + } + return res + + res = { + 'result': True, + 'id': token.id, + 'short_name': token.short_name, + '3d_secure': False, + 'verified': False, + } + + if verify_validity != False: + baseurl = request.env['ir.config_parameter'].sudo().get_param('web.base.url') + params = { + 'accept_url': baseurl + '/payment/ogone/validate/accept', + 'decline_url': baseurl + '/payment/ogone/validate/decline', + 'exception_url': baseurl + '/payment/ogone/validate/exception', + 'return_url': kwargs.get('return_url', baseurl) + } + tx = token.validate(**params) + res['verified'] = token.verified + + if tx and tx.html_3ds: + res['3d_secure'] = tx.html_3ds + + return res + @http.route(['/payment/ogone/s2s/create'], type='http', auth='public', methods=["POST"], csrf=False) def ogone_s2s_create(self, **post): error = '' diff --git a/addons/payment_ogone/models/payment.py b/addons/payment_ogone/models/payment.py index 0702d38c8bb..9ac90f9478f 100644 --- a/addons/payment_ogone/models/payment.py +++ b/addons/payment_ogone/models/payment.py @@ -302,10 +302,11 @@ class PaymentTxOgone(models.Model): }) vals.update(payment_token_id=pm.id) self.write(vals) - self.payment_token_id.verified = True + if self.payment_token_id: + self.payment_token_id.verified = True self.execute_callback() # if this transaction is a validation one, then we refund the money we just withdrawn - if self.type == 'verification': + if self.type == 'validation': self.s2s_do_refund() return True @@ -454,7 +455,8 @@ class PaymentTxOgone(models.Model): 'name': tree.get('CARDNO'), }) self.write({'payment_token_id': pm.id}) - self.payment_token_id.verified = True + if self.payment_token_id: + self.payment_token_id.verified = True self.execute_callback() return True elif status in self._ogone_cancel_tx_status: @@ -463,7 +465,7 @@ class PaymentTxOgone(models.Model): 'acquirer_reference': tree.get('PAYID'), }) elif status in self._ogone_pending_tx_status: - new_state = 'refunding' if self.type == 'validation' else 'pending' + new_state = 'refunding' if self.state == 'refunding' else 'pending' self.write({ 'state': new_state, 'acquirer_reference': tree.get('PAYID'), diff --git a/addons/payment_ogone/views/payment_ogone_templates.xml b/addons/payment_ogone/views/payment_ogone_templates.xml index 63b4af07142..c70d461d0a7 100644 --- a/addons/payment_ogone/views/payment_ogone_templates.xml +++ b/addons/payment_ogone/views/payment_ogone_templates.xml @@ -54,40 +54,27 @@ - diff --git a/addons/payment_stripe/controllers/main.py b/addons/payment_stripe/controllers/main.py index b4627157a9e..55cad84fd9d 100644 --- a/addons/payment_stripe/controllers/main.py +++ b/addons/payment_stripe/controllers/main.py @@ -15,7 +15,7 @@ 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) - return acquirer.s2s_process(kwargs) + return acquirer.s2s_process(kwargs).id @http.route(['/payment/stripe/s2s/create'], type='http', auth='public') def stripe_s2s_create(self, **post): @@ -24,6 +24,30 @@ class StripeController(http.Controller): acquirer.s2s_process(post) return werkzeug.utils.redirect(post.get('return_url', '/')) + @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): + token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs) + + if not token: + res = { + 'result': False, + } + return res + + res = { + 'result': True, + 'id': token.id, + 'short_name': token.short_name, + '3d_secure': False, + 'verified': False, + } + + if verify_validity != False: + token.validate() + res['verified'] = token.verified + + return res + @http.route(['/payment/stripe/create_charge'], type='json', auth='public') def stripe_create_charge(self, **post): """ Create a payment transaction diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py index e43e0cbd71f..1bb4ea9aad8 100644 --- a/addons/payment_stripe/models/payment.py +++ b/addons/payment_stripe/models/payment.py @@ -71,7 +71,7 @@ class PaymentAcquirerStripe(models.Model): 'acquirer_id': int(data['acquirer_id']), 'partner_id': int(data['partner_id']) }) - return payment_token.id + return payment_token @api.multi def stripe_s2s_form_validate(self, data): @@ -83,6 +83,21 @@ class PaymentAcquirerStripe(models.Model): return False return True + def _get_feature_support(self): + """Get advanced feature support by provider. + + Each provider should add its technical in the corresponding + key for the following features: + * fees: support payment fees computations + * authorize: support authorizing payment (separates + authorization and capture) + * tokenize: support saving payment data in a payment.tokenize + object + """ + res = super(PaymentAcquirerStripe, self)._get_feature_support() + res['tokenize'].append('stripe') + return res + class PaymentTransactionStripe(models.Model): _inherit = 'payment.transaction' @@ -112,6 +127,29 @@ class PaymentTransactionStripe(models.Model): result = self._create_stripe_charge(acquirer_ref=self.payment_token_id.acquirer_ref) return self._stripe_s2s_validate_tree(result) + + def _create_stripe_refund(self): + api_url_refund = 'https://%s/refunds' % (self.acquirer_id._get_stripe_api_url()) + + refund_params = { + 'charge': self.acquirer_reference, + 'amount': int(self.amount*100), # by default, stripe refund the full amount (we don't really need to specify the value) + 'metadata[reference]': self.reference, + } + + r = requests.post(api_url_refund, + auth=(self.acquirer_id.stripe_secret_key, ''), + params=refund_params, + headers=STRIPE_HEADERS) + return r.json() + + @api.multi + def stripe_s2s_do_refund(self, **kwargs): + self.ensure_one() + self.state = 'refunding' + result = self._create_stripe_refund() + return self._stripe_s2s_validate_tree(result) + @api.model def _stripe_form_get_tx_from_data(self, data): """ Given a data dict coming from stripe, verify it and find the related @@ -135,18 +173,21 @@ class PaymentTransactionStripe(models.Model): @api.multi def _stripe_s2s_validate_tree(self, tree): self.ensure_one() - if self.state not in ('draft', 'pending'): + if self.state not in ('draft', 'pending', 'refunding'): _logger.info('Stripe: trying to validate an already validated tx (ref %s)', self.reference) return True status = tree.get('status') if status == 'succeeded': + new_state = 'refunded' if self.state == 'refunding' else 'done' self.write({ - 'state': 'done', + 'state': new_state, 'date_validate': fields.datetime.now(), 'acquirer_reference': tree.get('id'), }) self.execute_callback() + if self.payment_token_id: + self.payment_token_id.verified = True return True else: error = tree['error']['message'] diff --git a/addons/payment_stripe/views/payment_stripe_templates.xml b/addons/payment_stripe/views/payment_stripe_templates.xml index 9e8e28c4d07..8f7f8edd0eb 100644 --- a/addons/payment_stripe/views/payment_stripe_templates.xml +++ b/addons/payment_stripe/views/payment_stripe_templates.xml @@ -32,38 +32,29 @@