From 302ed50cef01a9fe0c913a5543a46bea540d8863 Mon Sep 17 00:00:00 2001 From: Damien Bouvy Date: Fri, 6 Sep 2019 13:58:13 +0000 Subject: [PATCH] [ADD] payment_stripe_sca: Stripe API changes following PSD2 With the PSD2 directive coming into effect on September 14th, was had no choice but to switch our Stripe integration to use newer APIs that support Strong Customer Authentication (SCA for short). This meant switching from the old stripe.js implementation for 'redirection' flow to the Stripe Checkout API and from the Charge API to the newer SetupIntent API for s2s flows. This new modules introduce these changes in a non-intrusive way, this means that installing this module is *not* required for all customers (e.g. instances only accepting payment to and from countries that are not part of the EU), that installing it will support SCA flows and that uninstallation leaves you with a working instance as well. Essentially a backport of 9f5f2fc in a seperate module. Task 2039100 --- addons/payment_stripe_sca/__init__.py | 4 + addons/payment_stripe_sca/__manifest__.py | 38 ++ .../controllers/__init__.py | 3 + addons/payment_stripe_sca/controllers/main.py | 68 ++++ addons/payment_stripe_sca/models/__init__.py | 3 + addons/payment_stripe_sca/models/payment.py | 372 ++++++++++++++++++ .../static/description/icon.png | Bin 0 -> 25859 bytes .../static/description/icon.svg | 1 + .../static/description/index.html | 64 +++ .../static/description/redirection_flow.png | Bin 0 -> 39730 bytes .../static/description/s2s_flow.png | Bin 0 -> 14935 bytes .../static/src/js/payment_form.js | 188 +++++++++ .../static/src/js/payment_processing.js | 48 +++ .../static/src/js/stripe_sca.js | 82 ++++ addons/payment_stripe_sca/views/assets.xml | 8 + .../views/payment_templates.xml | 42 ++ 16 files changed, 921 insertions(+) create mode 100644 addons/payment_stripe_sca/__init__.py create mode 100644 addons/payment_stripe_sca/__manifest__.py create mode 100644 addons/payment_stripe_sca/controllers/__init__.py create mode 100644 addons/payment_stripe_sca/controllers/main.py create mode 100644 addons/payment_stripe_sca/models/__init__.py create mode 100644 addons/payment_stripe_sca/models/payment.py create mode 100644 addons/payment_stripe_sca/static/description/icon.png create mode 100644 addons/payment_stripe_sca/static/description/icon.svg create mode 100644 addons/payment_stripe_sca/static/description/index.html create mode 100644 addons/payment_stripe_sca/static/description/redirection_flow.png create mode 100644 addons/payment_stripe_sca/static/description/s2s_flow.png create mode 100644 addons/payment_stripe_sca/static/src/js/payment_form.js create mode 100644 addons/payment_stripe_sca/static/src/js/payment_processing.js create mode 100644 addons/payment_stripe_sca/static/src/js/stripe_sca.js create mode 100644 addons/payment_stripe_sca/views/assets.xml create mode 100644 addons/payment_stripe_sca/views/payment_templates.xml diff --git a/addons/payment_stripe_sca/__init__.py b/addons/payment_stripe_sca/__init__.py new file mode 100644 index 00000000000..bb0aae7ee63 --- /dev/null +++ b/addons/payment_stripe_sca/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import controllers +from . import models diff --git a/addons/payment_stripe_sca/__manifest__.py b/addons/payment_stripe_sca/__manifest__.py new file mode 100644 index 00000000000..6a2a0cac556 --- /dev/null +++ b/addons/payment_stripe_sca/__manifest__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +# ____________________ +# / \ +# | DO NOT FORWARD | +# | PORT FURTHER | +# | THAN 12.4 | +# \____________________/ +# ! ! +# ! ! +# L_ ! +# / _)! +# / /__L +# _____/ (____) +# (____) +# _____ (____) +# \_(____) +# ! ! +# ! ! +# \__/ +# +# Starting with 12.5, the payment_stripe module contains these changes OoB + +{ + 'name': 'Stripe Payment Acquirer - Strong Customer Authentication Update', + 'category': 'Hidden', + 'summary': 'Payment Acquirer: Stripe Implementation for the EU PSD2', + 'version': '1.0', + 'description': """Stripe Payment Acquirer - Strong Customer Authentication Update""", + 'depends': ['payment_stripe'], + 'auto_install': True, + 'data': [ + 'views/assets.xml', + 'views/payment_templates.xml', + ], + 'images': ['static/description/icon.png'], +} diff --git a/addons/payment_stripe_sca/controllers/__init__.py b/addons/payment_stripe_sca/controllers/__init__.py new file mode 100644 index 00000000000..7036966b1ce --- /dev/null +++ b/addons/payment_stripe_sca/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import main \ No newline at end of file diff --git a/addons/payment_stripe_sca/controllers/main.py b/addons/payment_stripe_sca/controllers/main.py new file mode 100644 index 00000000000..a3037125301 --- /dev/null +++ b/addons/payment_stripe_sca/controllers/main.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import logging +import pprint +import werkzeug + +from odoo.http import route, request +from odoo.addons.payment_stripe.controllers.main import StripeController + +_logger = logging.getLogger(__name__) + + +class StripeControllerSCA(StripeController): + _success_url = '/payment/stripe/success' + _cancel_url = '/payment/stripe/cancel' + + @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') + + @route(['/payment/stripe/s2s/create_json_3ds'], type='json', auth='public', csrf=False) + def stripe_s2s_create_json_3ds(self, verify_validity=False, **kwargs): + if not kwargs.get('partner_id'): + kwargs = dict(kwargs, partner_id=request.env.user.partner_id.id) + token = request.env['payment.acquirer'].browse(int(kwargs.get('acquirer_id'))).s2s_process(kwargs) + + if not token: + 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 + + @route('/payment/stripe/s2s/create_setup_intent', type='json', auth='public', csrf=False) + def stripe_s2s_create_setup_intent(self, acquirer_id, **kwargs): + acquirer = request.env['payment.acquirer'].browse(int(acquirer_id)) + res = acquirer._create_setup_intent(kwargs) + return res.get('client_secret') + + + @route('/payment/stripe/s2s/process_payment_intent', type='json') + def stripe_s2s_process_payment_intent(self, **post): + return request.env['payment.transaction'].sudo().form_feedback(post, 'stripe') + + + # These routes are deprecated, let's remove them for security's sake + def stripe_s2s_create_json(self, **post): + raise werkzeug.exceptions.NotFound() + + def stripe_s2s_create(self, **post): + raise werkzeug.exceptions.NotFound() + + def stripe_create_charge(self, **post): + raise werkzeug.exceptions.NotFound() diff --git a/addons/payment_stripe_sca/models/__init__.py b/addons/payment_stripe_sca/models/__init__.py new file mode 100644 index 00000000000..df1092a4fa3 --- /dev/null +++ b/addons/payment_stripe_sca/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import payment \ No newline at end of file diff --git a/addons/payment_stripe_sca/models/payment.py b/addons/payment_stripe_sca/models/payment.py new file mode 100644 index 00000000000..453f3a2678b --- /dev/null +++ b/addons/payment_stripe_sca/models/payment.py @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import logging +import requests +import pprint +from werkzeug import urls + +from odoo import api, models, fields, _ +from odoo.exceptions import ValidationError +from odoo.addons.payment_stripe_sca.controllers.main import StripeControllerSCA as StripeController +from odoo.addons.payment_stripe.models.payment import INT_CURRENCIES +from odoo.tools.float_utils import float_round + +_logger = logging.getLogger(__name__) + + +class PaymentAcquirerStripeSCA(models.Model): + _inherit = "payment.acquirer" + + def stripe_form_generate_values(self, tx_values): + self.ensure_one() + + 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, method="POST"): + self.ensure_one() + url = urls.url_join(self._get_stripe_api_url(), url) + headers = { + "AUTHORIZATION": "Bearer %s" % self.sudo().stripe_secret_key, + "Stripe-Version": "2019-05-16", # SetupIntent need a specific version + } + resp = requests.request(method, url, data=data, headers=headers) + try: + resp.raise_for_status() + except: + _logger.error(resp.text) + raise + 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"] + + def _create_setup_intent(self, kwargs): + self.ensure_one() + params = {"usage": "off_session"} + _logger.info( + "_stripe_create_setup_intent: Sending values to stripe, values:\n%s", + pprint.pformat(params), + ) + + res = self._stripe_request("setup_intents", params) + + _logger.info( + "_stripe_create_setup_intent: Values received:\n%s", pprint.pformat(res) + ) + return res + + @api.model + def _get_stripe_api_url(self): + return "https://api.stripe.com/v1/" + + @api.model + def stripe_s2s_form_process(self, data): + last4 = data.get("card", {}).get("last4") + if not last4: + # PM was created with a setup intent, need to get last4 digits through + # yet another call -_- + acquirer_id = self.env["payment.acquirer"].browse(int(data["acquirer_id"])) + pm = data.get("payment_method") + res = acquirer_id._stripe_request( + "payment_methods/%s" % pm, data=False, method="GET" + ) + last4 = res.get("card", {}).get("last4", "****") + + payment_token = ( + self.env["payment.token"] + .sudo() + .create( + { + "acquirer_id": int(data["acquirer_id"]), + "partner_id": int(data["partner_id"]), + "stripe_payment_method": data.get("payment_method"), + "name": "XXXXXXXXXXXX%s" % last4, + "acquirer_ref": data.get("customer"), + } + ) + ) + return payment_token + + def stripe_s2s_form_validate(self, data): + return True + + +class PaymentTransactionStripeSCA(models.Model): + _inherit = "payment.transaction" + + stripe_payment_intent = fields.Char( + string="Stripe Payment Intent ID", readonly=True + ) + stripe_payment_intent_secret = fields.Char(string='Stripe Payment Intent Secret', readonly=True) + + def _get_json_fields(self): + res = super()._get_json_fields() + res.append('stripe_payment_intent_secret') + return res + + def _get_processing_info(self): + res = super()._get_processing_info() + if self.acquirer_id.provider == 'stripe': + stripe_info = { + 'stripe_payment_intent': self.stripe_payment_intent, + 'stripe_payment_intent_secret': self.stripe_payment_intent_secret, + 'stripe_publishable_key': self.acquirer_id.stripe_publishable_key, + } + res.update(stripe_info) + return res + + def _create_stripe_charge(self, acquirer_ref=None, tokenid=None, email=None): + raise NotImplementedError( + "This method can no longer be used with the payment_stripe_sca module." + ) + + 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) + ) + # note: luckily, the base stripe module did not override this method, avoiding + # me from using a context key to avoid this call in the parent model + return super(PaymentTransactionStripeSCA, self).form_feedback(data, acquirer_name) + + def _stripe_create_payment_intent(self, acquirer_ref=None, email=None): + if self.stripe_payment_intent: + _logger.info( + "_stripe_create_payment_intent: trying to create an intent when one already exists (tx #%s), refetching values for intent %s", + self.id, self.stripe_payment_intent + ) + res = self.acquirer_id._stripe_request("payment_intents/%s" % self.stripe_payment_intent, method="GET") + _logger.info( + "_stripe_create_payment_intent: Values received:\n%s", pprint.pformat(res) + ) + return res + 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.lower(), + "confirm": True, + "off_session": True, + "payment_method": self.payment_token_id.stripe_payment_method, + "customer": self.payment_token_id.acquirer_ref, + "description": self.reference, + } + if not self.env.context.get('off_session'): + charge_params.update(setup_future_usage='off_session', off_session=False) + _logger.info( + "_stripe_create_payment_intent: 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( + "_stripe_create_payment_intent: Values received:\n%s", pprint.pformat(res) + ) + return res + + def stripe_s2s_do_transaction(self, **kwargs): + self.ensure_one() + result = self._stripe_create_payment_intent( + acquirer_ref=self.payment_token_id.acquirer_ref, email=self.partner_email + ) + return self._stripe_s2s_validate_tree(result) + + def _create_stripe_refund(self): + refund_params = { + "charge": self.acquirer_reference, + "amount": int( + float_round(self.amount * 100, 2) + ), # by default, stripe refund the full amount (we don't really need to specify the value) + "metadata[reference]": self.reference, + } + + _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 + + @api.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("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", + ) + 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'") + % stripe_error + ) + error_msg += " " + _( + "Perhaps the problem can be solved by double-checking your " + "credit card details, or contacting your bank?" + ) + raise ValidationError(error_msg) + + tx = self.search([("reference", "=", reference)]) + if not tx: + error_msg = _("Stripe: no order found for reference %s") % reference + _logger.error(error_msg) + raise ValidationError(error_msg) + elif len(tx) > 1: + error_msg = _("Stripe: %s orders found for reference %s") % ( + len(tx), + reference, + ) + _logger.error(error_msg) + raise ValidationError(error_msg) + return tx[0] + + def _stripe_s2s_validate_tree(self, tree): + self.ensure_one() + if self.state not in ("draft", "pending"): + _logger.info( + "Stripe: trying to validate an already validated tx (ref %s)", + self.reference, + ) + return True + + status = tree.get("status") + tx_id = tree.get("id") + tx_secret = tree.get("client_secret") + vals = {"date": fields.datetime.now(), "acquirer_reference": tx_id, "stripe_payment_intent": tx_id, "stripe_payment_intent_secret": tx_secret} + if status == "succeeded": + self.write(vals) + self._set_transaction_done() + self.execute_callback() + if self.type == "form_save": + s2s_data = { + "customer": tree.get("customer"), + "payment_method": 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 in ("processing", "requires_action"): + self.write(vals) + self._set_transaction_pending() + return True + else: + error = tree.get("failure_message") + _logger.warn(error) + vals.update({"state_message": error}) + self.write(vals) + self._set_transaction_cancel() + return False + + def _stripe_form_get_invalid_parameters(self, data): + invalid_parameters = [] + 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 + + +class PaymentTokenStripeSCA(models.Model): + _inherit = "payment.token" + + stripe_payment_method = fields.Char("Payment Method ID") + + @api.model + def stripe_create(self, values): + 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} + 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 + + def _stripe_create_customer(self, token, description=None, acquirer_id=None): + raise NotImplementedError( + "This method can no longer be used with the payment_stripe_sca module." + ) + diff --git a/addons/payment_stripe_sca/static/description/icon.png b/addons/payment_stripe_sca/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..497b2d65bd916dba91393a3143966e5d97ca004f GIT binary patch literal 25859 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

