Files
enterprise-addons/l10n_us_direct_deposit/models/wise_request.py
T

183 lines
7.8 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import logging
import requests
_logger = logging.getLogger(__name__)
class Wise:
def __init__(self, company):
self.url = "https://api.wise-sandbox.com" if company.sudo().wise_environment == 'sandbox' else "https://api.wise.com"
self.__session = requests.Session()
self.token = company.sudo().wise_api_key
self.debug_logger = company._log_external_wise_request
self.profile_id = company.sudo().wise_profile_identifier
def __make_api_request(self, method, endpoint, data=None):
headers = {
'Content-Type': "application/json",
'Accept': "application/json",
'Authorization': f"Bearer {self.token}",
}
access_url = self.url + endpoint
try:
# Make the API request
response = self.__session.request(method=method, url=access_url, json=data, headers=headers, timeout=30)
# Parse the response as JSON
response_json = response.json()
# Log the details for debugging purposes
self.debug_logger(
f"{access_url} ({method}) {response.status_code}\n\n"
f"request={json.dumps(data, indent=2)}\n\n"
f"response={json.dumps(response_json, indent=2)}\n",
f"_make_api_request ({endpoint})",
)
return response_json
except requests.exceptions.ConnectionError as error:
_logger.warning('Connection Error: %s with the given URL: %s', error, access_url)
return {'error': {'description': 'timeout', 'message': "Cannot reach the server. Please try again later."}}
except requests.exceptions.ReadTimeout as error:
_logger.warning('Timeout Error: %s with the given URL: %s', error, access_url)
return {'error': {'description': 'timeout', 'message': "Cannot reach the server. Please try again later."}}
except json.decoder.JSONDecodeError as error:
_logger.warning('JSONDecodeError: %s', error)
return {'error': {'description': 'JSONDecodeError', 'message': str(error)}}
except Exception as error: # noqa: BLE001
_logger.warning('UnknownException: %s', error)
return {'error': {'description': 'Exception', 'message': str(error)}}
########################
# Error Handling Methods
########################
def has_errors(self, response):
""" Wise stores all error messages in one of three keys: errors, error, or errorCode """
return not response or (isinstance(response, dict) and any(key in response for key in ('errors', 'error', 'errorCode')))
def format_errors(self, error_response):
if not isinstance(error_response, dict):
return "An unexpected error occurred while processing your request."
if "errors" in error_response and isinstance(error_response["errors"], list):
error_messages = []
for error in error_response["errors"]:
if isinstance(error, dict):
message = error.get("message", "Unknown validation error")
path = error.get("path")
arguments = error.get("arguments")
# Build the error message
if path:
error_msg = f"- {path}: {message}"
else:
error_msg = f"- {message}"
# Add arguments information if present and useful
if arguments and isinstance(arguments, list):
# Filter out the field name if it's already in the path to avoid redundancy
filtered_args = [arg for arg in arguments if arg != path]
if filtered_args:
args_str = ", ".join(str(arg) for arg in filtered_args)
error_msg += f" (Value: {args_str})"
error_messages.append(error_msg)
if error_messages:
return "\n".join(error_messages)
return "Validation errors occurred but no details were provided."
if "status" in error_response and "error" in error_response:
status = error_response.get("status", "Unknown")
error_type = error_response.get("error", "error")
message = error_response.get("message", "No additional details provided")
return f"API Error {status}: {error_type.title()}\n{message}"
if "error_description" in error_response:
error_type = error_response.get("error", "error")
message = error_response.get("error_description", "No additional details provided")
return f"API Error: {error_type.title()}\n{message}"
if "type" in error_response and "status" in error_response:
error_type = error_response.get("type", "Unknown")
status = error_response.get("status", "Unknown")
# Build the message
base_message = f"{error_type} operation {status.lower()}"
if error_message := error_response.get("errorMessage"):
return f"{base_message}: {error_message}"
if error_code := error_response.get("errorCode"):
# Convert error code to readable format
readable_code = error_code.replace(".", " ").replace("-", " ").title()
return f"{base_message}: {readable_code}"
return base_message
return "An unexpected API error occurred. Please contact support if the issue persists."
########################
# Profile Methods
########################
def get_profile(self, profile_type="business"):
"""Get user profile from Wise"""
return self.__make_api_request('GET', '/v1/profiles')
########################
# Quote Methods
########################
def create_quote(self, quote_data):
"""Create quote"""
profile_id = self.profile_id
return self.__make_api_request('POST', f'/v3/profiles/{profile_id}/quotes', quote_data)
########################
# Recipient Methods
########################
def create_recipient(self, recipient_data):
"""Create recipient"""
return self.__make_api_request('POST', '/v1/accounts', recipient_data)
def get_recipients(self, recipient_id=False):
"""Get recipients"""
if recipient_id:
url = f'/v2/accounts/{recipient_id}'
else:
url = '/v2/accounts'
return self.__make_api_request('GET', url)
########################
# Batch Group Methods
########################
def get_batch_group(self, batch_group_id):
profile_id = self.profile_id
return self.__make_api_request('GET', f'/v3/profiles/{profile_id}/batch-groups/{batch_group_id}')
def create_batch_group(self, currency, batch_name):
"""Create batch group"""
profile_id = self.profile_id
batch_data = {
'sourceCurrency': currency,
'name': batch_name,
}
return self.__make_api_request('POST', f'/v3/profiles/{profile_id}/batch-groups', batch_data)
def create_transfer_in_batch(self, batch_group_id, transfer_data):
"""Create transfer in batch group"""
profile_id = self.profile_id
return self.__make_api_request('POST', f'/v3/profiles/{profile_id}/batch-groups/{batch_group_id}/transfers', transfer_data)
def complete_batch_group(self, batch_group_id, batch_version):
"""Complete batch group"""
profile_id = self.profile_id
batch_data = {
'status': 'COMPLETED',
'version': batch_version,
}
return self.__make_api_request('PATCH', f'/v3/profiles/{profile_id}/batch-groups/{batch_group_id}', batch_data)