From 595c0ad8ee48b092c332b95d9a529957013d1307 Mon Sep 17 00:00:00 2001 From: Nikunj Ladava Date: Wed, 29 May 2019 11:00:39 +0000 Subject: [PATCH] [IMP] payment_stripe: integrate stripe with new checkout api There is two payment flow in stripe: 1) Redirection Flow (Form Based): in this flow, the user will redirect to stripe website for payment - create a session for a user and redirect the user to stripe with that session - after successful payment, the user will redirect back to odoo - get all the details of charge by using payment intent API - if user have tick save data then the token will be created 2) S2S Flow: after clicking on pay now button, one popup display with card element element is provided by a stripe with all validation facilities After submitting details, payment flow is - create payment method with card element on stripe - create a customer with partner email on stripe - attach customer to the payment method on stripe - create a token with customer and payment method in odoo - create payment intent in odoo - make a request to stripe - after successful request, payment will be charged for that card task- 1986267 closes: https://github.com/odoo/odoo/pull/33978 --- addons/payment_stripe/controllers/main.py | 67 +---- addons/payment_stripe/models/payment.py | 272 ++++++++---------- .../static/src/js/payment_form.js | 154 ++++++++++ addons/payment_stripe/static/src/js/stripe.js | 194 +++++-------- .../static/src/xml/stripe_templates.xml | 37 ++- .../views/payment_stripe_templates.xml | 53 +--- 6 files changed, 383 insertions(+), 394 deletions(-) create mode 100644 addons/payment_stripe/static/src/js/payment_form.js diff --git a/addons/payment_stripe/controllers/main.py b/addons/payment_stripe/controllers/main.py index f021131fd8c..be915f49080 100644 --- a/addons/payment_stripe/controllers/main.py +++ b/addons/payment_stripe/controllers/main.py @@ -1,41 +1,21 @@ # -*- coding: utf-8 -*- import logging -import pprint import werkzeug from odoo import http from odoo.http import request -from odoo.addons.payment.controllers.portal import PaymentProcessing _logger = logging.getLogger(__name__) class StripeController(http.Controller): + _success_url = '/payment/stripe/success' + _cancel_url = '/payment/stripe/cancel' - @http.route(['/payment/stripe/s2s/create_json'], type='json', auth='public') - 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') - def stripe_s2s_create(self, **post): - acquirer_id = int(post.get('acquirer_id')) - acquirer = request.env['payment.acquirer'].browse(acquirer_id) - error = None - try: - acquirer.s2s_process(post) - except Exception as e: - error = str(e) - - return_url = post.get('return_url', '/') - if error: - separator = '?' if werkzeug.urls.url_parse(return_url).query == '' else '&' - return_url += '{}{}'.format(separator, werkzeug.urls.url_encode({'error': error})) - - return werkzeug.utils.redirect(return_url) + @http.route(['/payment/stripe/success', '/payment/stripe/cancel'], type='http', auth='public') + def stripe_success(self, **kwargs): + request.env['payment.transaction'].sudo().form_feedback(kwargs, 'stripe') + return werkzeug.utils.redirect('/payment/process') @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): @@ -62,38 +42,3 @@ class StripeController(http.Controller): 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 - - Expects the result from the user input from checkout.js popup""" - TX = request.env['payment.transaction'] - tx = None - if post.get('tx_ref'): - tx = TX.sudo().search([('reference', '=', post['tx_ref'])]) - if not tx: - tx_id = (post.get('tx_id') or request.session.get('sale_transaction_id') or - request.session.get('website_payment_tx_id')) - tx = TX.sudo().browse(int(tx_id)) - if not tx: - raise werkzeug.exceptions.NotFound() - - stripe_token = post['token'] - response = None - if tx.type == 'form_save' and tx.partner_id: - payment_token_id = request.env['payment.token'].sudo().create({ - 'acquirer_id': tx.acquirer_id.id, - 'partner_id': tx.partner_id.id, - 'stripe_token': stripe_token - }) - tx.payment_token_id = payment_token_id - response = tx._create_stripe_charge(acquirer_ref=payment_token_id.acquirer_ref, email=stripe_token['email']) - else: - response = tx._create_stripe_charge(tokenid=stripe_token['id'], email=stripe_token['email']) - _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') - # 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 bfe9a0c6c93..efde9123b8c 100644 --- a/addons/payment_stripe/models/payment.py +++ b/addons/payment_stripe/models/payment.py @@ -3,19 +3,15 @@ import logging import requests import pprint +from werkzeug import urls from odoo import api, fields, models, _ -from odoo.addons.payment.models.payment_acquirer import ValidationError -from odoo.exceptions import UserError -from odoo.tools.safe_eval import safe_eval from odoo.tools.float_utils import float_round -_logger = logging.getLogger(__name__) +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment_stripe.controllers.main import StripeController -# Force the API version to avoid breaking in case of update on Stripe side -# cf https://stripe.com/docs/api#versioning -# changelog https://stripe.com/docs/upgrades#api-changelog -STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'} +_logger = logging.getLogger(__name__) # The following currencies are integer only, see https://stripe.com/docs/currencies#zero-decimal INT_CURRENCIES = [ @@ -38,58 +34,59 @@ class PaymentAcquirerStripe(models.Model): def stripe_form_generate_values(self, tx_values): self.ensure_one() - stripe_tx_values = dict(tx_values) - temp_stripe_tx_values = { - 'company': self.company_id.name, - 'amount': tx_values['amount'], # Mandatory - 'currency': tx_values['currency'].name, # Mandatory anyway - 'currency_id': tx_values['currency'].id, # same here - 'address_line1': tx_values.get('partner_address'), # Any info of the partner is not mandatory - 'address_city': tx_values.get('partner_city'), - 'address_country': tx_values.get('partner_country') and tx_values.get('partner_country').name or '', - 'email': tx_values.get('partner_email'), - 'address_zip': tx_values.get('partner_zip'), - 'name': tx_values.get('partner_name'), - 'phone': tx_values.get('partner_phone'), - } - stripe_tx_values.update(temp_stripe_tx_values) - return stripe_tx_values + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + stripe_session_data = { + 'payment_method_types[]': 'card', + 'line_items[][amount]': int(tx_values['amount'] if tx_values['currency'].name in INT_CURRENCIES else float_round(tx_values['amount'] * 100, 2)), + 'line_items[][currency]': tx_values['currency'].name, + 'line_items[][quantity]': 1, + 'line_items[][name]': tx_values['reference'], + 'client_reference_id': tx_values['reference'], + 'success_url': urls.url_join(base_url, StripeController._success_url) + '?reference=%s' % tx_values['reference'], + 'cancel_url': urls.url_join(base_url, StripeController._cancel_url) + '?reference=%s' % tx_values['reference'], + 'customer_email': tx_values['partner_email'], + } + tx_values['session_id'] = self._create_stripe_session(stripe_session_data) + + return tx_values + + def _stripe_request(self, url, data=False): + self.ensure_one() + url = urls.url_join(self._get_stripe_api_url(), url) + headers = {'AUTHORIZATION': 'Bearer %s' % self.sudo().stripe_secret_key} + resp = requests.post(url, data, headers=headers) + resp.raise_for_status() + return resp.json() + + def _create_stripe_session(self, kwargs): + self.ensure_one() + resp = self._stripe_request('checkout/sessions', kwargs) + if resp.get('payment_intent') and kwargs.get('client_reference_id'): + tx = self.env['payment.transaction'].sudo().search([('reference', '=', kwargs['client_reference_id'])]) + tx.stripe_payment_intent = resp['payment_intent'] + return resp['id'] @api.model def _get_stripe_api_url(self): - return 'api.stripe.com/v1' + return 'https://api.stripe.com/v1/' @api.model def stripe_s2s_form_process(self, data): payment_token = self.env['payment.token'].sudo().create({ - 'cc_number': data['cc_number'], - 'cc_holder_name': data['cc_holder_name'], - 'cc_expiry': data['cc_expiry'], - 'cc_brand': data['cc_brand'], - 'cvc': data['cvc'], 'acquirer_id': int(data['acquirer_id']), - 'partner_id': int(data['partner_id']) + 'partner_id': int(data['partner_id']), + 'stripe_payment_method': data.get('id'), + 'name': 'XXXXXXXXXXXX%s' % data.get('card', {}).get('last4'), + 'acquirer_ref': data.get('customer') }) return payment_token - def stripe_s2s_form_validate(self, data): - self.ensure_one() - - # mandatory fields - for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]: - if not data.get(field_name): - return False - return True - 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 """ @@ -101,27 +98,38 @@ class PaymentAcquirerStripe(models.Model): class PaymentTransactionStripe(models.Model): _inherit = 'payment.transaction' - def _create_stripe_charge(self, acquirer_ref=None, tokenid=None, email=None): - api_url_charge = 'https://%s/charges' % (self.acquirer_id._get_stripe_api_url()) + stripe_payment_intent = fields.Char(string='Stripe Payment Intent ID', readonly=True) + + def form_feedback(self, data, acquirer_name): + if data.get('reference') and acquirer_name == 'stripe': + transaction = self.env['payment.transaction'].search([('reference', '=', data['reference'])]) + + url = 'payment_intents/%s' % transaction.stripe_payment_intent + resp = transaction.acquirer_id._stripe_request(url) + if resp.get('charges') and resp.get('charges').get('total_count'): + resp = resp.get('charges').get('data')[0] + + data.update(resp) + _logger.info('Stripe: entering form_feedback with post data %s' % pprint.pformat(data)) + return super(PaymentTransactionStripe, self).form_feedback(data, acquirer_name) + + def _create_stripe_charge(self, acquirer_ref=None, email=None): + charge_params = { 'amount': int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)), 'currency': self.currency_id.name, - 'metadata[reference]': self.reference, - 'description': self.reference, + 'payment_method_types[]': 'card', + 'customer': acquirer_ref, + 'off_session': 'recurring', + 'confirm': True, + 'payment_method': self.payment_token_id.stripe_payment_method } - if acquirer_ref: - charge_params['customer'] = acquirer_ref - if tokenid: - charge_params['card'] = str(tokenid) - if email: - charge_params['receipt_email'] = email.strip() + _logger.info('_create_stripe_charge: Sending values to stripe, values:\n%s', pprint.pformat(charge_params)) + + res = self.acquirer_id._stripe_request('payment_intents', charge_params) + if res.get('charges') and res.get('charges').get('total_count'): + res = res.get('charges').get('data')[0] - _logger.info('_create_stripe_charge: Sending values to URL %s, values:\n%s', api_url_charge, pprint.pformat(charge_params)) - r = requests.post(api_url_charge, - auth=(self.acquirer_id.stripe_secret_key, ''), - params=charge_params, - headers=STRIPE_HEADERS) - res = r.json() _logger.info('_create_stripe_charge: Values received:\n%s', pprint.pformat(res)) return res @@ -130,9 +138,7 @@ class PaymentTransactionStripe(models.Model): result = self._create_stripe_charge(acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email) 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, @@ -140,13 +146,10 @@ class PaymentTransactionStripe(models.Model): 'metadata[reference]': self.reference, } - _logger.info('_create_stripe_refund: Sending values to URL %s, values:\n%s', api_url_refund, pprint.pformat(refund_params)) - r = requests.post(api_url_refund, - auth=(self.acquirer_id.stripe_secret_key, ''), - params=refund_params, - headers=STRIPE_HEADERS) - res = r.json() + _logger.info('_create_stripe_refund: Sending values to stripe URL, values:\n%s', pprint.pformat(refund_params)) + res = self.acquirer_id._stripe_request('refunds', refund_params) _logger.info('_create_stripe_refund: Values received:\n%s', pprint.pformat(res)) + return res def stripe_s2s_do_refund(self, **kwargs): @@ -158,11 +161,11 @@ class PaymentTransactionStripe(models.Model): def _stripe_form_get_tx_from_data(self, data): """ Given a data dict coming from stripe, verify it and find the related transaction record. """ - reference = data.get('metadata', {}).get('reference') + reference = data.get('reference') if not reference: stripe_error = data.get('error', {}).get('message', '') _logger.error('Stripe: invalid reply received from stripe API, looks like ' - 'the transaction failed. (error: %s)', stripe_error or 'n/a') + 'the transaction failed. (error: %s)', stripe_error or 'n/a') error_msg = _("We're sorry to report that the transaction has failed.") if stripe_error: error_msg += " " + (_("Stripe gave us the following info about the problem: '%s'") % @@ -189,111 +192,78 @@ class PaymentTransactionStripe(models.Model): return True status = tree.get('status') + tx_id = tree.get('id') + vals = { + 'date': fields.datetime.now(), + 'acquirer_reference': tx_id, + } if status == 'succeeded': - self.write({ - 'date': fields.datetime.now(), - 'acquirer_reference': tree.get('id'), - }) + self.write(vals) self._set_transaction_done() self.execute_callback() + if self.type == 'form_save': + s2s_data = { + 'customer': tree.get('customer'), + 'id': tree.get('payment_method'), + 'card': tree.get('payment_method_details').get('card'), + 'acquirer_id': self.acquirer_id.id, + 'partner_id': self.partner_id.id + } + token = self.acquirer_id.stripe_s2s_form_process(s2s_data) + self.payment_token_id = token.id if self.payment_token_id: self.payment_token_id.verified = True return True + if status == 'processing': + self.write(vals) + self._set_transaction_pending() + return True else: - error = tree['error']['message'] + error = tree.get('failure_message') _logger.warn(error) - self.sudo().write({ - 'state_message': error, - 'acquirer_reference': tree.get('id'), - 'date': fields.datetime.now(), - }) + vals.update({'state_message': error}) + self.write(vals) self._set_transaction_cancel() return False def _stripe_form_get_invalid_parameters(self, data): invalid_parameters = [] - reference = data['metadata']['reference'] - if reference != self.reference: - invalid_parameters.append(('Reference', reference, self.reference)) + if data.get('amount') != int(self.amount if self.currency_id.name in INT_CURRENCIES else float_round(self.amount * 100, 2)): + invalid_parameters.append(('Amount', data.get('amount'), self.amount * 100)) + if data.get('currency').upper() != self.currency_id.name: + invalid_parameters.append(('Currency', data.get('currency'), self.currency_id.name)) + if data.get('payment_intent') and data.get('payment_intent') != self.stripe_payment_intent: + invalid_parameters.append(('Payment Intent', data.get('payment_intent'), self.stripe_payment_intent)) return invalid_parameters - def _stripe_form_validate(self, data): + def _stripe_form_validate(self, data): return self._stripe_s2s_validate_tree(data) class PaymentTokenStripe(models.Model): _inherit = 'payment.token' + stripe_payment_method = fields.Char('Payment Method ID') + @api.model def stripe_create(self, values): - token = values.get('stripe_token') - description = None - payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) - # when asking to create a token on Stripe servers - if values.get('cc_number'): - url_token = 'https://%s/tokens' % payment_acquirer._get_stripe_api_url() - payment_params = { - 'card[number]': values['cc_number'].replace(' ', ''), - 'card[exp_month]': str(values['cc_expiry'][:2]), - 'card[exp_year]': str(values['cc_expiry'][-2:]), - 'card[cvc]': values['cvc'], - 'card[name]': values['cc_holder_name'], + if values.get('stripe_payment_method') and not values.get('acquirer_ref'): + partner_id = self.env['res.partner'].browse(values.get('partner_id')) + payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id')) + + # create customer to stipe + customer_data = { + 'email': partner_id.email } - r = requests.post(url_token, - auth=(payment_acquirer.stripe_secret_key, ''), - params=payment_params, - headers=STRIPE_HEADERS) - token = r.json() - description = values['cc_holder_name'] - else: - partner_id = self.env['res.partner'].browse(values['partner_id']) - description = 'Partner: %s (id: %s)' % (partner_id.name, partner_id.id) - - if not token: - raise UserError(_("Stripe: no payment token was provided or the token creation failed.")) - - res = self._stripe_create_customer(token, description, payment_acquirer.id) - - # pop credit card info to info sent to create - for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand", "stripe_token"]: - values.pop(field_name, None) - return res - - - def _stripe_create_customer(self, token, description=None, acquirer_id=None): - if token.get('error'): - _logger.error('payment.token.stripe_create_customer: Token error:\n%s', pprint.pformat(token['error'])) - raise Exception(token['error']['message']) - - if token['object'] != 'token': - _logger.error('payment.token.stripe_create_customer: Cannot create a customer for object type "%s"', token.get('object')) - raise Exception('We are unable to process your credit card information.') - - if token['type'] != 'card': - _logger.error('payment.token.stripe_create_customer: Cannot create a customer for token type "%s"', token.get('type')) - raise Exception('We are unable to process your credit card information.') - - payment_acquirer = self.env['payment.acquirer'].browse(acquirer_id or self.acquirer_id.id) - url_customer = 'https://%s/customers' % payment_acquirer._get_stripe_api_url() - - customer_params = { - 'source': token['id'], - 'description': description or token["card"]["name"] - } - - r = requests.post(url_customer, - auth=(payment_acquirer.stripe_secret_key, ''), - params=customer_params, - headers=STRIPE_HEADERS) - customer = r.json() - - if customer.get('error'): - _logger.error('payment.token.stripe_create_customer: Customer error:\n%s', pprint.pformat(customer['error'])) - raise Exception(customer['error']['message']) - - values = { - 'acquirer_ref': customer['id'], - 'name': 'XXXXXXXXXXXX%s - %s' % (token['card']['last4'], customer_params["description"]) - } + cust_resp = payment_acquirer._stripe_request('customers', customer_data) + # link customer with payment method + api_url_payment_method = 'payment_methods/%s/attach' % values['stripe_payment_method'] + method_data = { + 'customer': cust_resp.get('id') + } + payment_acquirer._stripe_request(api_url_payment_method, method_data) + return { + 'acquirer_ref': cust_resp['id'], + } return values diff --git a/addons/payment_stripe/static/src/js/payment_form.js b/addons/payment_stripe/static/src/js/payment_form.js new file mode 100644 index 00000000000..43227941d56 --- /dev/null +++ b/addons/payment_stripe/static/src/js/payment_form.js @@ -0,0 +1,154 @@ +odoo.define('payment_stripe.payment_form', function (require) { +"use strict"; + +var ajax = require('web.ajax'); +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var PaymentForm = require('payment.payment_form'); + +var qweb = core.qweb; +var _t = core._t; + +ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb); + +PaymentForm.include({ + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * called when clicking on pay now or add payment event to create token for credit card/debit card. + * + * @private + * @param {Event} ev + * @param {DOMElement} checkedRadio + * @param {Boolean} addPmEvent + */ + _createStripeToken: function (ev, $checkedRadio, addPmEvent) { + var self = this; + var acquirerID = this.getAcquirerIdFromRadio($checkedRadio); + var acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID); + var inputsForm = $('input', acquirerForm); + if (this.options.partnerId === undefined) { + console.warn('payment_form: unset partner_id when adding new token; things could go wrong'); + } + + ajax.loadJS("https://js.stripe.com/v3/").then(function () { + var formData = self.getFormData(inputsForm); + var stripe = Stripe(formData.stripe_publishable_key); + var element = stripe.elements(); + var card = element.create('card', {hidePostalCode: true}); + var dialog = new Dialog(self, { + title: _t('Card Details'), + size: 'medium', + technical: false, + buttons: [{text: _t('Submit'), classes: 'btn-primary', click: function (ev) { + stripe.createPaymentMethod('card', card).then(function (result) { + if (result.error) { + // Inform the user if there was an error. + var errorElement = document.getElementById('card-errors'); + errorElement.textContent = result.error.message; + } else { + ev.currentTarget.disabled = true; + _.extend(formData, result.paymentMethod); + // Send the token to server. + self._rpc({ + route: formData.data_set, + params: formData + }).then (function (data) { + if (addPmEvent) { + if (formData.return_url) { + window.location = formData.return_url; + } else { + window.location.reload(); + } + } else { + $checkedRadio.val(data.id); + self.$el.submit(); + } + }).guardedCatch(function (error) { + // if the rpc fails, pretty obvious + self.enableButton(ev.target); + + self.displayError( + _t('Server Error'), + _t("We are not able to add your payment method at the moment.") + + error.message.data.message + ); + }); + } + }); + }}], + $content: $(qweb.render('stripe.payment.element')), + }); + dialog.opened().then(function () { + self.disableButton(ev.target); + card.mount('#card-element'); + card.addEventListener('change', function (event) { + var displayError = document.getElementById('card-errors'); + displayError.textContent = ''; + if (event.error) { + displayError.textContent = event.error.message; + } + }); + }); + dialog.on('closed', self, function () { + self.enableButton(ev.target); + }); + dialog.open(); + }); + }, + /** + * @override + */ + updateNewPaymentDisplayStatus: function () { + var $checkedRadio = this.$('input[type="radio"]:checked'); + + if ($checkedRadio.length !== 1) { + return; + } + + // hide add token form for stripe + if ($checkedRadio.data('provider') === 'stripe' && this.isNewPaymentRadio($checkedRadio)) { + this.$('[id*="o_payment_add_token_acq_"]').addClass('d-none'); + } else { + this._super.apply(this, arguments); + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @override + */ + payEvent: function (ev) { + ev.preventDefault(); + var $checkedRadio = this.$('input[type="radio"]:checked'); + + // first we check that the user has selected a stripe as s2s payment method + if ($checkedRadio.length === 1 && this.isNewPaymentRadio($checkedRadio) && $checkedRadio.data('provider') === 'stripe') { + this._createStripeToken(ev, $checkedRadio); + } else { + this._super.apply(this, arguments); + } + }, + /** + * @override + */ + addPmEvent: function (ev) { + ev.stopPropagation(); + ev.preventDefault(); + var $checkedRadio = this.$('input[type="radio"]:checked'); + + // first we check that the user has selected a stripe as add payment method + if ($checkedRadio.length === 1 && this.isNewPaymentRadio($checkedRadio) && $checkedRadio.data('provider') === 'stripe') { + this._createStripeToken(ev, $checkedRadio, true); + } else { + this._super.apply(this, arguments); + } + }, +}); +}); diff --git a/addons/payment_stripe/static/src/js/stripe.js b/addons/payment_stripe/static/src/js/stripe.js index 53c0b79e7d7..4868c9db9bd 100644 --- a/addons/payment_stripe/static/src/js/stripe.js +++ b/addons/payment_stripe/static/src/js/stripe.js @@ -1,141 +1,81 @@ -odoo.define('payment_stripe.stripe', function(require) { - "use strict"; +odoo.define('payment_stripe.stripe', function (require) { +"use strict"; - var ajax = require('web.ajax'); - var core = require('web.core'); - var _t = core._t; - var qweb = core.qweb; - ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb); +var ajax = require('web.ajax'); +var core = require('web.core'); - // The following currencies are integer only, see - // https://stripe.com/docs/currencies#zero-decimal - var int_currencies = [ - 'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYG', - 'RWF', 'KRW', 'VUV', 'VND', 'XOF' - ]; +var qweb = core.qweb; +var _t = core._t; - if ($.blockUI) { - // our message needs to appear above the modal dialog - $.blockUI.defaults.baseZ = 2147483647; //same z-index as StripeCheckout - $.blockUI.defaults.css.border = '0'; - $.blockUI.defaults.css["background-color"] = ''; - $.blockUI.defaults.overlayCSS["opacity"] = '0.9'; - } - var stripeHandler; - function getStripeHandler() - { - if (stripeHandler) { - return stripeHandler; - } - var handler = stripeHandler = StripeCheckout.configure({ - key: $("input[name='stripe_key']").val(), - image: $("input[name='stripe_image']").val(), - locale: 'auto', - token: function(token, args) { - handler.isTokenGenerate = true; - if ($.blockUI) { - var msg = _t("Just one more second, confirming your payment..."); - $.blockUI({ - 'message': '

