"""Client for interacting with UK Government API.""" import json import os import re from marshmallow import validate, ValidationError import requests from payee.config import Config, secrets_manager_client from payee.connectors import sentry from payee.constants.constants import ( HMRC_AUTH_URL, HMRC_CHECK_VAT_URL, HMRC_TOKEN_CACHE_KEY, HMRC_TOKEN_CACHE_TIMEOUT_DELTA, UK_GOV_VAT_PATTERN, ) from payee.constants.error import ( ERROR_HMRC_API_UNREACHABLE, ERROR_VAT_HMRC_API_CHECK_FAILED, ERROR_VAT_UK_VAT_FORMAT, ) from payee.utils.exception import HmrcApiException, NoHmrcCredentialsException from payee.utils.uwsg_cache import get_uwsgi_cache_object def _get_hmrc_token_from_api(): """Get HMRC token from API.""" if not Config.HMRC_API_CLIENT_ID or not Config.HMRC_API_CLIENT_SECRET: return None cache = get_uwsgi_cache_object() if cache.has(HMRC_TOKEN_CACHE_KEY): token = cache.get(HMRC_TOKEN_CACHE_KEY) if token: return token response = requests.post( Config.HMRC_API_URL + HMRC_AUTH_URL, data={ 'client_id': Config.HMRC_API_CLIENT_ID, 'client_secret': Config.HMRC_API_CLIENT_SECRET, 'grant_type': 'client_credentials', 'scope': 'read:vat', }, ) response.raise_for_status() data = response.json() token = data['access_token'] expires_in = data['expires_in'] cache.set(HMRC_TOKEN_CACHE_KEY, token, expires_in - HMRC_TOKEN_CACHE_TIMEOUT_DELTA) return token def _get_hmrc_token_from_secrets() -> str | None: """Get HMRC token from secrets.""" if not Config.HMRC_API_AUTH_TOKEN_ARN: return None return secrets_manager_client.get_secret_value( SecretId=Config.HMRC_API_AUTH_TOKEN_ARN, VersionStage='AWSCURRENT' )['SecretString'] def get_hmrc_token() -> str: """Get HMRC token.""" token = os.environ.get('HMRC_API_AUTH_TOKEN') if not token and Config.ENVIRONMENT in ( Config.QA_ENVIRONMENT, Config.UAT_ENVIRONMENT, Config.PROD_ENVIRONMENT, ): token = _get_hmrc_token_from_secrets() if not token: token = _get_hmrc_token_from_api() if not token: raise NoHmrcCredentialsException() return token def build_hmrc_headers() -> dict: """Build HMRC headers.""" token = get_hmrc_token() return { 'Accept': 'application/vnd.hmrc.2.0+json', 'Authorization': f'Bearer {token}', } def validate_vat_number(vat_number): """Validate a vat number against UK Gov API.""" # 1 stage - marshmallow validate vat with Regexp validate.Regexp(UK_GOV_VAT_PATTERN, error=ERROR_VAT_UK_VAT_FORMAT).__call__( vat_number ) # 2 stage - validate vat API requirest if 1 stage is success try: r = requests.get( Config.HMRC_API_URL + HMRC_CHECK_VAT_URL.format(vat_number=preprocess_vat(vat_number)), headers=build_hmrc_headers(), ) if r.status_code == 400 or r.status_code == 404: raise ValidationError(ERROR_VAT_HMRC_API_CHECK_FAILED) if r.status_code == 200: return True response_text = json.dumps(r.json()) sentry.send_to_sentry( 'API api.service.hmrc.gov.uk Error!', response_text, 'error', 'API api.service.hmrc.gov.uk Error!', ) raise HmrcApiException(response_text) except requests.exceptions.Timeout as e: sentry.send_to_sentry( 'API api.service.hmrc.gov.uk Timeout', 'Timeout', 'error', 'Error! API api.service.hmrc.gov.uk Timeout', ) raise HmrcApiException(ERROR_HMRC_API_UNREACHABLE.format(error=str(e))) except requests.exceptions.TooManyRedirects as e: sentry.send_to_sentry( 'API api.service.hmrc.gov.uk TooManyRedirects', 'Timeout', 'error', 'Error! API api.service.hmrc.gov.uk TooManyRedirects', ) raise HmrcApiException(ERROR_HMRC_API_UNREACHABLE.format(error=str(e))) except requests.exceptions.RequestException as e: sentry.send_to_sentry( 'API api.service.hmrc.gov.uk TooManyRedirects', str(e), 'error', f'Error! Code: {type(e).__name__}, Message, {str(e)}', ) raise HmrcApiException(ERROR_HMRC_API_UNREACHABLE.format(error=str(e))) def preprocess_vat(vat): """Preprocess vat number before sending to API.""" return re.sub(r'^[A-Za-z]{2}', '', str(vat))