Skip to content

Commit

Permalink
[ADD] payment_razorpay_oauth, _*: quick onboarding with payment razorpay
Browse files Browse the repository at this point in the history
_* = payment_razorpay, website_payment

This commit Razorpay payment onboarding form to take advantage
of the Razorpay Connect Onboarding Flow. It integrates the Razorpay
Onboarding using the IAP proxy.

Purpose
=======
Help users easily onboard with Razorpay by using the Razorpay Connect API.

Specification
=============
1.  Connect and  authorized the razorpay account.
    To connect to a sub-merchant's Razorpay account, the application redirects
    the user to a Razorpay-hosted webpage. The user can approve or deny the
    authorisation request on this page.

2.  Get an access token.
    After you obtain an access token, you can use it to access the sub-merchant
    data on Razorpay APIs. The access is controlled based on the scope requested
    for and granted by the user during the authorization process.

3.  Get a refresh token.
    You can use refresh tokens to generate a new access token. If your access
    token expires, you will receive a 4XX response from the API. You can make a
    request using your refresh token to generate a new access token.

4.  Revoke token.
    The API supports token revocation to enhance security and manage access.
    If needed, tokens can be revoked through this mechanism.

5.  Create & update webhook.
    This method is responsible for creating or updating the Razorpay webhook
    associated with the current Odoo instance.The webhook is crucial for
    updating payment states within Odoo when changes occur in Razorpay.

6.  Revoke the application from Razorpay.
    User can initiate the revocation of their application from the Razorpay side
    facilitating a seamless process for application revocation.

task-3537535

closes odoo#188502

X-original-commit: c8c78cc
Signed-off-by: Antoine Vandevenne (anv) <[email protected]>
Signed-off-by: Nikhil Joshi (nikj) <[email protected]>
  • Loading branch information
nikj-odoo committed Nov 26, 2024
1 parent 8acfad7 commit e1b3bdb
Show file tree
Hide file tree
Showing 14 changed files with 630 additions and 7 deletions.
38 changes: 32 additions & 6 deletions addons/payment_razorpay/models/payment_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import pprint

import requests
from werkzeug.urls import url_join

from odoo import _, fields, models
from odoo.exceptions import ValidationError
Expand Down Expand Up @@ -50,7 +49,7 @@ def _compute_feature_support_fields(self):
'support_tokenization': True,
})

# === BUSINESS METHODS ===#
# === BUSINESS METHODS - PAYMENT FLOW === #