' + - '
' + msg + - '

' - }); - } - ajax.jsonRpc("/payment/stripe/create_charge", 'call', { - tokenid: token.id, // TBE TODO: for backward compatibility, remove on master - email: token.email, // TBE TODO: for backward compatibility, remove on master - token: token, - amount: $("input[name='amount']").val(), - acquirer_id: $("#acquirer_stripe").val(), - currency: $("input[name='currency']").val(), - invoice_num: $("input[name='invoice_num']").val(), - tx_ref: $("input[name='invoice_num']").val(), - return_url: $("input[name='return_url']").val() - }).then(function(data){ - handler.isTokenGenerate = false; - window.location.href = data; - if ($.blockUI) { - $.unblockUI(); - } - }).guardedCatch(function(data){ - var msg = data && data.data && data.data.message; - var wizard = $(qweb.render('stripe.error', {'msg': msg || _t('Payment error')})); - wizard.appendTo($('body')).modal({'keyboard': true}); - if ($.blockUI) { - $.unblockUI(); - } - }); - }, - }); - return handler; - } +ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb); - require('web.dom_ready'); - if (!$('.o_payment_form').length) { - return Promise.reject("DOM doesn't contain '.o_payment_form'"); - } +if ($.blockUI) { + // our message needs to appear above the modal dialog + $.blockUI.defaults.baseZ = 2147483647; //same z-index as StripeCheckout + $.blockUI.defaults.css.border = '0'; + $.blockUI.defaults.css["background-color"] = ''; + $.blockUI.defaults.overlayCSS["opacity"] = '0.9'; +} - var observer = new MutationObserver(function(mutations, observer) { - for(var i=0; i'); - payment_form.attr('disabled','disabled'); - - var payment_tx_url = payment_form.find('input[name="prepare_tx_url"]').val(); - var access_token = $("input[name='access_token']").val() || $("input[name='token']").val() || ''; - - var get_input_value = function(name) { - return provider_form.find('input[name="' + name + '"]').val(); - } - - var acquirer_id = parseInt(provider_form.find('#acquirer_stripe').val()); - var amount = parseFloat(get_input_value("amount") || '0.0'); - var currency = get_input_value("currency"); - var email = get_input_value("email"); - var invoice_num = get_input_value("invoice_num"); - var merchant = get_input_value("merchant"); - - // Search if the user wants to save the credit card information - var form_save_token = false; - var acquirer_form = $('#o_payment_form_acq_' + acquirer_id); - if (acquirer_form.length) { - form_save_token = acquirer_form.find('input[name="o_payment_form_save_token"]').prop('checked'); - } - - ajax.jsonRpc(payment_tx_url, 'call', { - acquirer_id: acquirer_id, - access_token: access_token, - save_token: form_save_token, - }).then(function(data) { - var $pay_stripe = $('#pay_stripe').detach(); - try { provider_form[0].innerHTML = data; } catch (e) {} - // Restore 'Pay Now' button HTML since data might have changed it. - $(provider_form[0]).find('#pay_stripe').replaceWith($pay_stripe); - }).then(function () { - getStripeHandler().open({ - name: merchant, - description: invoice_num, - email: email, - currency: currency, - amount: _.contains(int_currencies, currency) ? amount : amount * 100, - }); +function _redirectToStripeCheckout(providerForm) { + // Open Checkout with further options + if ($.blockUI) { + var msg = _t("Just one more second, We are redirecting you to Stripe..."); + $.blockUI({ + 'message': '

