Skip to content

Commit

Permalink
Merge pull request #74 from cuenca-mx/bootcamp_rogelio
Browse files Browse the repository at this point in the history
Bootcamp rogelio
  • Loading branch information
Ricardo committed Jan 13, 2020
2 parents 34779cc + 6ce86b2 commit 33d69ba
Show file tree
Hide file tree
Showing 24 changed files with 1,420 additions and 70 deletions.
3 changes: 2 additions & 1 deletion Makefile
Expand Up @@ -23,9 +23,10 @@ format:
$(black)

lint:
flake8 $(PROJECT) tests setup.py
$(isort) --check-only
$(black) --check
flake8 $(PROJECT) tests setup.py
mypy $(PROJECT) tests

clean:
find . -name '*.pyc' -exec rm -f {} +
Expand Down
17 changes: 9 additions & 8 deletions arcus/auth.py
Expand Up @@ -3,24 +3,25 @@
from base64 import b64encode
from datetime import datetime
from hashlib import md5, sha1
from typing import Tuple

import pytz

CONTENT_TYPE = 'application/json'


def compute_md5_header(data: dict) -> tuple:
data = json.dumps(data)
return 'Content-MD5', base64_md5(data)
def compute_md5_header(data: dict) -> Tuple[str, str]:
data_str = json.dumps(data)
return 'Content-MD5', base64_md5(data_str)


def compute_date_header() -> tuple:
def compute_date_header() -> Tuple[str, str]:
return 'Date', compute_timestamp()


def compute_auth_header(
headers: list, endpoint: str, api_key: str, secret_key: str
) -> tuple:
) -> Tuple[str, str]:
verify_secret = calculate_checksum(endpoint, headers, secret_key)
return 'Authorization', f'APIAuth {api_key}:{verify_secret}'

