Odoo18-Base/addons/payment_razorpay_oauth/models/payment_provider.py
2025-01-06 10:57:38 +07:00

207 lines
7.5 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import pprint
import uuid
from datetime import timedelta
from urllib.parse import urlencode
import requests
from odoo import _, fields, models
from odoo.exceptions import RedirectWarning, ValidationError
from odoo.http import request
from odoo.addons.payment_razorpay import const
from odoo.addons.payment_razorpay_oauth import const as oauth_const
from odoo.addons.payment_razorpay_oauth.controllers.onboarding import RazorpayController
_logger = logging.getLogger(__name__)
class PaymentProvider(models.Model):
_inherit = 'payment.provider'
razorpay_key_id = fields.Char(required_if_provider=False)
razorpay_key_secret = fields.Char(required_if_provider=False)
razorpay_webhook_secret = fields.Char(required_if_provider=False)
# OAuth fields
razorpay_account_id = fields.Char(string="Razorpay Account ID", groups='base.group_system')
razorpay_refresh_token = fields.Char(
string="Razorpay Refresh Token", groups='base.group_system'
)
razorpay_public_token = fields.Char(string="Razorpay Public Token", groups='base.group_system')
razorpay_access_token = fields.Char(string="Razorpay Access Token", groups='base.group_system')
razorpay_access_token_expiry = fields.Datetime(
string="Razorpay Access Token Expiry", groups='base.group_system'
)
# === ACTION METHODS ===#
def action_razorpay_redirect_to_oauth_url(self):
""" Redirect to the Razorpay OAuth URL.
Note: `self.ensure_one()`
:return: An URL action to redirect to the Razorpay OAuth URL.
:rtype: dict
"""
self.ensure_one()
if self.company_id.currency_id.name not in const.SUPPORTED_CURRENCIES:
raise RedirectWarning(
_(
"Razorpay is not available in your country; please use another payment"
" provider."
),
self.env.ref('payment.action_payment_provider').id,
_("Other Payment Providers"),
)
params = {
'return_url': f'{self.get_base_url()}{RazorpayController.OAUTH_RETURN_URL}',
'provider_id': self.id,
'csrf_token': request.csrf_token(),
}
authorization_url = f'{oauth_const.OAUTH_URL}/authorize?{urlencode(params)}'
return {
'type': 'ir.actions.act_url',
'url': authorization_url,
'target': 'self',
}
def action_razorpay_reset_oauth_account(self):
""" Reset the Razorpay OAuth account.
Note: self.ensure_one()
:return: None
"""
self.ensure_one()
return self.write({
'razorpay_account_id': None,
'razorpay_public_token': None,
'razorpay_refresh_token': None,
'razorpay_access_token': None,
'razorpay_access_token_expiry': None,
'state': 'disabled',
'is_published': False,
})
def action_razorpay_create_webhook(self):
""" Create a webhook and display a toast notification.
Note: `self.ensure_one()`
:return: The feedback notification.
:rtype: dict
"""
self.ensure_one()
webhook_secret = uuid.uuid4().hex # Generate a random webhook secret.
payload = {
'url': f'{self.get_base_url()}/payment/razorpay/webhook',
'alert_email': self.env.user.partner_id.email,
'secret': webhook_secret,
'events': const.HANDLED_WEBHOOK_EVENTS,
}
_logger.info(
"Sending '/accounts/%(account_id)s/webhooks' request:\n%(payload)s",
{'account_id': self.razorpay_account_id, 'payload': pprint.pformat(payload)},
)
webhook_data = self.with_context(razorpay_api_version='v2')._razorpay_make_request(
f'accounts/{self.razorpay_account_id}/webhooks', payload=payload
)
_logger.info(
"Response of '/accounts/%(account_id)s/webhooks' request:\n%(response)s",
{'account_id': self.razorpay_account_id, 'response': pprint.pformat(webhook_data)},
)
self.razorpay_webhook_secret = webhook_secret
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': _("Your Razorpay webhook was successfully set up!"),
'next': {'type': 'ir.actions.client', 'tag': 'soft_reload'},
},
}
# === BUSINESS METHODS - OAUTH === #
def _razorpay_make_proxy_request(self, endpoint, payload=None):
""" Make a request to the Razorpay proxy at the specified endpoint.
:param str endpoint: The proxy endpoint to be reached by the request; prefixed with '/'.
:param dict payload: The payload of the request.
:return The JSON-formatted content of the response.
:rtype: dict
:raise ValidationError: If an HTTP error occurs.
"""
proxy_payload = {
'jsonrpc': '2.0',
'id': uuid.uuid4().hex,
'method': 'call',
'params': payload,
}
url = f'{oauth_const.OAUTH_URL}{endpoint}'
try:
response = requests.post(url, json=proxy_payload, timeout=10)
response.raise_for_status()
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
_logger.exception("Unable to reach endpoint at %s", url)
raise ValidationError("Razorpay Proxy: " + _("Could not establish the connection."))
except requests.exceptions.HTTPError:
_logger.exception(
"Invalid API request at %s with data %s", url, pprint.pformat(payload)
)
raise ValidationError(
"Razorpay Proxy: " + _("An error occurred when communicating with the proxy.")
)
# Razorpay proxy endpoints always respond with HTTP 200 as they implement JSON-RPC 2.0.
response_content = response.json()
if response_content.get('error'): # An exception was raised on the proxy side.
error_message = response_content['error']['data']['message']
_logger.exception("Request forwarded with error: %s", error_message)
raise ValidationError(f"Razorpay Proxy: {error_message}")
return response_content['result']
def _razorpay_get_public_token(self):
self.ensure_one()
return self.razorpay_public_token
def _razorpay_get_access_token(self):
self.ensure_one()
if self.razorpay_access_token and self.razorpay_access_token_expiry < fields.Datetime.now():
self._razorpay_refresh_access_token()
return self.razorpay_access_token
def _razorpay_refresh_access_token(self):
""" Refresh the access token.
Note: `self.ensure_one()`
:return: dict
"""
self.ensure_one()
response_content = self._razorpay_make_proxy_request(
'/refresh_access_token', payload={'refresh_token': self.razorpay_refresh_token}
)
if response_content.get('access_token'):
expiry = fields.Datetime.now() + timedelta(seconds=int(response_content['expires_in']))
self.write({
'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': expiry,
})