>BX}N=)=bI(|@sQB4Q*>{-=At2&6ux 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 E10U#i$N&HU literal 0 HcmV?d00001 diff --git a/addons/payment_stripe_sca/static/description/icon.svg b/addons/payment_stripe_sca/static/description/icon.svg new file mode 100644 index 00000000000..f169bb0d45e --- /dev/null +++ b/addons/payment_stripe_sca/static/description/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/payment_stripe_sca/static/description/index.html b/addons/payment_stripe_sca/static/description/index.html new file mode 100644 index 00000000000..284cee7c413 --- /dev/null +++ b/addons/payment_stripe_sca/static/description/index.html @@ -0,0 +1,64 @@ +

+
+
+

Stripe - Strong Customer Authentication

+

Add support for strong customer authentication as part of the EU PSD2

+
+
+

+ The PSD2 - the second edition of the Payment Services Directive - is coming into full effect in + the European Union (EU) and the European Economic Area (EEA) on September 14th, 2019. This + directive may indirectly impact your business by requiring stronger authentication mechanisms + for online payments. +

+

Am I impacted by the PSD2 directive?

+

You are impacted by this change if all of these statements applies to your business: +

    +
  • You are legally based in the EU/EEA
  • +
  • You accept payments from customers based in the EU/EEA
  • +
  • You accept payments via cards (credit/debit)
  • +
+

+

What should I do if I am impacted?

+

+ This directive does not bind you legally to do anything in particular regarding + your business processes - it applies to payment processors, acquirers and banks. + Legally, you don't have to do anything to comply with this directive - it does not + apply to you. +

+

+ You might however be indirectly impacted because some online payment flows will + change for customers that pay online - they might need to authenticate themselves during + a transaction that previously didn't require it. This is why Odoo is adapting some of + its integration with payment acquirers to avoid disruptions in your business flows. +

+

+ If you use Stripe and are impacted by the PSD2 directive, then simply installing this + module will avoid problems for your online customers during their payment flow. No extra + configuration is needed once the module is installed. +

+

What happens if I don't install this update?

+

If you don't install this update, some of your customers may be unable to pay you. + There is no reason not to install it if you are impacted by the directive.

+

I'm not impacted by the PSD2, should I install this module anyway?

+

While not required, installing this module remains a good idea nonetheless since + it will switch your instance to newer - and thus more future-proof - APIs of the Stripe + payment acquirer. +

+

What will changes for my online customers

+

The interface displayed to your customers will change when they pay you online.

+

If your Stripe acquirer is configured with the payment flow 'Redirection to the acquirer website' + in Odoo, the small payment overlay will be replaced by a full-screen interface. +

+ +

If your Stripe acquirer is configured with the payment flow 'Payment from Odoo' + in Odoo, the credit card input form will look slightly different. +

+ +

The rest of the payment experience remains the same.

+
+
+
+ + + diff --git a/addons/payment_stripe_sca/static/description/redirection_flow.png b/addons/payment_stripe_sca/static/description/redirection_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..593fb5e1aab76e80ccfa047cd1502c5452a42ab2 GIT binary patch literal 39730 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 E0G6z>3jhEB literal 0 HcmV?d00001 diff --git a/addons/payment_stripe_sca/static/src/js/payment_form.js b/addons/payment_stripe_sca/static/src/js/payment_form.js new file mode 100644 index 00000000000..e4f7b8f3257 --- /dev/null +++ b/addons/payment_stripe_sca/static/src/js/payment_form.js @@ -0,0 +1,188 @@ +odoo.define('payment_stripe.payment_form', function (require) { + "use strict"; + + var ajax = require('web.ajax'); + var core = require('web.core'); + var rpc = require('web.rpc'); + var Dialog = require('web.Dialog'); + var PaymentForm = require('payment.payment_form'); + + var _t = core._t; + + PaymentForm.include({ + + willStart: function () { + return this._super.apply(this, arguments).then(function () { + return ajax.loadJS("https://js.stripe.com/v3/"); + }) + }, + + //-------------------------------------------------------------------------- + // 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; + if (ev.type === 'submit') { + var button = $(ev.target).find('*[type="submit"]')[0] + } else { + var button = ev.target; + } + this.disableButton(button); + 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'); + } + + var formData = self.getFormData(inputsForm); + var stripe = this.stripe; + var card = this.stripe_card_element; + if (card._invalid) { + return; + } + return rpc.query({ + route: '/payment/stripe/s2s/create_setup_intent', + params: {'acquirer_id': formData.acquirer_id} + }).then(function(intent_secret){ + // need to convert between ES6 promises and jQuery 2 deferred \o/ + return $.Deferred(function(defer) { + stripe.handleCardSetup(intent_secret, card) + .then(function(result) {defer.resolve(result)}) + }); + }).then(function(result) { + if (result.error) { + return $.Deferred().reject({"message": {"data": { "message": result.error.message}}}); + } else { + _.extend(formData, {"payment_method": result.setupIntent.payment_method}); + return rpc.query({ + route: formData.data_set, + params: formData, + }) + } + }).then(function(result) { + if (addPmEvent) { + if (formData.return_url) { + window.location = formData.return_url; + } else { + window.location.reload(); + } + } else { + $checkedRadio.val(result.id); + self.el.submit(); + } + }).fail(function (error, event) { + // if the rpc fails, pretty obvious + self.enableButton(button); + self.displayError( + _t('Unable to save card'), + _t("We are not able to add your payment method at the moment. ") + + error.message.data.message + ); + }); + }, + /** + * called when clicking a Stripe radio if configured for s2s flow; instanciates the card and bind it to the widget. + * + * @private + * @param {DOMElement} checkedRadio + */ + _bindStripeCard: function ($checkedRadio) { + var acquirerID = this.getAcquirerIdFromRadio($checkedRadio); + var acquirerForm = this.$('#o_payment_add_token_acq_' + acquirerID); + var inputsForm = $('input', acquirerForm); + var formData = this.getFormData(inputsForm); + var stripe = Stripe(formData.stripe_publishable_key); + var element = stripe.elements(); + var card = element.create('card', {hidePostalCode: true}); + card.mount('#card-element'); + card.on('ready', function(ev) { + card.focus(); + }); + card.addEventListener('change', function (event) { + var displayError = document.getElementById('card-errors'); + displayError.textContent = ''; + if (event.error) { + displayError.textContent = event.error.message; + } + }); + this.stripe = stripe; + this.stripe_card_element = card; + }, + /** + * destroys the card element and any stripe instance linked to the widget. + * + * @private + */ + _unbindStripeCard: function () { + if (this.stripe_card_element) { + this.stripe_card_element.destroy(); + } + this.stripe = undefined; + this.stripe_card_element = undefined; + }, + /** + * @override + */ + updateNewPaymentDisplayStatus: function () { + var $checkedRadio = this.$('input[type="radio"]:checked'); + + if ($checkedRadio.length !== 1) { + return; + } + var provider = $checkedRadio.data('provider') + if (provider === 'stripe') { + // always re-init stripe (in case of multiple acquirers for stripe, make sure the stripe instance is using the right key) + this._unbindStripeCard(); + if (this.isNewPaymentRadio($checkedRadio)) { + this._bindStripeCard($checkedRadio); + } + } + return 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 && $checkedRadio.data('provider') === 'stripe' && this.isNewPaymentRadio($checkedRadio)) { + return this._createStripeToken(ev, $checkedRadio); + } else { + return 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') { + return this._createStripeToken(ev, $checkedRadio, true); + } else { + return this._super.apply(this, arguments); + } + }, + }); +}); + \ No newline at end of file diff --git a/addons/payment_stripe_sca/static/src/js/payment_processing.js b/addons/payment_stripe_sca/static/src/js/payment_processing.js new file mode 100644 index 00000000000..adb57e6221a --- /dev/null +++ b/addons/payment_stripe_sca/static/src/js/payment_processing.js @@ -0,0 +1,48 @@ +odoo.define('payment_stripe_sca.processing', function (require) { + 'use strict'; + + var ajax = require('web.ajax'); + var rpc = require('web.rpc') + var PaymentProcessing = require('payment.processing'); + + return PaymentProcessing.include({ + init: function () { + this._super.apply(this, arguments); + this._authInProgress = false; + }, + willStart: function () { + return this._super.apply(this, arguments).then(function () { + return ajax.loadJS("https://js.stripe.com/v3/"); + }) + }, + _stripeAuthenticate: function (tx) { + var stripe = Stripe(tx.stripe_publishable_key); + return $.Deferred(function(defer) { + stripe.handleCardPayment(tx.stripe_payment_intent_secret) + .then(function(result) {defer.resolve(result)}) + }).then(function(result) { + if (result.error) { + return $.Deferred().reject({"message": {"data": { "message": result.error.message}}}); + } + return rpc.query({ + route: '/payment/stripe/s2s/process_payment_intent', + params: _.extend({}, result.paymentIntent, {reference: tx.reference}), + }); + }).then(function(result) { + window.location = '/payment/process'; + }).fail(function (error) { + this._authInProgress = false; + }); + }, + processPolledData: function(transactions) { + this._super.apply(this, arguments); + for (var itx=0; itx < transactions.length; itx++) { + var tx = transactions[itx]; + if (tx.acquirer_provider === 'stripe' && tx.state === 'pending' && tx.stripe_payment_intent_secret && !this._authInProgress) { + this._authInProgress = true; + this._stripeAuthenticate(tx); + } + } + }, + }); +}); \ No newline at end of file diff --git a/addons/payment_stripe_sca/static/src/js/stripe_sca.js b/addons/payment_stripe_sca/static/src/js/stripe_sca.js new file mode 100644 index 00000000000..74aac900006 --- /dev/null +++ b/addons/payment_stripe_sca/static/src/js/stripe_sca.js @@ -0,0 +1,82 @@ +odoo.define('payment_stripe.stripe', function (require) { + "use strict"; + + var ajax = require('web.ajax'); + var core = require('web.core'); + + var qweb = core.qweb; + var _t = core._t; + + ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb); + + 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'; + } + + require('web.dom_ready'); + if (!$('.o_payment_form').length) { + return Promise.reject("DOM doesn't contain '.o_payment_form'"); + } + + var observer = new MutationObserver(function (mutations, observer) { + for (var i = 0; i < mutations.length; ++i) { + for (var j = 0; j < mutations[i].addedNodes.length; ++j) { + if (mutations[i].addedNodes[j].tagName.toLowerCase() === "form" && mutations[i].addedNodes[j].getAttribute('provider') === 'stripe') { + _redirectToStripeCheckout($(mutations[i].addedNodes[j])); + } + } + } + }); + + function displayError(message) { + var wizard = $(qweb.render('stripe.error', {'msg': message || _t('Payment error')})); + wizard.appendTo($('body')).modal({'keyboard': true}); + if ($.blockUI) { + $.unblockUI(); + } + $("#o_payment_form_pay").removeAttr('disabled'); + } + + + 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 + + '

' + }); + } + + 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"]')); + }); + }); + \ No newline at end of file diff --git a/addons/payment_stripe_sca/views/assets.xml b/addons/payment_stripe_sca/views/assets.xml new file mode 100644 index 00000000000..6dd7fbab536 --- /dev/null +++ b/addons/payment_stripe_sca/views/assets.xml @@ -0,0 +1,8 @@ + + + diff --git a/addons/payment_stripe_sca/views/payment_templates.xml b/addons/payment_stripe_sca/views/payment_templates.xml new file mode 100644 index 00000000000..0590c2aae22 --- /dev/null +++ b/addons/payment_stripe_sca/views/payment_templates.xml @@ -0,0 +1,42 @@ + + + + + + + + + + \ No newline at end of file