Expand All @@ -29,9 +30,9 @@ def calculate_checksum(endpoint: str, headers: list, secret_key: str) -> str:
"""
Calculate checksum based on https://www.arcusfi.com/api/v3/#authentication
"""
headers = dict(headers)
content_md5 = headers['Content-MD5']
date = headers['Date']
headers_dict = dict(headers)
content_md5 = headers_dict['Content-MD5']
date = headers_dict['Date']
verify_this = f'{CONTENT_TYPE},{content_md5},{endpoint},{date}'
verify_secret = hmac.new(
secret_key.encode('utf-8'), verify_this.encode('utf-8'), sha1
Expand Down
16 changes: 9 additions & 7 deletions arcus/client.py
@@ -1,5 +1,5 @@
import os
from typing import Dict, Optional
from typing import Dict, Optional, Union

import requests

Expand Down Expand Up @@ -37,7 +37,7 @@ def __init__(
# refers to: https://github.com/cuenca-mx/arcus-read-only
proxy: Optional[str] = None,
):
self.headers = {}
self.headers: Dict[str, str] = dict()
self.session = requests.Session()
self.sandbox = sandbox
self.proxy = proxy
Expand All @@ -46,10 +46,11 @@ def __init__(
primary_secret or os.environ['ARCUS_SECRET_KEY'],
)

self.topup_key: Optional[ApiKey]
try:
self.topup_key = ApiKey(
topup_user or os.environ.get('TOPUP_API_KEY'),
topup_secret or os.environ.get('TOPUP_SECRET_KEY', ''),
topup_user or os.environ.get('TOPUP_API_KEY') or '',
topup_secret or os.environ.get('TOPUP_SECRET_KEY') or '',
)
except ValueError:
self.topup_key = None
Expand Down Expand Up @@ -80,25 +81,26 @@ def request(
**kwargs,
) -> dict:
url = self.base_url + endpoint
api_key: Union[str, ApiKey]
if not self.proxy:
if topup and self.topup_key:
api_key = self.topup_key
else:
api_key = self.api_key
headers = self._build_headers(api_key, endpoint, api_version, data)
else:
if topup:
if topup and self.topup_key:
api_key = self.topup_key.user
else:
api_key = self.api_key.user
headers = {'X-ARCUS-API-KEY': api_key}
# GET request with Body is not allowed on CloudFront
data = None
data = dict()
response = self.session.request(
method,
url,
headers={**self.headers, **headers},
json=data,
json=data if data else None,
**kwargs,
)
self._check_response(response)
Expand Down
240 changes: 227 additions & 13 deletions arcus/exc.py
Expand Up @@ -46,51 +46,265 @@ def __str__(self):


class InvalidAccountNumber(UnprocessableEntity):
def __init__(self, code: str, account_number: str, biller_id: int):
def __init__(self, account_number: str, biller_id: int, **kwargs):
message = (
f'{account_number} is an invalid account_number for biller '
f'{biller_id}'
)
super().__init__(
code, message, biller_id=biller_id, account_number=account_number
message=message,
biller_id=biller_id,
account_number=account_number,
**kwargs,
)


class InvalidOperation(UnprocessableEntity):
def __init__(self, code: str, transaction_id: Union[int, str]):
def __init__(self, transaction_id: Union[int, str], **kwargs):
message = f'Unable to cancel the transaction {transaction_id}'
super().__init__(code, message, transaction_id=transaction_id)
super().__init__(
message=message, transaction_id=transaction_id, **kwargs
)


class InvalidAmount(UnprocessableEntity):
pass
def __init__(self, amount: float, **kwargs):
message = f'Invalid amount: {amount}'
super().__init__(message=message, **kwargs)


class AlreadyPaid(UnprocessableEntity):
def __init__(self, code: str):
def __init__(self, **kwargs):
message = f'Payment already made'
super().__init__(code, message)
super().__init__(message=message, **kwargs)


class RecurrentPayments(UnprocessableEntity):
def __init__(self, code: str):
def __init__(self, **kwargs):
message = f'Recurrent payments enabled'
super().__init__(code, message)
super().__init__(message=message, **kwargs)


class DuplicatedPayment(UnprocessableEntity):
def __init__(self, code: str, amount: float):
def __init__(self, amount: float, **kwargs):
message = f'Duplicated payment for {amount}'
super().__init__(code, message, amount=amount)
super().__init__(message=message, amount=amount, **kwargs)


class IncompleteAmount(UnprocessableEntity):
def __init__(self, code: str, amount: float):
def __init__(self, amount: float, **kwargs):
message = (
f'Incomplete payment amount of {amount}, must pay full balance'
)
super().__init__(code, message, amount=amount)
super().__init__(message=message, amount=amount, **kwargs)


class Forbidden(ArcusException):
"""Method Not Allowed"""


class UnexpectedError(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Unexpected error. Failed to make the consult'
super().__init__(message=message, **kwargs)


class InvalidOrMissingParameters(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Invalid or missing parameters'
super().__init__(message=message, **kwargs)


class InvalidBalance(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Account Balance is 0 or not enough'
super().__init__(message=message, **kwargs)


class FailedConsult(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Failed to make the consult, please try again later'
super().__init__(message=message, **kwargs)


class LimitExceeded(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Limit of transactions exceeded'
super().__init__(message=message, **kwargs)


class BillerMaintenance(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Biller maintenance in progress, please try again later'
super().__init__(message=message, **kwargs)


class ReversalNotSupported(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Reversal not supported'
super().__init__(message=message, **kwargs)


class BillerConnection(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Timeout from biller'
super().__init__(message=message, **kwargs)


class CancellationError(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Error with performing the cancellation'
super().__init__(message=message, **kwargs)


class OverdueBill(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Overdue Bill'
super().__init__(message=message, **kwargs)


class InvalidPhone(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Invalid Phone Number'
super().__init__(message=message, **kwargs)


class FraudSuspected(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Fraud suspected'
super().__init__(message=message, **kwargs)


class InvalidCurrency(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Invalid Currency'
super().__init__(message=message, **kwargs)


class InvalidCompany(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Invalid company'
super().__init__(message=message, **kwargs)


class InvalidBarCode(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Invalid Barcode Format.'
super().__init__(message=message, **kwargs)


class InvalidCustomerStatus(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Invalid Customer Status.'
super().__init__(message=message, **kwargs)


class DailyPaymentsLimit(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'The maximum number of payments on this day was reached'
super().__init__(message=message, **kwargs)


class BalanceNotFound(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Can\'t get balance.'
super().__init__(message=message, **kwargs)


class InvalidSubscription(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Invalid Subscription ID. Verify the ID or create a new'
super().__init__(message=message, **kwargs)


class InvalidPOS(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'POS number is invalid'
super().__init__(message=message, **kwargs)


class AccessDenied(UnprocessableEntity):
def __init__(self, **kwargs):
message = f'Processor Access Denied'
super().__init__(message=message, **kwargs)


ARCUS_EXCEPTIONS = dict(
R1=InvalidAccountNumber,
R2=InvalidAccountNumber,
R3=IncompleteAmount,
R4=InvalidAccountNumber,
R5=InvalidAccountNumber,
R6=InvalidBiller,
R7=RecurrentPayments,
R8=AlreadyPaid,
R9=UnexpectedError,
R10=InvalidAccountNumber,
R11=IncompleteAmount,
R12=AlreadyPaid,
R13=InvalidOrMissingParameters,
R14=InvalidAmount,
R15=InvalidBalance,
R16=FailedConsult,
R17=InvalidAmount,
R18=LimitExceeded,
R19=InvalidBiller,
R20=InvalidBiller,
R21=InvalidBiller,
R22=BillerMaintenance,
R23=ReversalNotSupported,
R24=BillerConnection,
R25=CancellationError,
R26=InvalidOperation,
R27=OverdueBill,
R28=InvalidAccountNumber,
R29=InvalidAccountNumber,
R30=InvalidCompany,
R31=InvalidPhone,
R32=InvalidAmount,
R33=FraudSuspected,
R34=InvalidCurrency,
R35=FailedConsult,
R36=DuplicatedPayment,
R37=RecurrentPayments,
R41=IncompleteAmount,
R42=InvalidBalance,
R43=InvalidBarCode,
R44=InvalidCustomerStatus,
R45=DailyPaymentsLimit,
R46=BalanceNotFound,
R47=BillerConnection,
R48=BillerConnection,
R99=UnexpectedError,
R100=InvalidBalance,
R101=BalanceNotFound,
R102=InvalidAmount,
R103=InvalidOperation,
R104=InvalidSubscription,
R105=ReversalNotSupported,
R114=InvalidAmount,
R115=InvalidPOS,
R117=InvalidCompany,
R121=BalanceNotFound,
R122=BalanceNotFound,
R201=AccessDenied,
R202=BalanceNotFound,
R299=UnexpectedError,
)


def raise_arcus_exception(
ex: UnprocessableEntity,
account_number: Optional[str] = "",
biller_id: Union[int, str] = 0,
amount: Optional[float] = 0,
transaction_id: Optional[Union[int, str]] = 0,
):
exc = ARCUS_EXCEPTIONS[ex.code]
raise exc(
code=ex.code,
account_number=account_number,
biller_id=biller_id,
amount=amount,
transaction_id=transaction_id,
)

0 comments on commit 33d69ba

Please sign in to comment.