diff --git a/addons/payment/data/payment_method_data.xml b/addons/payment/data/payment_method_data.xml
index b1d209c75c4..7610bec608b 100644
--- a/addons/payment/data/payment_method_data.xml
+++ b/addons/payment/data/payment_method_data.xml
@@ -3150,7 +3150,7 @@
1000
False
- False
+ True
False
partial
\n"
+" Enable recurring payments on Razorpay"
+msgstr ""
+
#. module: payment_razorpay
#. odoo-python
#: code:addons/payment_razorpay/models/payment_transaction.py:0
@@ -62,6 +69,13 @@ msgstr ""
msgid "Payment Transaction"
msgstr ""
+#. module: payment_razorpay
+#. odoo-javascript
+#: code:addons/payment_razorpay/static/src/js/payment_form.js:0
+#, python-format
+msgid "Payment processing failed"
+msgstr ""
+
#. module: payment_razorpay
#: model:ir.model.fields.selection,name:payment_razorpay.selection__payment_provider__code__razorpay
msgid "Razorpay"
@@ -82,6 +96,13 @@ msgstr ""
msgid "Razorpay Webhook Secret"
msgstr ""
+#. module: payment_razorpay
+#. odoo-python
+#: code:addons/payment_razorpay/models/payment_provider.py:0
+#, python-format
+msgid "Razorpay gave us the following information: '%s'"
+msgstr ""
+
#. module: payment_razorpay
#. odoo-python
#: code:addons/payment_razorpay/models/payment_transaction.py:0
@@ -118,12 +139,8 @@ msgid "Received incomplete refund data."
msgstr ""
#. module: payment_razorpay
-#. odoo-python
-#: code:addons/payment_razorpay/models/payment_provider.py:0
-#, python-format
-msgid ""
-"The communication with the API failed. Razorpay gave us the following "
-"information: '%s'"
+#: model_terms:ir.ui.view,arch_db:payment_razorpay.redirect_form
+msgid "TODO: remove in master"
msgstr ""
#. module: payment_razorpay
@@ -131,6 +148,13 @@ msgstr ""
msgid "The key solely used to identify the account with Razorpay."
msgstr ""
+#. module: payment_razorpay
+#. odoo-python
+#: code:addons/payment_razorpay/models/payment_transaction.py:0
+#, python-format
+msgid "The phone number is invalid."
+msgstr ""
+
#. module: payment_razorpay
#. odoo-python
#: code:addons/payment_razorpay/models/payment_transaction.py:0
@@ -143,6 +167,13 @@ msgstr ""
msgid "The technical code of this payment provider."
msgstr ""
+#. module: payment_razorpay
+#. odoo-python
+#: code:addons/payment_razorpay/models/payment_transaction.py:0
+#, python-format
+msgid "The transaction is not linked to a token."
+msgstr ""
+
#. module: payment_razorpay
#. odoo-python
#: code:addons/payment_razorpay/models/payment_transaction.py:0
diff --git a/addons/payment_razorpay/models/payment_provider.py b/addons/payment_razorpay/models/payment_provider.py
index 2283ebb4fa5..120a2cdc0a5 100644
--- a/addons/payment_razorpay/models/payment_provider.py
+++ b/addons/payment_razorpay/models/payment_provider.py
@@ -47,6 +47,7 @@ class PaymentProvider(models.Model):
self.filtered(lambda p: p.code == 'razorpay').update({
'support_manual_capture': 'full_only',
'support_refund': 'partial',
+ 'support_tokenization': True,
})
# === BUSINESS METHODS ===#
@@ -88,8 +89,8 @@ class PaymentProvider(models.Model):
"Invalid API request at %s with data:\n%s", url, pprint.pformat(payload),
)
raise ValidationError("Razorpay: " + _(
- "The communication with the API failed. Razorpay gave us the following "
- "information: '%s'", response.json().get('error', {}).get('description')
+ "Razorpay gave us the following information: '%s'",
+ response.json().get('error', {}).get('description')
))
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
_logger.exception("Unable to reach endpoint at %s", url)
@@ -126,3 +127,15 @@ class PaymentProvider(models.Model):
if self.code != 'razorpay':
return default_codes
return const.DEFAULT_PAYMENT_METHODS_CODES
+
+ def _get_validation_amount(self):
+ """ Override of `payment` to return the amount for Razorpay validation operations.
+
+ :return: The validation amount.
+ :rtype: float
+ """
+ res = super()._get_validation_amount()
+ if self.code != 'razorpay':
+ return res
+
+ return 1.0
diff --git a/addons/payment_razorpay/models/payment_transaction.py b/addons/payment_razorpay/models/payment_transaction.py
index d77dd659d2e..07c9da19a9c 100644
--- a/addons/payment_razorpay/models/payment_transaction.py
+++ b/addons/payment_razorpay/models/payment_transaction.py
@@ -2,15 +2,16 @@
import logging
import pprint
+import time
+from datetime import datetime
-from werkzeug.urls import url_encode, url_join
+from dateutil.relativedelta import relativedelta
-from odoo import _, models
+from odoo import _, api, models
from odoo.exceptions import UserError, ValidationError
from odoo.addons.payment import utils as payment_utils
from odoo.addons.payment_razorpay import const
-from odoo.addons.payment_razorpay.controllers.main import RazorpayController
_logger = logging.getLogger(__name__)
@@ -19,76 +20,121 @@ _logger = logging.getLogger(__name__)
class PaymentTransaction(models.Model):
_inherit = 'payment.transaction'
- def _get_specific_rendering_values(self, processing_values):
- """ Override of `payment` to return razorpay-specific rendering values.
+ def _get_specific_processing_values(self, processing_values):
+ """ Override of `payment` to return razorpay-specific processing values.
Note: self.ensure_one() from `_get_processing_values`
:param dict processing_values: The generic and specific processing values of the
transaction.
- :return: The dict of provider-specific rendering values.
+ :return: The provider-specific processing values.
:rtype: dict
"""
- res = super()._get_specific_rendering_values(processing_values)
+ res = super()._get_specific_processing_values(processing_values)
if self.provider_code != 'razorpay':
return res
- # Initiate the payment and retrieve the related order id.
- payload = self._razorpay_prepare_order_request_payload()
+ customer_id = self._razorpay_create_customer()['id']
+ order_id = self._razorpay_create_order(customer_id)['id']
+ return {
+ 'razorpay_key_id': self.provider_id.razorpay_key_id,
+ 'razorpay_customer_id': customer_id,
+ 'is_tokenize_request': self.tokenize,
+ 'razorpay_order_id': order_id,
+ }
+
+ def _razorpay_create_customer(self):
+ """ Create and return a Customer object.
+
+ :return: The created Customer.
+ :rtype: dict
+ """
+ payload = {
+ 'name': self.partner_name,
+ 'email': self.partner_email,
+ 'contact': self._validate_phone_number(self.partner_phone),
+ 'fail_existing': '0', # Don't throw an error if the customer already exists.
+ }
_logger.info(
- "Payload of '/orders' request for transaction with reference %s:\n%s",
+ "Sending '/customers' request for transaction with reference %s:\n%s",
self.reference, pprint.pformat(payload)
)
- order_data = self.provider_id._razorpay_make_request(endpoint='orders', payload=payload)
+ customer_data = self.provider_id._razorpay_make_request('customers', payload=payload)
+ _logger.info(
+ "Response of '/customers' request for transaction with reference %s:\n%s",
+ self.reference, pprint.pformat(customer_data)
+ )
+ return customer_data
+
+ @api.model
+ def _validate_phone_number(self, phone):
+ """ Validate and format the phone number.
+
+ :param str phone: The phone number to validate.
+ :return str: The formatted phone number.
+ :raise ValidationError: If the phone number is missing or incorrect.
+ """
+ if not phone:
+ raise ValidationError("Razorpay: " + _("The phone number is missing."))
+
+ try:
+ phone = self._phone_format(
+ number=phone, country=self.partner_country_id, raise_exception=True
+ )
+ except Exception:
+ raise ValidationError("Razorpay: " + _("The phone number is invalid."))
+ return phone
+
+ def _razorpay_create_order(self, customer_id=None):
+ """ Create and return an Order object to initiate the payment.
+
+ :param str customer_id: The ID of the Customer object to assign to the Order for
+ non-subsequent payments.
+ :return: The created Order.
+ :rtype: dict
+ """
+ payload = self._razorpay_prepare_order_payload(customer_id=customer_id)
+ _logger.info(
+ "Sending '/orders' request for transaction with reference %s:\n%s",
+ self.reference, pprint.pformat(payload)
+ )
+ order_data = self.provider_id._razorpay_make_request('orders', payload=payload)
_logger.info(
"Response of '/orders' request for transaction with reference %s:\n%s",
self.reference, pprint.pformat(order_data)
)
+ return order_data
- # Initiate the payment
- converted_amount = payment_utils.to_minor_currency_units(self.amount, self.currency_id)
- base_url = self.provider_id.get_base_url()
- return_url_params = {'reference': self.reference}
-
- phone = self.partner_phone
- if phone:
- # sanitize partner phone
- try:
- phone = self._phone_format(number=phone, country=self.partner_country_id, raise_exception=True)
- except Exception as err:
- raise ValidationError("Razorpay: " + str(err)) from err
- else:
- raise ValidationError("Razorpay: " + _("The phone number is missing."))
-
- rendering_values = {
- 'key_id': self.provider_id.razorpay_key_id,
- 'name': self.company_id.name,
- 'description': self.reference,
- 'company_logo': url_join(base_url, f'web/image/res.company/{self.company_id.id}/logo'),
- 'order_id': order_data['id'],
- 'amount': converted_amount,
- 'currency': self.currency_id.name,
- 'partner_name': self.partner_name,
- 'partner_email': self.partner_email,
- 'partner_phone': phone,
- 'method': self.payment_method_code,
- 'return_url': url_join(
- base_url, f'{RazorpayController._return_url}?{url_encode(return_url_params)}'
- ),
- }
- return rendering_values
-
- def _razorpay_prepare_order_request_payload(self):
- """ Create the payload for the order request based on the transaction values.
+ def _razorpay_prepare_order_payload(self, customer_id=None):
+ """ Prepare the payload for the order request based on the transaction values.
+ :param str customer_id: The ID of the Customer object to assign to the Order for
+ non-subsequent payments.
:return: The request payload.
:rtype: dict
"""
converted_amount = payment_utils.to_minor_currency_units(self.amount, self.currency_id)
+ pm_code = (self.payment_method_id.primary_payment_method_id or self.payment_method_id).code
payload = {
'amount': converted_amount,
'currency': self.currency_id.name,
+ 'method': pm_code,
}
+ if self.operation in ['online_direct', 'validation']:
+ payload['customer_id'] = customer_id # Required for only non-subsequent payments.
+ if self.tokenize:
+ payload['token'] = {
+ 'max_amount': payment_utils.to_minor_currency_units(
+ self._get_mandate_max_amount(), self.currency_id
+ ),
+ 'expire_at': time.mktime(
+ (datetime.today() + relativedelta(years=10)).timetuple()
+ ), # Don't expire the token before at least 10 years.
+ 'frequency': 'as_presented',
+ }
+ else: # 'online_token', 'offline'
+ # Required for only subsequent payments.
+ payload['payment_capture'] = not self.provider_id.capture_manually
if self.provider_id.capture_manually: # The related payment must be only authorized.
payload.update({
'payment': {
@@ -101,6 +147,70 @@ class PaymentTransaction(models.Model):
})
return payload
+ def _get_mandate_max_amount(self):
+ """ Return the eMandate's maximum amount to define.
+
+ :return: The eMandate's maximum amount.
+ :rtype: int
+ """
+ mandate_values = self._get_mandate_values()
+ if 'amount' in mandate_values:
+ max_amount = mandate_values['amount'] * 5 # FP's rule of thumb for a good max amount.
+ else:
+ pm_code = (
+ self.payment_method_id.primary_payment_method_id or self.payment_method_id
+ ).code
+ max_amount = const.MANDATE_MAX_AMOUNT.get(pm_code, 100000)
+ return max_amount
+
+ def _send_payment_request(self):
+ """ Override of `payment` to send a payment request to Razorpay.
+
+ Note: self.ensure_one()
+
+ :return: None
+ :raise UserError: If the transaction is not linked to a token.
+ """
+ super()._send_payment_request()
+ if self.provider_code != 'razorpay':
+ return
+
+ if not self.token_id:
+ raise UserError("Razorpay: " + _("The transaction is not linked to a token."))
+
+ try:
+ order_data = self._razorpay_create_order()
+ phone = self._validate_phone_number(self.partner_phone)
+ customer_id, token_id = self.token_id.provider_ref.split(',')
+ payload = {
+ 'email': self.partner_email,
+ 'contact': phone,
+ 'amount': order_data['amount'],
+ 'currency': self.currency_id.name,
+ 'order_id': order_data['id'],
+ 'customer_id': customer_id,
+ 'token': token_id,
+ 'description': self.reference,
+ 'recurring': '1',
+ }
+ _logger.info(
+ "Sending '/payments/create/recurring' request for transaction with reference %s:\n%s",
+ self.reference, pprint.pformat(payload)
+ )
+ recurring_payment_data = self.provider_id._razorpay_make_request(
+ 'payments/create/recurring', payload=payload
+ )
+ _logger.info(
+ "Response of '/payments/create/recurring' request for transaction with reference "
+ "%s:\n%s", self.reference, pprint.pformat(recurring_payment_data)
+ )
+ self._handle_notification_data('razorpay', recurring_payment_data)
+ except ValidationError as e:
+ if self.operation == 'offline':
+ self._set_error(str(e))
+ else:
+ raise
+
def _send_refund_request(self, amount_to_refund=None):
""" Override of `payment` to send a refund request to Razorpay.
@@ -286,6 +396,8 @@ class PaymentTransaction(models.Model):
elif entity_status in const.PAYMENT_STATUS_MAPPING['authorized']:
self._set_authorized()
elif entity_status in const.PAYMENT_STATUS_MAPPING['done']:
+ if self.tokenize:
+ self._razorpay_tokenize_from_notification_data(notification_data)
self._set_done()
# Immediately post-process the transaction if it is a refund, as the post-processing
@@ -308,3 +420,41 @@ class PaymentTransaction(models.Model):
self._set_error(
"Razorpay: " + _("Received data with invalid status: %s", entity_status)
)
+
+ def _razorpay_tokenize_from_notification_data(self, notification_data):
+ """ Create a new token based on the notification data.
+
+ :param dict notification_data: The notification data built with Razorpay objects.
+ See `_process_notification_data`.
+ :return: None
+ """
+ pm_code = (self.payment_method_id.primary_payment_method_id or self.payment_method_id).code
+ if pm_code == 'card':
+ details = notification_data.get('card', {}).get('last4')
+ elif pm_code == 'upi':
+ temp_vpa = notification_data.get('vpa')
+ details = temp_vpa[temp_vpa.find('@') - 1:]
+ else:
+ details = pm_code
+
+ token = self.env['payment.token'].create({
+ 'provider_id': self.provider_id.id,
+ 'payment_method_id': self.payment_method_id.id,
+ 'payment_details': details,
+ 'partner_id': self.partner_id.id,
+ # Razorpay requires both the customer ID and the token ID which are extracted from here.
+ 'provider_ref': f'{notification_data["customer_id"]},{notification_data["token_id"]}',
+ })
+ self.write({
+ 'token_id': token,
+ 'tokenize': False,
+ })
+ _logger.info(
+ "Created token with id %(token_id)s for partner with id %(partner_id)s from "
+ "transaction with reference %(ref)s",
+ {
+ 'token_id': token.id,
+ 'partner_id': self.partner_id.id,
+ 'ref': self.reference,
+ },
+ )
diff --git a/addons/payment_razorpay/static/src/js/payment_form.js b/addons/payment_razorpay/static/src/js/payment_form.js
new file mode 100644
index 00000000000..c09d2adef64
--- /dev/null
+++ b/addons/payment_razorpay/static/src/js/payment_form.js
@@ -0,0 +1,84 @@
+/** @odoo-module **/
+/* global Razorpay */
+
+import { _t } from "@web/core/l10n/translation";
+import { loadJS } from "@web/core/assets";
+import paymentForm from '@payment/js/payment_form';
+
+paymentForm.include({
+
+ // #=== DOM MANIPULATION ===#
+
+ /**
+ * Update the payment context to set the flow to 'direct'.
+ *
+ * @override method from @payment/js/payment_form
+ * @private
+ * @param {number} providerId - The id of the selected payment option's provider.
+ * @param {string} providerCode - The code of the selected payment option's provider.
+ * @param {number} paymentOptionId - The id of the selected payment option
+ * @param {string} paymentMethodCode - The code of the selected payment method, if any.
+ * @param {string} flow - The online payment flow of the selected payment option.
+ * @return {void}
+ */
+ async _prepareInlineForm(providerId, providerCode, paymentOptionId, paymentMethodCode, flow) {
+ if (providerCode !== 'razorpay') {
+ this._super(...arguments);
+ return;
+ }
+
+ if (flow === 'token') {
+ return; // No need to update the flow for tokens.
+ }
+
+ // Overwrite the flow of the select payment method.
+ this._setPaymentFlow('direct');
+ },
+
+ // #=== PAYMENT FLOW ===#
+
+ async _processDirectFlow(providerCode, paymentOptionId, paymentMethodCode, processingValues) {
+ if (providerCode !== 'razorpay') {
+ this._super(...arguments);
+ return;
+ }
+ const razorpayOptions = this._prepareRazorpayOptions(processingValues);
+ await loadJS('https://checkout.razorpay.com/v1/checkout.js');
+ const RazorpayJS = Razorpay(razorpayOptions);
+ RazorpayJS.open();
+ RazorpayJS.on('payment.failed', response => {
+ this._displayErrorDialog(_t("Payment processing failed"), response.error.description);
+ });
+ },
+
+ /**
+ * Prepare the options to init the RazorPay SDK Object.
+ *
+ * @param {object} processingValues - The processing values.
+ * @return {object}
+ */
+ _prepareRazorpayOptions(processingValues) {
+ return Object.assign({}, processingValues, {
+ 'key': processingValues['razorpay_key_id'],
+ 'order_id': processingValues['razorpay_order_id'],
+ 'customer_id': processingValues['razorpay_customer_id'],
+ 'description': processingValues['reference'],
+ 'recurring': processingValues['is_tokenize_request'] ? '1': '0',
+ 'handler': response => {
+ if (
+ response['razorpay_payment_id']
+ && response['razorpay_order_id']
+ && response['razorpay_signature']
+ ) { // The payment reached a final state; redirect to the status page.
+ window.location = '/payment/status';
+ }
+ },
+ 'modal': {
+ 'ondismiss': () => {
+ window.location.reload();
+ }
+ },
+ });
+ },
+
+});
diff --git a/addons/payment_razorpay/tests/common.py b/addons/payment_razorpay/tests/common.py
index 28394c508a2..4be7dcb86fe 100644
--- a/addons/payment_razorpay/tests/common.py
+++ b/addons/payment_razorpay/tests/common.py
@@ -1,6 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.payment.tests.common import PaymentCommon
+from odoo.fields import Command
class RazorpayCommon(PaymentCommon):
@@ -13,8 +14,12 @@ class RazorpayCommon(PaymentCommon):
'razorpay_key_id': 'rzp_123',
'razorpay_key_secret': 'Y63AyP9eL91',
'razorpay_webhook_secret': 'coincoin_motherducker',
+ 'payment_method_ids': [Command.set([cls.env.ref('payment.payment_method_card').id])],
})
+ cls.razorpay_customer_id = 'cust_123'
+ cls.razorpay_token_id = 'token_404'
+
cls.payment_id = 'pay_123'
cls.refund_id = 'rfd_456'
cls.order_id = 'order_789'
@@ -23,11 +28,17 @@ class RazorpayCommon(PaymentCommon):
'razorpay_order_id': cls.order_id,
'razorpay_signature': 'dummy',
}
+ cls.payment_method_id = cls.provider.payment_method_ids[:1].id
cls.payment_data = {
'id': cls.payment_id,
'description': cls.reference,
'status': 'captured',
}
+ cls.tokenize_payment_data = {
+ **cls.payment_data,
+ 'customer_id': cls.razorpay_customer_id,
+ 'token_id': cls.razorpay_token_id,
+ }
cls.refund_data = {
'id': cls.refund_id,
'payment_id': cls.payment_id,
diff --git a/addons/payment_razorpay/tests/test_payment_transaction.py b/addons/payment_razorpay/tests/test_payment_transaction.py
index 91e3834cb8a..81f56ce699c 100644
--- a/addons/payment_razorpay/tests/test_payment_transaction.py
+++ b/addons/payment_razorpay/tests/test_payment_transaction.py
@@ -1,5 +1,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import time
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
from unittest.mock import patch
from odoo.exceptions import UserError
@@ -14,13 +19,15 @@ class TestPaymentTransaction(RazorpayCommon):
def test_no_item_missing_from_order_request_payload(self):
""" Test that the request values are conform to the transaction fields. """
- tx = self._create_transaction('redirect')
- request_payload = tx._razorpay_prepare_order_request_payload()
+ tx = self._create_transaction('redirect', operation='online_direct', payment_method_id=self.payment_method_id)
+ request_payload = tx._razorpay_prepare_order_payload(customer_id=self.razorpay_customer_id)
self.maxDiff = 10000 # Allow comparing large dicts.
converted_amount = payment_utils.to_minor_currency_units(tx.amount, tx.currency_id)
self.assertDictEqual(request_payload, {
'amount': converted_amount,
'currency': tx.currency_id.name,
+ 'customer_id': self.razorpay_customer_id,
+ 'method': 'card',
})
def test_void_is_not_supported(self):
@@ -68,3 +75,44 @@ class TestPaymentTransaction(RazorpayCommon):
):
tx._process_notification_data(self.payment_data)
self.assertEqual(tx.state, 'done')
+
+ def test_order_request_payload_for_tokenize_tx(self):
+ """ Test that order payload for tokenize tx is proper. """
+ tx = self._create_transaction('redirect', operation='online_direct', tokenize=True, payment_method_id=self.payment_method_id)
+ self.assertDictEqual(tx._get_specific_rendering_values(None), {}, "Should return empty dict of rendering values for tokenize transaction")
+
+ request_payload = tx._razorpay_prepare_order_payload(customer_id=self.razorpay_customer_id)
+ converted_amount = payment_utils.to_minor_currency_units(tx.amount, tx.currency_id)
+ token_expiry_date = datetime.today() + relativedelta(years=10)
+ token_expiry_timeslamp = time.mktime(token_expiry_date.timetuple())
+ self.assertDictEqual(request_payload, {
+ 'token': {
+ "expire_at": token_expiry_timeslamp,
+ "frequency": "as_presented",
+ 'max_amount': 50000000,
+ },
+ 'amount': converted_amount,
+ 'currency': tx.currency_id.name,
+ 'customer_id': self.razorpay_customer_id,
+ 'method': 'card',
+
+ })
+
+ def test_processing_notification_data_confirms_tokenize_transaction(self):
+ """ Test that the transaction state is set to 'done' when the notification data indicate a
+ successful payment. """
+ tx = self._create_transaction('redirect', tokenize=True, payment_method_id=self.payment_method_id)
+ tx._process_notification_data(self.tokenize_payment_data)
+ self.assertEqual(tx.state, 'done')
+
+ def test_token_creation_for_tokenize_transaction(self):
+ """ Test that the token is create on confirmation of tokenize transaction """
+ tx = self._create_transaction('redirect', tokenize=True, payment_method_id=self.payment_method_id)
+ tx._process_notification_data(self.tokenize_payment_data)
+ token = tx.token_id
+ self.assertTrue(token, "Should create token for tokenize transction")
+ self.assertFalse(tx.tokenize, "Trasection should be non tokenize after token creation")
+ self.assertEqual(
+ token.provider_ref, f"{self.tokenize_payment_data['customer_id']},{self.tokenize_payment_data['token_id']}",
+ "Should set proper values for provider_ref to get customer_id and token_id from that field"
+ )
diff --git a/addons/payment_razorpay/tests/test_processing_flows.py b/addons/payment_razorpay/tests/test_processing_flows.py
index d2263428aea..e84ea5560c3 100644
--- a/addons/payment_razorpay/tests/test_processing_flows.py
+++ b/addons/payment_razorpay/tests/test_processing_flows.py
@@ -15,22 +15,6 @@ from odoo.addons.payment_razorpay.tests.common import RazorpayCommon
@tagged('post_install', '-at_install')
class TestProcessingFlows(RazorpayCommon, PaymentHttpCommon):
- @mute_logger('odoo.addons.payment_razorpay.controllers.main')
- def test_redirect_notification_triggers_processing(self):
- """ Test that receiving a redirect notification triggers the processing of the notification
- data. """
- self._create_transaction('redirect')
- url = self._build_url(f'{RazorpayController._return_url}?reference={self.reference}')
- with patch(
- 'odoo.addons.payment_razorpay.controllers.main.RazorpayController'
- '._verify_notification_signature'
- ), patch(
- 'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
- '._handle_notification_data'
- ) as handle_notification_data_mock:
- self._make_http_post_request(url, data=self.redirect_notification_data)
- self.assertEqual(handle_notification_data_mock.call_count, 1)
-
@mute_logger('odoo.addons.payment_razorpay.controllers.main')
def test_webhook_notification_triggers_processing(self):
""" Test that receiving a valid webhook notification triggers the processing of the
@@ -48,19 +32,20 @@ class TestProcessingFlows(RazorpayCommon, PaymentHttpCommon):
self.assertEqual(handle_notification_data_mock.call_count, 1)
@mute_logger('odoo.addons.payment_razorpay.controllers.main')
- def test_redirect_notification_triggers_signature_check(self):
- """ Test that receiving a redirect notification triggers a signature check. """
- self._create_transaction('redirect')
- url = self._build_url(f'{RazorpayController._return_url}?reference={self.reference}')
+ def test_webhook_notification_triggers_processing_for_tokenize_transaction(self):
+ """ Test that receiving a valid webhook notification triggers the processing of the
+ notification data for tokenize transaction. """
+ self._create_transaction('redirect', tokenize=True, payment_method_id=self.payment_method_id)
+ url = self._build_url(RazorpayController._webhook_url)
with patch(
- 'odoo.addons.payment_razorpay.controllers.main.RazorpayController'
- '._verify_notification_signature'
- ) as signature_check_mock, patch(
+ 'odoo.addons.payment_razorpay.controllers.main.RazorpayController.'
+ '_verify_notification_signature'
+ ), patch(
'odoo.addons.payment.models.payment_transaction.PaymentTransaction'
'._handle_notification_data'
- ):
- self._make_http_post_request(url, data=self.redirect_notification_data)
- self.assertEqual(signature_check_mock.call_count, 1)
+ ) as handle_notification_data_mock:
+ self._make_json_request(url, data=self.webhook_notification_data)
+ self.assertEqual(handle_notification_data_mock.call_count, 1)
@mute_logger('odoo.addons.payment_razorpay.controllers.main')
def test_webhook_notification_triggers_signature_check(self):
diff --git a/addons/payment_razorpay/views/payment_provider_views.xml b/addons/payment_razorpay/views/payment_provider_views.xml
index 3c95a70191e..225840cd2fa 100644
--- a/addons/payment_razorpay/views/payment_provider_views.xml
+++ b/addons/payment_razorpay/views/payment_provider_views.xml
@@ -22,6 +22,18 @@
password="True"/>
+
+
+
diff --git a/addons/payment_razorpay/views/payment_razorpay_templates.xml b/addons/payment_razorpay/views/payment_razorpay_templates.xml
index e7769b90600..bf25c9d0497 100644
--- a/addons/payment_razorpay/views/payment_razorpay_templates.xml
+++ b/addons/payment_razorpay/views/payment_razorpay_templates.xml
@@ -2,21 +2,7 @@
-
+ TODO: remove in master