def _get_supported_currencies(self):
""" Override of `payment` to return the supported currencies. """
Expand All @@ -75,13 +74,30 @@ def _razorpay_make_request(self, endpoint, payload=None, method='POST'):
"""
self.ensure_one()

url = url_join('https://api.razorpay.com/v1/', endpoint)
auth = (self.razorpay_key_id, self.razorpay_key_secret)
# TODO: Make api_version a kwarg in master.
api_version = self.env.context.get('razorpay_api_version', 'v1')
url = f'https://api.razorpay.com/{api_version}/{endpoint}'
headers = None
if access_token := self._razorpay_get_access_token():
headers = {'Authorization': f'Bearer {access_token}'}
auth = (self.razorpay_key_id, self.razorpay_key_secret) if self.razorpay_key_id else None
try:
if method == 'GET':
response = requests.get(url, params=payload, auth=auth, timeout=10)
response = requests.get(
url,
params=payload,
headers=headers,
auth=auth,
timeout=10,
)
else:
response = requests.post(url, json=payload, auth=auth, timeout=10)
response = requests.post(
url,
json=payload,
headers=headers,
auth=auth,
timeout=10,
)
try:
response.raise_for_status()
except requests.exceptions.HTTPError:
Expand Down Expand Up @@ -129,3 +145,13 @@ def _get_validation_amount(self):
return res

return 1.0

# === BUSINESS METHODS - OAUTH === #

def _razorpay_get_public_token(self): # TODO: remove in master
self.ensure_one()
return None

def _razorpay_get_access_token(self): # TODO: remove in master
self.ensure_one()
return None
1 change: 1 addition & 0 deletions addons/payment_razorpay/models/payment_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def _get_specific_processing_values(self, processing_values):
order_id = self._razorpay_create_order(customer_id)['id']
return {
'razorpay_key_id': self.provider_id.razorpay_key_id,
'razorpay_public_token': self.provider_id._razorpay_get_public_token(),
'razorpay_customer_id': customer_id,
'is_tokenize_request': self.tokenize,
'razorpay_order_id': order_id,
Expand Down
2 changes: 1 addition & 1 deletion addons/payment_razorpay/static/src/js/payment_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ paymentForm.include({
*/
_prepareRazorpayOptions(processingValues) {
return Object.assign({}, processingValues, {
'key': processingValues['razorpay_key_id'],
'key': processingValues['razorpay_public_token'] || processingValues['razorpay_key_id'],
'customer_id': processingValues['razorpay_customer_id'],
'order_id': processingValues['razorpay_order_id'],
'description': processingValues['reference'],
Expand Down
4 changes: 4 additions & 0 deletions addons/payment_razorpay_oauth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import controllers
from . import models
16 changes: 16 additions & 0 deletions addons/payment_razorpay_oauth/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

{
'name': "Razorpay OAuth Integration",
'category': 'Accounting/Payment Providers',
'sequence': 350,
'summary': "Easy Razorpay Onboarding With Oauth.",
'icon': '/payment_razorpay/static/description/icon.png',
'depends': ['payment_razorpay'],
'data': [
'views/payment_provider_views.xml',
'views/razorpay_templates.xml',
],
'auto_install': True,
'license': 'LGPL-3',
}
3 changes: 3 additions & 0 deletions addons/payment_razorpay_oauth/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

OAUTH_URL = 'https://razorpay.api.odoo.com/api/razorpay/1'
3 changes: 3 additions & 0 deletions addons/payment_razorpay_oauth/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import onboarding
85 changes: 85 additions & 0 deletions addons/payment_razorpay_oauth/controllers/onboarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import logging
import pprint
from datetime import timedelta
from urllib.parse import urlencode

from werkzeug.exceptions import Forbidden

from odoo import _, fields
from odoo.exceptions import ValidationError
from odoo.http import Controller, request, route


_logger = logging.getLogger(__name__)


class RazorpayController(Controller):

OAUTH_RETURN_URL = '/payment/razorpay/oauth/return'

@route(OAUTH_RETURN_URL, type='http', auth='user', methods=['GET'], website=True)
def razorpay_return_from_authorization(self, **data):
""" Exchange the authorization code for an access token and redirect to the provider form.
:param dict data: The authorization code received from Razorpay, in addition to the provided
provider id and CSRF token that were sent back by the proxy.
:raise Forbidden: If the received CSRF token cannot be verified.
:raise ValidationError: If the provider id does not match any Razorpay provider.
:return: Redirect to the payment provider form.
"""
_logger.info("Returning from authorization with data:\n%s", pprint.pformat(data))

# Retrieve the Razorpay data and Odoo metadata from the redirect data.
provider_id = int(data['provider_id'])
authorization_code = data['authorization_code']
csrf_token = data['csrf_token']
provider_sudo = request.env['payment.provider'].sudo().browse(provider_id).exists()
if not provider_sudo or provider_sudo.code != 'razorpay':
raise ValidationError(_("Could not find Razorpay provider with id %s", provider_sudo))

# Verify the CSRF token.
if not request.validate_csrf(csrf_token):
_logger.warning("CSRF token verification failed.")
raise Forbidden()

# Request and set the OAuth tokens on the provider.
action = request.env.ref('payment.action_payment_provider')
url_params = {
'model': provider_sudo._name,
'id': provider_sudo.id,
'action': action.id,
'view_type': 'form',
}
redirect_url = f'/web#{urlencode(url_params)}' # TODO: change to /odoo in saas-17.2!
try:
response_content = provider_sudo._razorpay_make_proxy_request(
'/get_access_token', payload={'authorization_code': authorization_code}
)
except ValidationError as e:
return request.render(
'payment_razorpay_oauth.authorization_error',
qcontext={'error_message': str(e), 'provider_url': redirect_url},
)
expires_in = fields.Datetime.now() + timedelta(seconds=int(response_content['expires_in']))
provider_sudo.write({
# Reset the classical API key fields.
'razorpay_key_id': None,
'razorpay_key_secret': None,
'razorpay_webhook_secret': None,
# Set the new OAuth fields.
'razorpay_account_id': response_content['razorpay_account_id'],
'razorpay_public_token': response_content['public_token'],
'razorpay_refresh_token': response_content['refresh_token'],
'razorpay_access_token': response_content['access_token'],
'razorpay_access_token_expiry': expires_in,
# Enable the provider.
'state': 'enabled',
'is_published': True,
})
try:
provider_sudo.action_razorpay_create_webhook()
except ValidationError as error:
_logger.warning(error)
return request.redirect(redirect_url)
7 changes: 7 additions & 0 deletions addons/payment_razorpay_oauth/data/neutralize.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- disable razorpay payment provider
UPDATE payment_provider
SET razorpay_public_token = NULL,
razorpay_refresh_token = NULL,
razorpay_access_token = NULL,
razorpay_access_token_expiry = NULL,
razorpay_account_id = NULL;
168 changes: 168 additions & 0 deletions addons/payment_razorpay_oauth/i18n/payment_razorpay_oauth.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * payment_razorpay_oauth
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-13 15:58+0000\n"
"PO-Revision-Date: 2024-11-13 15:58+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid "Account ID"
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.authorization_error
msgid "An error occurred"
msgstr ""

#. module: payment_razorpay_oauth
#. odoo-python
#: code:addons/payment_razorpay_oauth/models/payment_provider.py:0
#, python-format
msgid "An error occurred when communicating with the proxy."
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.authorization_error
msgid "An error occurred while linking your Razorpay account with Odoo."
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid "Are you sure you want to disconnect?"
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.authorization_error
msgid "Back to the Razorpay provider"
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid "Connect"
msgstr ""

#. module: payment_razorpay_oauth
#. odoo-python
#: code:addons/payment_razorpay_oauth/models/payment_provider.py:0
#, python-format
msgid "Could not establish the connection."
msgstr ""

#. module: payment_razorpay_oauth
#. odoo-python
#: code:addons/payment_razorpay_oauth/controllers/onboarding.py:0
#, python-format
msgid "Could not find Razorpay provider with id %s"
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid "Generate your webhook"
msgstr ""

#. module: payment_razorpay_oauth
#. odoo-python
#: code:addons/payment_razorpay_oauth/models/payment_provider.py:0
#, python-format
msgid "Other Payment Providers"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model,name:payment_razorpay_oauth.model_payment_provider
msgid "Payment Provider"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_access_token
msgid "Razorpay Access Token"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_access_token_expiry
msgid "Razorpay Access Token Expiry"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_account_id
msgid "Razorpay Account ID"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_key_id
msgid "Razorpay Key Id"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_key_secret
msgid "Razorpay Key Secret"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_public_token
msgid "Razorpay Public Token"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_refresh_token
msgid "Razorpay Refresh Token"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,field_description:payment_razorpay_oauth.field_payment_provider__razorpay_webhook_secret
msgid "Razorpay Webhook Secret"
msgstr ""

#. module: payment_razorpay_oauth
#. odoo-python
#: code:addons/payment_razorpay_oauth/models/payment_provider.py:0
#, python-format
msgid ""
"Razorpay is not available in your country; please use another payment "
"provider."
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid "Reset Your Razorpay Account"
msgstr ""

#. module: payment_razorpay_oauth
#: model:ir.model.fields,help:payment_razorpay_oauth.field_payment_provider__razorpay_key_id
msgid "The key solely used to identify the account with Razorpay."
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid "This provider is linked with your Razorpay account."
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid "Webhook Secret"
msgstr ""

#. module: payment_razorpay_oauth
#: model_terms:ir.ui.view,arch_db:payment_razorpay_oauth.payment_provider_form_razorpay_oauth
msgid ""
"You are currently connected to Razorpay through the credentials method, which is\n"
" deprecated. Click the \"Connect\" button below to use the recommended OAuth\n"
" method."
msgstr ""

#. module: payment_razorpay_oauth
#. odoo-python
#: code:addons/payment_razorpay_oauth/models/payment_provider.py:0
#, python-format
msgid "Your Razorpay webhook was successfully set up!"
msgstr ""
3 changes: 3 additions & 0 deletions addons/payment_razorpay_oauth/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from . import payment_provider
Loading

0 comments on commit e1b3bdb

Please sign in to comment.