' + + '
' + msg + + '

' }); } - $.getScript("https://checkout.stripe.com/checkout.js", function(data, textStatus, jqxhr) { - observer.observe(document.body, {childList: true}); - display_stripe_form($('form[provider="stripe"]')); + var paymentForm = $('.o_payment_form'); + if (!paymentForm.find('i').length) { + paymentForm.append(''); + paymentForm.attr('disabled', 'disabled'); + } + + var _getStripeInputValue = function (name) { + return providerForm.find('input[name="' + name + '"]').val(); + }; + + var stripe = Stripe(_getStripeInputValue('stripe_key')); + + stripe.redirectToCheckout({ + sessionId: _getStripeInputValue('session_id') + }).then(function (result) { + if (result.error) { + displayError(result.error.message); + } }); +} + +$.getScript("https://js.stripe.com/v3/", function (data, textStatus, jqxhr) { + observer.observe(document.body, {childList: true}); + _redirectToStripeCheckout($('form[provider="stripe"]')); +}); }); diff --git a/addons/payment_stripe/static/src/xml/stripe_templates.xml b/addons/payment_stripe/static/src/xml/stripe_templates.xml index fd47d911cc9..e0ab901a825 100644 --- a/addons/payment_stripe/static/src/xml/stripe_templates.xml +++ b/addons/payment_stripe/static/src/xml/stripe_templates.xml @@ -1,21 +1,28 @@ - -